diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..43c7bc7deaa --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,27 @@ +# Changesets + +This project uses [changesets](https://github.com/changesets/changesets) for version management and changelog generation. + +## Adding a changeset + +When you make a change that should be released, run: + +```bash +pnpm changeset +``` + +This will prompt you to: +1. Select which packages are affected +2. Choose the bump type (patch/minor/major) +3. Write a summary of the changes + +## Lockstep versioning + +All `@stencil/*` packages are configured for **lockstep versioning** - they will always have the same version number. When any package changes, all packages are bumped together. + +## Release process + +1. Changesets accumulate in `.changeset/` as PRs are merged +2. When ready to release, run `pnpm changeset:version` to consume changesets and bump versions +3. Review the generated CHANGELOG.md files +4. Run `pnpm changeset:publish` to publish all packages diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..c0054d88e07 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [["@stencil/core", "@stencil/cli", "@stencil/dev-server", "@stencil/mock-doc"]], + "linked": [], + "access": "public", + "baseBranch": "v5", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 248472f0ceb..00000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,106 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'jsdoc', 'jest', 'simple-import-sort', 'wdio'], - extends: [ - 'plugin:jest/recommended', - // including prettier here ensures that we don't set any rules which will conflict - // with Prettier's formatting. Keep it last in the list so that nothing else messes - // with it! - 'prettier', - ], - rules: { - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - // TODO(STENCIL-452): Investigate using eslint-plugin-react to remove the need for varsIgnorePattern - varsIgnorePattern: '^(h|Fragment)$', - }, - ], - /** - * Configuration for Jest rules can be found here: - * https://github.com/jest-community/eslint-plugin-jest/tree/main/docs/rules - */ - 'jest/expect-expect': [ - 'error', - { - // we set this to `expect*` so that any function whose name starts with expect will be counted - // as an assertion function, allowing us to use functions to DRY up test suites. - assertFunctionNames: ['expect*'], - }, - ], - // we...have a number of things disabled :) - // TODO(STENCIL-488): Turn this rule back on once there are no violations of it remaining - 'jest/no-disabled-tests': ['off'], - // we use this in enough places that we don't want to do per-line disables - 'jest/no-conditional-expect': ['off'], - // this enforces that Jest hooks (e.g. `beforeEach`) are declared in test files in their execution order - // see here for details: https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/prefer-hooks-in-order.md - 'jest/prefer-hooks-in-order': ['warn'], - // this enforces that Jest hooks (e.g. `beforeEach`) are declared at the top of `describe` blocks - 'jest/prefer-hooks-on-top': ['warn'], - /** - * Configuration for the JSDoc plugin rules can be found at: - * https://github.com/gajus/eslint-plugin-jsdoc - */ - // validates that the name immediately following `@param` matches the parameter name in the function signature - // this works in conjunction with "jsdoc/require-param" - 'jsdoc/check-param-names': [ - 'error', - { - // if `checkStructured` is `true`, it asks that the JSDoc describe the fields being destructured. - // turn this off to not leak function internals/discourage describing them - checkDestructured: false, - }, - ], - // require that jsdoc attached to a method/function require one `@param` per parameter - 'jsdoc/require-param': [ - 'error', - { - // if `checkStructured` is `true`, it asks that the JSDoc describe the fields being destructured. - // turn this off to not leak function internals/discourage describing them - checkDestructured: false, - // always check setters as they should require a parameter (by definition) - checkSetters: true, - }, - ], - 'jsdoc/require-param-description': ['error'], - // rely on TypeScript types to be the source of truth, minimize verbosity in comments - 'jsdoc/require-param-type': ['off'], - 'jsdoc/require-returns': ['error'], - 'jsdoc/require-returns-check': ['error'], - 'jsdoc/require-returns-description': ['error'], - // rely on TypeScript types to be the source of truth, minimize verbosity in comments - 'jsdoc/require-returns-type': ['off'], - 'no-cond-assign': 'error', - 'no-var': 'error', - 'prefer-const': 'error', - 'prefer-rest-params': 'error', - 'prefer-spread': 'error', - 'simple-import-sort/exports': 'error', - 'simple-import-sort/imports': 'error', - }, - overrides: [ - { - // the stencil entry point still uses `var`, ignore errors related to it - files: 'bin/**', - rules: { - 'no-var': 'off', - }, - }, - { - // we don't want to use jest-related lint rules in the wdio tests - files: 'test/wdio/**/*.tsx', - rules: { - 'jest/expect-expect': 'off', - 'wdio/await-expect': 'error', - }, - }, - ], - // inform ESLint about the global variables defined in a Jest context - // see https://github.com/jest-community/eslint-plugin-jest/#usage - env: { - 'jest/globals': true, - }, -}; diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3c4e5ccbc7c..3f4072a1824 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,133 +1,72 @@ # Stencil Continuous Integration (CI) -Continuous integration (CI) is an important aspect of any project, and is used to verify and validate the changes to the -codebase work as intended, to avoid introducing regressions (bugs), and to adhere to coding standards (e.g. formatting -rules). It provides a consistent means of performing a series of checks over the entire codebase on behalf of the team. - -This document explains Stencil's CI setup. +This document explains Stencil's CI setup for the v5 monorepo. ## CI Environment -Stencil's CI system runs on GitHub Actions. -GitHub Actions allow developers to declare a series of _workflows_ to run following an _event_ in the repository, or on -a set schedule. - -The workflows that are run as a part of Stencil's CI process are declared as YAML files, and are stored in the same -directory as this file. -Each workflow file is explained in greater depth in the [workflows section](#workflows) of this document. +Stencil's CI runs on GitHub Actions using pnpm and supports Node.js 22 and 24. -## Workflows +## Workflow Structure -This section describes each of Stencil's GitHub Actions workflows. -Each of these tasks below are codified as [reusable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows). +```mermaid +graph TD; + build[Build] + + build --> quality[Quality] + build --> unit[Unit Tests] + build --> test-build[Build Tests] + build --> test-integration[Integration Tests] + build --> test-runtime[Runtime Tests] + build --> test-special-config[Special Config Tests] + build --> test-ssr[SSR Tests] + build --> test-starter[Component Starter] +``` -Generally speaking, workflows are designed to be declarative in nature. -As such, this section does not intend to duplicate the details of each workflow, but rather give a high level overview -of each one and mention nuances of each. +## Workflows ### Main (`main.yml`) -The main workflow for Stencil can be found in `main.yml` in this directory. -This workflow is the entrypoint of Stencil's CI system, and initializes every workflow & job that runs. +The orchestrator workflow that runs on push to `main`/`v5` branches and on pull requests. ### Build (`build.yml`) -This workflow is responsible for building Stencil and validating the resultant artifact. - -### Format (`format.yml`) - -This workflow is responsible for validating that the code adheres to the Stencil team's formatting configuration before -a pull request is merged. - -### Dev Release (`release-dev.yml`) - -This workflow initiates a developer build of Stencil from the `main` branch. -It is intended to be manually invoked by a member of the Stencil team. - -### Nightly Release (`release-nightly.yml`) - -This workflow initiates a nightly build of Stencil from the `main` branch. -A nightly build is similar to a 'Dev Release', except that: -- it is run on a set cadence (it is not expectedthat a developer to manually invoke it) -- it is published to the npm registry under the 'nightly' tag - -### Test Analysis (`test-analysis.yml`) - -This workflow is responsible for running the Stencil analysis testing suite. +Builds all packages and uploads artifacts for downstream jobs. -### Test End-to-End (`test-e2e.yml`) +### Quality (`quality.yml`) -This workflow is responsible for running the Stencil end-to-end testing suite. -This suite does _not_ run Stencil's BrowserStack tests. -Those are handled by a [separate workflow](#browserstack-browserstackyml). +Runs quality checks (Linux only): +- `pnpm format:check` - Code formatting (oxfmt) +- `pnpm lint:check` - Linting (oxlint) +- `pnpm typecheck` - TypeScript type checking +- `pnpm knip` - Unused code detection -### Test Unit (`test-unit.yml`) +### Test Workflows -This workflow is responsible for running the Stencil unit testing suite. +| Workflow | Matrix | Description | +|----------|--------|-------------| +| `test-unit.yml` | Linux | Unit tests for packages (`pnpm test`) | +| `test-build.yml` | Linux/Windows × Node 22/24 | Build test suite (`test/build`) | +| `test-integration.yml` | Linux/Windows × Node 22/24 | Integration tests (`test/integration`) | +| `test-runtime.yml` | Linux/Windows × Node 22/24 | Runtime tests (`test/runtime`) | +| `test-special-config.yml` | Linux/Windows × Node 22/24 | Special config tests (`test/special-config`) | +| `test-ssr.yml` | Linux/Windows × Node 22/24 | SSR tests (`test/ssr`) | +| `test-component-starter.yml` | Linux/Windows × Node 22/24 | Smoke test with component starter template | -### WebdriverIO Tests (`test-wdio.yml`) +## Release Workflows -This workflow runs our integration tests which assert that various Stencil -features work correctly when components using them are built and then rendered -in actual browsers. We run these tests using -[WebdriverIO](https://webdriver.io/) against Firefox, Chrome, and Edge. - -For more information on how those tests are set up please see the [WebdriverIO -test README](../../test/wdio/README.md). - -### Design - -#### Overview - -Most of the workflows above are contingent on the build finishing (otherwise there would be nothing to run against). -The diagram below displays the dependencies between each workflow. - -```mermaid -graph LR; - build-core-->test-analysis; - build-core-->test-e2e; - build-core-->test-unit; - format; -``` - -Making each 'task' a reusable workflow allows CI to run more jobs in parallel, improving the throughput of Stencil's CI. -All resusable workflows can be found in the [workflows directory](.). -This is a GitHub Actions convention that cannot be overridden. - -#### Running Tests - -All test-related jobs require the build to finish first. -Upon successful completion of the build workflow, each test workflow will start. - -The test-running workflows have been designed to run in parallel and are configured to run against several operating -systems & versions of node. -For a test workflow that theoretically runs on Ubuntu and Windows operating systems and targets Node v14, v16 and v18, a -single test workflow may spawn several jobs: - -```mermaid -graph LR; - test-analysis-->ubuntu-node14; - test-analysis-->ubuntu-node16; - test-analysis-->ubuntu-node18; - test-analysis-->windows-node14; - test-analysis-->windows-node16; - test-analysis-->windows-node18; -``` +Release workflows are managed separately and support both v4 (legacy) and v5 (monorepo with changesets). -These 'os-node jobs' (e.g. `ubuntu-node16`) are designed to _not_ prematurely stop their sibling jobs should one of -them fail. -This allows the opportunity for the sibling test jobs to potentially pass, and reduce the number of runners that need to -be spun up again should a developer wish to 're-run failed jobs'. -Should a developer feel that it is more appropriate to re-run all os-node jobs, they may do so using GitHub's 're-run -all jobs' options in the GitHub Actions UI. +| Workflow | Description | +|----------|-------------| +| `release-dev.yml` | Developer builds from main | +| `release-nightly.yml` | Nightly builds | +| `release-production.yml` | Production releases | +| `publish-npm.yml` | NPM publishing | -#### Concurrency +## Test Matrix -When a `git push` is made to a branch, Stencil's CI is designed to stop existing job(s) associated with the workflow + -branch. -A new CI run (of each workflow) will begin upon stopping the existing job(s) using the new `HEAD` of the branch. +Integration test workflows use `fail-fast: false` so sibling jobs continue even if one fails. This reduces the need to re-run all jobs when investigating failures. -## Repository Configuration +## Concurrency -Each of the workflows described in the [workflows section](#workflows) of this document must be configured in the -Stencil GitHub repository to be _required_ to pass in order to land code in the `main` branch. \ No newline at end of file +When a `git push` is made to a branch, existing CI jobs for that branch are cancelled and a new run begins. diff --git a/.github/workflows/actions/check-git-context/action.yml b/.github/workflows/actions/check-git-context/action.yml deleted file mode 100644 index f7907ac7ef3..00000000000 --- a/.github/workflows/actions/check-git-context/action.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: 'Check Git Context' -description: 'checks for a dirty git context, failing if the context is dirty' -runs: - using: composite - steps: - - name: Git status check - # here we check that there are no changed / new files. - # we use `git status`, grep out the build zip used throughout CI, - # and check if there are more than 0 lines in the output. - run: if [[ $(git status --short | grep -c -v stencil-core-build.zip) -ne 0 ]]; then STATUS=$(git status --verbose); printf "%s" "$STATUS"; git diff | cat; exit 1; fi - shell: bash diff --git a/.github/workflows/actions/download-archive/action.yml b/.github/workflows/actions/download-archive/action.yml deleted file mode 100644 index 26d92589ba5..00000000000 --- a/.github/workflows/actions/download-archive/action.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: 'Stencil Archive Download' -description: 'downloads and decompresses an archive from a previous job' -inputs: - path: - description: 'location to decompress the archive to' - filename: - description: 'the name of the decompressed artifact' - name: - description: 'name of the archive to decompress' -runs: - using: 'composite' - steps: - - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 - with: - name: ${{ inputs.name }} - path: ${{ inputs.path }} - - - name: Extract Archive - run: unzip -q -o ${{ inputs.path }}/${{ inputs.filename }} -d ${{ inputs.path }} - shell: bash diff --git a/.github/workflows/actions/get-core-dependencies/action.yml b/.github/workflows/actions/get-core-dependencies/action.yml index 186ae4e2053..d0bc8e04b2a 100644 --- a/.github/workflows/actions/get-core-dependencies/action.yml +++ b/.github/workflows/actions/get-core-dependencies/action.yml @@ -1,20 +1,17 @@ name: 'Get Core Dependencies' -description: 'sets the node version & initializes core dependencies' +description: 'Sets up pnpm, node version & installs dependencies' runs: using: composite steps: - # this overrides previous versions of the node runtime that was set. - # jobs that need a different version of the Node runtime should explicitly - # set their node version after running this step + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Use Node Version from Volta - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: './package.json' - cache: 'npm' + cache: 'pnpm' - name: Install Dependencies - run: | - npm ci \ - && npm run install.jest - + run: pnpm install --frozen-lockfile shell: bash diff --git a/.github/workflows/actions/install-playwright/action.yml b/.github/workflows/actions/install-playwright/action.yml new file mode 100644 index 00000000000..0298ba34e33 --- /dev/null +++ b/.github/workflows/actions/install-playwright/action.yml @@ -0,0 +1,37 @@ +name: 'Install Playwright Browsers' +description: 'Installs Playwright browsers with caching' +inputs: + working-directory: + description: 'Directory containing the Playwright installation to use' + required: false + default: 'test/ssr' +runs: + using: composite + steps: + - name: Get Playwright version + id: playwright-version + working-directory: ${{ inputs.working-directory }} + run: echo "version=$(npx playwright --version)" >> $GITHUB_OUTPUT + shell: bash + + - name: Cache Playwright browsers + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + id: playwright-cache + with: + path: | + ~/.cache/ms-playwright + ~/Library/Caches/ms-playwright + ~/AppData/Local/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright Browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + working-directory: ${{ inputs.working-directory }} + run: npx playwright install --with-deps chromium + shell: bash + + - name: Install Playwright system deps (cache hit) + if: steps.playwright-cache.outputs.cache-hit == 'true' && runner.os == 'Linux' + working-directory: ${{ inputs.working-directory }} + run: npx playwright install-deps chromium + shell: bash diff --git a/.github/workflows/actions/upload-archive/action.yml b/.github/workflows/actions/upload-archive/action.yml deleted file mode 100644 index 8142eefee8f..00000000000 --- a/.github/workflows/actions/upload-archive/action.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: 'Stencil Archive Upload' -description: 'compresses and uploads an archive to be reused across jobs' -inputs: - paths: - description: 'paths to files or directories to archive (recursive)' - output: - description: 'output file name' - name: - description: 'name of the archive to upload' -runs: - using: 'composite' - steps: - - name: Create Archive - run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }} - shell: bash - - - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - with: - name: ${{ inputs.name }} - path: ${{ inputs.output }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c1041d7456..7e19b573afe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,43 +1,30 @@ -name: Build Stencil +name: Build on: workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows permissions: contents: read jobs: - build_core: - name: Core - strategy: - matrix: - os: ['ubuntu-22.04', 'windows-latest'] - runs-on: ${{ matrix.os }} + build: + name: Build + runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Get Core Dependencies uses: ./.github/workflows/actions/get-core-dependencies - - name: Core Build - run: npm run build -- --ci - shell: bash - - - name: Validate Build - run: npm run test.dist - shell: bash - - - name: Validate Testing - run: npm run test.testing - shell: bash + - name: Build + run: pnpm build - name: Upload Build Artifacts - if: ${{ matrix.os == 'ubuntu-22.04' }} - uses: ./.github/workflows/actions/upload-archive + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: stencil-core - output: stencil-core-build.zip - paths: cli compiler dev-server internal mock-doc scripts/build screenshot sys testing + name: stencil-build + path: | + packages/*/dist/ + packages/*/bin/ + retention-days: 1 diff --git a/.github/workflows/lint-and-format.yml b/.github/workflows/lint-and-format.yml deleted file mode 100644 index 299f384e976..00000000000 --- a/.github/workflows/lint-and-format.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Lint and Format Stencil (Check) - -on: - merge_group: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - format: - name: Check - runs-on: 'ubuntu-22.04' - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: ESLint - run: npm run lint - - - name: Prettier Check - run: npm run prettier.dry-run - shell: bash - - - name: Spellcheck - run: npm run spellcheck diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 939712874a6..e6ca519640b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,7 @@ on: push: branches: - 'main' - - 'stencil/v4-dev' + - 'v5' pull_request: branches: - '**' @@ -18,55 +18,48 @@ permissions: contents: read jobs: - build_core: + # Build runs first + build: name: Build uses: ./.github/workflows/build.yml - lint_and_format: - name: Lint and Format - uses: ./.github/workflows/lint-and-format.yml + # Everything else runs in parallel after build + quality: + name: Quality + needs: [build] + uses: ./.github/workflows/quality.yml - type_tests: - name: Type Tests - needs: [build_core] - uses: ./.github/workflows/test-types.yml + unit_tests: + name: Unit Tests + needs: [build] + uses: ./.github/workflows/test-unit.yml - analysis_tests: - name: Analysis Tests - needs: [build_core] - uses: ./.github/workflows/test-analysis.yml + build_tests: + name: Build Tests + needs: [build] + uses: ./.github/workflows/test-build.yml - docs_build_tests: - name: Docs Build Tests - needs: [build_core] - uses: ./.github/workflows/test-docs-build.yml + integration_tests: + name: Integration Tests + needs: [build] + uses: ./.github/workflows/test-integration.yml - bundler_tests: - name: Bundler Tests - needs: [build_core] - uses: ./.github/workflows/test-bundlers.yml + runtime_tests: + name: Runtime Tests + needs: [build] + uses: ./.github/workflows/test-runtime.yml - copytask_tests: - name: Copy Task Tests - needs: [build_core] - uses: ./.github/workflows/test-copytask.yml + special_config_tests: + name: Special Config Tests + needs: [build] + uses: ./.github/workflows/test-special-config.yml + + ssr_tests: + name: SSR Tests + needs: [build] + uses: ./.github/workflows/test-ssr.yml component_starter_tests: name: Component Starter Smoke Test - needs: [build_core] + needs: [build] uses: ./.github/workflows/test-component-starter.yml - - e2e_tests: - name: E2E Tests - needs: [build_core] - uses: ./.github/workflows/test-e2e.yml - - unit_tests: - name: Unit Tests - needs: [build_core] - uses: ./.github/workflows/test-unit.yml - - wdio_tests: - name: WebdriverIO Tests - needs: [build_core] - uses: ./.github/workflows/test-wdio.yml diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 00000000000..58603dbe166 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,39 @@ +name: Quality + +on: + workflow_call: + +permissions: + contents: read + +jobs: + quality: + name: Quality + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Core Dependencies + uses: ./.github/workflows/actions/get-core-dependencies + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: stencil-build + path: packages/ + + - name: Format Check + run: pnpm format:check + + - name: Lint Check + run: pnpm lint:check + + - name: Type Check + run: pnpm typecheck + + - name: Knip + run: pnpm knip + + - name: Spellcheck + run: pnpm spellcheck diff --git a/.github/workflows/test-analysis.yml b/.github/workflows/test-analysis.yml deleted file mode 100644 index 2091967d2c6..00000000000 --- a/.github/workflows/test-analysis.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Analysis Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - analysis_test: - name: (${{ matrix.os }}.${{ matrix.node }}) - strategy: - fail-fast: false - matrix: - node: ['20', '22'] - os: ['ubuntu-latest', 'windows-latest'] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Use Node ${{ matrix.node }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: Bundle Size Test - run: npm run test.bundle-size - shell: bash - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 00000000000..cfeb1350a97 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,38 @@ +name: Build Tests + +on: + workflow_call: + +permissions: + contents: read + +jobs: + test_build: + name: Build (${{ matrix.os }}, node ${{ matrix.node }}) + strategy: + fail-fast: false + matrix: + node: ['22', '24'] + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Core Dependencies + uses: ./.github/workflows/actions/get-core-dependencies + + - name: Use Node ${{ matrix.node }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ matrix.node }} + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: stencil-build + path: packages/ + + - name: Build Tests + run: pnpm --filter "@stencil-core-tests/build" test + shell: bash diff --git a/.github/workflows/test-bundlers.yml b/.github/workflows/test-bundlers.yml deleted file mode 100644 index 3a80a6cbd30..00000000000 --- a/.github/workflows/test-bundlers.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Bundler Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - bundler_tests: - name: Verify Bundlers - runs-on: 'ubuntu-22.04' - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: Bundler Tests - run: npm run test.bundlers - shell: bash - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.github/workflows/test-component-starter.yml b/.github/workflows/test-component-starter.yml index 9d03188cc23..5f591bceac6 100644 --- a/.github/workflows/test-component-starter.yml +++ b/.github/workflows/test-component-starter.yml @@ -2,76 +2,45 @@ name: Component Starter Smoke Test on: workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows permissions: contents: read jobs: - analysis_test: - name: (${{ matrix.os }}.node-${{ matrix.node }}) + component_starter: + name: Component Starter (${{ matrix.os }}, node ${{ matrix.node }}) strategy: fail-fast: false matrix: - node: ['20', '22'] + node: ['22', '24'] os: ['ubuntu-latest', 'windows-latest'] runs-on: ${{ matrix.os }} steps: - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Get Core Dependencies uses: ./.github/workflows/actions/get-core-dependencies - name: Use Node ${{ matrix.node }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node }} - cache: 'npm' - - name: Create Pack Directory - # `mkdir` will fail if this directory already exists. - # in the next steps, we'll immediately put the packed build archive in this directory. - # between that and excluding `*.tgz` files in `.gitignore`, that _should_ make it safe enough for us to later - # use `mv` to rename the `npm pack`ed tarball - run: mkdir stencil-pack-destination - shell: bash - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - name: stencil-core - path: ./stencil-pack-destination - filename: stencil-core-build.zip - - - name: Copy package.json - # need `package.json` in order to run `npm pack` - run: cp package.json ./stencil-pack-destination - shell: bash - - - name: Copy bin - # `bin/` isn't a part of the compiled output (therefore not in the build archive). - # we need this entrypoint for stencil to run. - run: cp -R bin ./stencil-pack-destination - shell: bash - - - name: Remove node_modules - # clear out our local `node_modules/` so that they're not linked to in any way when `npm pack` is run - run: rm -rf node_modules/ - shell: bash + name: stencil-build + path: packages/ - - name: Pack the Build Archive - run: npm pack - working-directory: ./stencil-pack-destination + - name: Pack @stencil/core + run: pnpm pack --pack-destination ../../ + working-directory: ./packages/core shell: bash - - name: Move the Stencil Build Artifact - # there isn't a great way to get the output of `npm pack`, just grab the most recent from our destination - # directory and hope for the best. - # - # we don't set the working-directory here to avoid having to deal with relative paths in the destination arg - run: mv $(ls -t stencil-pack-destination/*.tgz | head -1) stencil-eval.tgz + - name: Pack @stencil/cli + run: pnpm pack --pack-destination ../../ + working-directory: ./packages/cli shell: bash - name: Initialize Component Starter @@ -79,17 +48,24 @@ jobs: shell: bash - name: Install Component Starter Dependencies - run: npm install + run: npm install --legacy-peer-deps working-directory: ./tmp-component-starter shell: bash - - name: Install Stencil Eval - run: npm i ../stencil-eval.tgz + - name: Install Stencil from Pack + run: npm install ../stencil-cli-*.tgz ../stencil-core-*.tgz --legacy-peer-deps working-directory: ./tmp-component-starter shell: bash - name: Install Playwright Browsers - run: npx playwright install + uses: ./.github/workflows/actions/install-playwright + with: + working-directory: ./tmp-component-starter + + - name: Run Stencil Migrations + run: npx stencil migrate + working-directory: ./tmp-component-starter + shell: bash - name: Build Starter Project run: npm run build @@ -97,40 +73,6 @@ jobs: shell: bash - name: Test Starter Project - run: npm run test -- --no-build # the project was just built, don't build it again + run: npm run test -- --no-build working-directory: ./tmp-component-starter shell: bash - - # TEMPORARILY DISABLE - # Disable until we update the generate task in v5 to work with new testing setup. - - # - name: Test npx stencil generate - # # `stencil generate` doesn't have a way to skip file generation, so we provide it with a component name and run - # # `echo` with a newline to select "all files" to generate (and use -e to interpret that backslash for a newline) - # run: echo -e '\n' | npm run generate -- hello-world - # working-directory: ./tmp-component-starter - # shell: bash - - # - name: Verify Files Exist - # run: | - # file_list=( - # src/components/hello-world/hello-world.tsx - # src/components/hello-world/hello-world.css - # src/components/hello-world/test/hello-world.spec.tsx - # src/components/hello-world/test/hello-world.e2e.ts - # ) - # for file in "${file_list[@]}"; do - # if [ -f "$file" ]; then - # echo "File '$file' exists." - # else - # echo "File '$file' does not exist." - # exit 1 - # fi - # done - # working-directory: ./tmp-component-starter - # shell: bash - - # - name: Test Generated Files - # run: npm run test - # working-directory: ./tmp-component-starter - # shell: bash diff --git a/.github/workflows/test-copytask.yml b/.github/workflows/test-copytask.yml deleted file mode 100644 index 9ee47f28eb8..00000000000 --- a/.github/workflows/test-copytask.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Copy Task Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - bundler_tests: - name: Verify Copy Task - runs-on: 'ubuntu-22.04' - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: Bundler Tests - run: npm run test.copytask - shell: bash - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.github/workflows/test-docs-build.yml b/.github/workflows/test-docs-build.yml deleted file mode 100644 index 2a6ab771304..00000000000 --- a/.github/workflows/test-docs-build.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Docs OT Build Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - docs_build_test: - name: (${{ matrix.os }}.${{ matrix.node }}) - strategy: - fail-fast: false - matrix: - node: ['20', '22'] - os: ['ubuntu-latest', 'windows-latest'] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Use Node ${{ matrix.node }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: Docs Build Tests - run: npm run test.docs-build - shell: bash - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml deleted file mode 100644 index 5c7cd91d057..00000000000 --- a/.github/workflows/test-e2e.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: E2E Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - e2e_test: - name: (${{ matrix.os }}.${{ matrix.node }}) - strategy: - fail-fast: false - matrix: - node: ['20', '22'] - os: ['ubuntu-latest', 'windows-latest'] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Use Node ${{ matrix.node }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: End-to-End Tests - uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0 - with: - timeout_minutes: 10 - max_attempts: 3 - command: npm run test.end-to-end -- --ci - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml new file mode 100644 index 00000000000..b80fb036c96 --- /dev/null +++ b/.github/workflows/test-integration.yml @@ -0,0 +1,41 @@ +name: Integration Tests + +on: + workflow_call: + +permissions: + contents: read + +jobs: + test_integration: + name: Integration (${{ matrix.os }}, node ${{ matrix.node }}) + strategy: + fail-fast: false + matrix: + node: ['22', '24'] + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Core Dependencies + uses: ./.github/workflows/actions/get-core-dependencies + + - name: Use Node ${{ matrix.node }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ matrix.node }} + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: stencil-build + path: packages/ + + - name: Install Playwright Browsers + uses: ./.github/workflows/actions/install-playwright + + - name: Integration Tests + run: pnpm --filter "@stencil-core-tests/integration" test + shell: bash diff --git a/.github/workflows/test-runtime.yml b/.github/workflows/test-runtime.yml new file mode 100644 index 00000000000..cb2f412ca2f --- /dev/null +++ b/.github/workflows/test-runtime.yml @@ -0,0 +1,41 @@ +name: Runtime Tests + +on: + workflow_call: + +permissions: + contents: read + +jobs: + test_runtime: + name: Runtime (${{ matrix.os }}, node ${{ matrix.node }}) + strategy: + fail-fast: false + matrix: + node: ['22', '24'] + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Core Dependencies + uses: ./.github/workflows/actions/get-core-dependencies + + - name: Use Node ${{ matrix.node }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ matrix.node }} + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: stencil-build + path: packages/ + + - name: Install Playwright Browsers + uses: ./.github/workflows/actions/install-playwright + + - name: Runtime Tests + run: pnpm --filter "@stencil-core-tests/runtime" test + shell: bash diff --git a/.github/workflows/test-special-config.yml b/.github/workflows/test-special-config.yml new file mode 100644 index 00000000000..4b5e446dc86 --- /dev/null +++ b/.github/workflows/test-special-config.yml @@ -0,0 +1,41 @@ +name: Special Config Tests + +on: + workflow_call: + +permissions: + contents: read + +jobs: + test_special_config: + name: Special Config (${{ matrix.os }}, node ${{ matrix.node }}) + strategy: + fail-fast: false + matrix: + node: ['22', '24'] + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Core Dependencies + uses: ./.github/workflows/actions/get-core-dependencies + + - name: Use Node ${{ matrix.node }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ matrix.node }} + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: stencil-build + path: packages/ + + - name: Install Playwright Browsers + uses: ./.github/workflows/actions/install-playwright + + - name: Special Config Tests + run: pnpm --filter "@stencil-core-tests/special-config" test + shell: bash diff --git a/.github/workflows/test-ssr.yml b/.github/workflows/test-ssr.yml new file mode 100644 index 00000000000..f058517aec8 --- /dev/null +++ b/.github/workflows/test-ssr.yml @@ -0,0 +1,41 @@ +name: SSR Tests + +on: + workflow_call: + +permissions: + contents: read + +jobs: + test_ssr: + name: SSR (${{ matrix.os }}, node ${{ matrix.node }}) + strategy: + fail-fast: false + matrix: + node: ['22', '24'] + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get Core Dependencies + uses: ./.github/workflows/actions/get-core-dependencies + + - name: Use Node ${{ matrix.node }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ matrix.node }} + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: stencil-build + path: packages/ + + - name: Install Playwright Browsers + uses: ./.github/workflows/actions/install-playwright + + - name: SSR Tests + run: pnpm --filter "@stencil-core-tests/ssr" test + shell: bash diff --git a/.github/workflows/test-types.yml b/.github/workflows/test-types.yml deleted file mode 100644 index ec695e06a2a..00000000000 --- a/.github/workflows/test-types.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Type Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - unit_test: - name: Type Tests - strategy: - fail-fast: false - matrix: - node: ['20', '22'] - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Use Node ${{ matrix.node }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: Type Tests - run: npm run test.type-tests - shell: bash diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index fe117f0d9dc..d891ecee70e 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -2,44 +2,27 @@ name: Unit Tests on: workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows permissions: contents: read jobs: unit_test: - name: (${{ matrix.os }}.${{ matrix.node }}) - strategy: - fail-fast: false - matrix: - node: ['20', '22'] - os: ['ubuntu-latest', 'windows-latest'] - runs-on: ${{ matrix.os }} + name: Unit Tests + runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Get Core Dependencies uses: ./.github/workflows/actions/get-core-dependencies - - name: Use Node ${{ matrix.node }} - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip + name: stencil-build + path: packages/ - name: Unit Tests - run: npm run test.jest + run: pnpm test shell: bash - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.github/workflows/test-wdio.yml b/.github/workflows/test-wdio.yml deleted file mode 100644 index 80f25aef4da..00000000000 --- a/.github/workflows/test-wdio.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: WebdriverIO Tests - -on: - workflow_call: - # Make this a reusable workflow, no value needed - # https://docs.github.com/en/actions/using-workflows/reusing-workflows - -permissions: - contents: read - -jobs: - wdio_test: - name: Run WebdriverIO Component Tests (${{ matrix.browser }}) - runs-on: ubuntu-22.04 - strategy: - matrix: - # browser: [CHROME, FIREFOX, EDGE] - browser: [CHROME] - - steps: - - name: Checkout Code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Get Core Dependencies - uses: ./.github/workflows/actions/get-core-dependencies - - - name: Use Node Version from Volta - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - # pull the version to use from the volta key in package.json - node-version-file: './test/wdio/package.json' - cache: 'npm' - - - name: Download Build Archive - uses: ./.github/workflows/actions/download-archive - with: - name: stencil-core - path: . - filename: stencil-core-build.zip - - - name: Run WebdriverIO Component Tests - run: npm run test.wdio - shell: bash - env: - BROWSER: ${{ matrix.browser }} - - - name: Check Git Context - uses: ./.github/workflows/actions/check-git-context diff --git a/.gitignore b/.gitignore index ce2837e8f8d..d9d46cebda4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ log.txt .idea/ .vscode/ .claude +.agents .history/ .sass-cache/ .versions/ @@ -56,3 +57,13 @@ unused-exports*.txt # readme file from docs-readme that is expected to be missing so it will be emitted in full test/docs-readme/custom-readme-output-overwrite-if-missing-missing/components/styleurls-component/readme.md + + +.turbo +# temporary ignore whilst we complete v5 +/src +dist +www +hydrate +loader +test-results \ No newline at end of file diff --git a/.npmrc b/.npmrc deleted file mode 100644 index ca42cd5199b..00000000000 --- a/.npmrc +++ /dev/null @@ -1,8 +0,0 @@ -# By default, Node allocates 2 GB for a process to run. -# When building Stencil, it may reach that point before the garbage collector is invoked, causing an out-of-memory -# related failure. -# If this value is changed, please ensure that it works both locally and in a continuous integration environment in -# a repeatable manner (i.e. it can run many times, one after the other, without failing due to out-of-memory errors). -node_options=--max-old-space-size=4096 -# TODO(STENCIL-1141): remove `PUPPETEER_DOWNLOAD_BASE_URL` once support for Node v16 is dropped -PUPPETEER_DOWNLOAD_BASE_URL=https://storage.googleapis.com/chrome-for-testing-public diff --git a/.nvmrc b/.nvmrc index 2c022021b85..a4a7a41bca7 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22.13.0 +v24.14.0 diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 00000000000..0d01c36a9f4 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,24 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "singleQuote": true, + "jsxSingleQuote": true, + "ignorePatterns": ["**/dist/**", "**/*.d.ts", "**/*.md"], + "sortImports": { + "groups": [ + "value-builtin", + "value-external", + "type-external", + { "newlinesBetween": true }, + "value-internal", + "type-internal", + { "newlinesBetween": true }, + "value-parent", + "value-sibling", + "value-index", + "type-parent", + "type-sibling", + "type-index" + ], + "newlinesBetween": false + } +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000000..455fb730fe7 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,56 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "import", "jsdoc"], + "categories": { + "correctness": "error", + "suspicious": "warn", + "pedantic": "off", + "style": "off", + "perf": "warn" + }, + "rules": { + "no-cond-assign": "error", + "no-var": "error", + "prefer-const": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "no-async-promise-executor": "off", + "no-control-regex": "off", + "no-await-in-loop": "off", + "no-new": "off", + "no-unmodified-loop-condition": "off", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/triple-slash-reference": "off", + "import/no-unassigned-import": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^(_.*|h|Fragment|IntrinsicElements)$" + } + ], + "jsdoc/require-param": [ + "error", + { + "checkDestructured": false, + "checkSetters": true + } + ], + "jsdoc/require-param-description": "error", + "jsdoc/require-param-type": "off", + "jsdoc/require-returns": "error", + "jsdoc/require-returns-description": "error", + "jsdoc/require-returns-type": "off" + }, + "ignorePatterns": [ + "node_modules/**", + "dist/**", + "**/dist/**", + "**/*.d.ts", + "**/*.spec.ts", + "**/_test_/**", + "packages/core/src/client/polyfills/**", + "packages/core/src/declarations/stencil-public-runtime.ts" + ] +} diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 02f004900cf..00000000000 --- a/.prettierignore +++ /dev/null @@ -1,69 +0,0 @@ -# npm packages in the root of the project and subdirectories -node_modules/ - -# submodule packages -/build/ -/cli/ -/compiler/ -/dev-server/ -/internal/ -/mock-doc/ -/sys/ -/testing/ - -# shims that are attributed to external authors, and are minified out of the box -/src/client/polyfills/core-js.js -/src/client/polyfills/dom.js -/src/client/polyfills/es5-html-element.js -/src/client/polyfills/index.js -/src/client/polyfills/system.js - -# project notes shared with the community -/notes/ - -# output of building various scripts that support the project -/scripts/build/ - -# these files are intentionally incomplete JavaScript files (they are parts of an Immediately Invoked Funciton -# Expression (IIFE)). They act as a 'header' and 'footer' that get prepended and appended to the Stencil compiler. -# Ignore them so Prettier doesn't fail and the 'prettier-ignore' pragma doesn't get put in the compiler output. -/scripts/bundles/helpers/compiler-cjs-intro.js -/scripts/bundles/helpers/compiler-cjs-outro.js - -# code coverage output -coverage/ - -# output from compiling Stencil projects for testing purposes -test/**/dist/ -test/**/dist-react/ -test/**/hydrate/ -test/**/test-output/ -test/**/www/ -test/**/components.d.ts -test/end-to-end/screenshot/ -test/end-to-end/docs.d.ts -test/end-to-end/docs.json -test/docs-json/docs.d.ts -test/docs-json/docs.json - -# minified angular that exists in the test directory - -# generated screenshot files -/screenshot/index.js -/screenshot/package.json -/screenshot/pixel-match.js -/screenshot/*.d.ts - -# third party scripts -src/mock-doc/third-party/jquery.ts -test/wdio/slot-ng-if/assets/ - - -# wdio test output -test/wdio/test-components -test/wdio/test-components-no-external-runtime -test/wdio/www-global-script/ -test/wdio/www-prerender-script -test/wdio/www-invisible-prehydration/ -test/wdio/test-ts-target-output -test/wdio/test-components-autoloader \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 639e38d8557..cb6c38ad1ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,18 @@ } ], "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Current Vitest File", + "autoAttachChildProcesses": true, + "skipFiles": ["/**", "**/node_modules/**"], + "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", + "args": ["run", "${file}"], + "smartStep": true, + "console": "integratedTerminal", + "cwd": "${workspaceRoot}/packages/core" + }, { "type": "node", "request": "launch", diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..c50dbb01467 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,27 @@ +This is Stencil - a toolchain for building reusable, scalable Design Systems built with Custom Elements. + +This is a major version development branch - breaking changes are ok. + +Read the `./V5_PLANNING.md` file at session start for more details on the goals and plans for this major version. Add and amend this document as needed to keep track of the major version planning and progress. + +Always seek to replace code with more modern standards and more modern 3rd party dependencies where possible, and remove older code and dependencies that are no longer needed - but please discuss this with the user before doing so. + +User should not have to ask you for your opinion explicitly. Always evaluate what the user is asking you to do, and voice your concerns before proceeding if you don’t think it's a good idea. If possible, propose a better solution, but you can voice concerns even without one. + +This applies even to direct requests to revert or simplify. Still evaluate whether your original approach was better. The user may be missing important context. If there was a solid reasoning you suggested that approach, push back with reasoning instead of silently complying. + +Assume any package starting with `@stencil/` is potentially updatable and suggest changes if you think it would be beneficial. + +`as any` is very rarely an acceptable solution. Check with the user before using it, and use better alternatives whenever possible - don't be lazy. + +Never commit changes without the user explicitly asking you to. Always ask for confirmation before committing, and provide a clear summary of the changes that will be committed. If the user asks for changes after you’ve provided a summary but before you’ve committed, update the summary to reflect the new changes before asking for confirmation again. + +Keep all code comments terse as you can ... but don't delete existing comments without good reason. + +Generally, non-trivial changes should pass - +- `pnpm build` +- `pnpm typecheck` +- `pnpm test` +- `pnpm lint` + +To run a specific unit test: `pnpm -F PACKAGE_NAME test TEST_NAME` \ No newline at end of file diff --git a/V5_PLANNING.md b/V5_PLANNING.md new file mode 100644 index 00000000000..e0f9e861230 --- /dev/null +++ b/V5_PLANNING.md @@ -0,0 +1,198 @@ +# Stencil v5 Planning Document + +> **Living Document** - Track progress on v5 modernization + +## Vision + +Modernize Stencil after 10 years: shed tech debt, embrace modern tooling, simplify architecture, streamline user experience. + +--- + +## Major Goals + +### 1. 🧪 Remove Integrated Testing +**Status:** 📋 Replacement packages ready - need to remove integrated testing +- `@stencil/vitest` + `@stencil/playwright` audited and ready +- Still need to migrate Stencil's internal tests from jest to vitest +- Still migrating integration / e2e test suites (in `packages/core/tests/`) + +### 2. 🗑️ Update / Remove Legacy Features +**Status:** In Progress +- ES5 builds → ✅ REMOVED +- Internal CommonJS → ✅ REMOVED (Pure ESM, Node 18+) +- Ancient polyfills → ✅ REMOVED +- In-browser compilation → REMOVE +- `*-sys` in-memory file-system → Replace with TypeScript incremental APIs (see Tasks) +- Hand-crafted dev server / HMR → modernize as `@stencil/dev-server` + +### 3. 🔧 Build System +**Status:** ✅ Complete +- **tsdown** for all package builds (single config per package) +- **pnpm -r** for build orchestration (no Turborepo) + +### 4. 📦 Mono-repo Restructure +**Status:** ✅ Complete (dev-server pending) +- `packages/core/` (@stencil/core), `packages/cli/` (@stencil/cli), `packages/mock-doc/` (@stencil/mock-doc) + +### 5. 🔗 CLI/Core Dependency Architecture +**Status:** ✅ Complete +- Broke circular dependency between CLI and Core. Core standalone, CLI thin. + +### 6. Update Public Build Chain +**Status:** 📋 Planned +- Migrate from rollup to rolldown +- Potentially move from typescript to tsgo + +### 7. 📤 Output Target Modernization +**Status:** ✅ Complete +- Renamed output targets for clarity (`dist` → `loader-bundle`, `dist-custom-elements` → `standalone`, etc.) +- Elevated sub-outputs to first-class citizens (`types`, `stencil-rebundle`) +- See Breaking Changes for full details + +### 8. 📁 Global Styles & Assets Modernization +**Status:** ✅ Complete +- New `global-style` and `assets` output targets (first-class, auto-generated) +- Unified `dist/assets/` location shared by all outputs +- See Breaking Changes for full details + +### 9. 🏷️ Release Management: Changesets +**Status:** 📋 Planned +- Adopt [Changesets](https://github.com/changesets/changesets) for monorepo release management with lockstep versioning + +--- + +## Breaking Changes + +- `@stencil/core/internal` → `@stencil/core/runtime` +- `@stencil/core/internal/client` → `@stencil/core/runtime/client` +- `@stencil/core/internal/hydrate` → `@stencil/core/runtime/server` +- `@stencil/core/cli` → `@stencil/cli` +- `@stencil/core/dev-server` → `@stencil/dev-server` +- `openBrowser` now defaults to `false`. Override with `--open` flag or `openBrowser: true` in config. +- **Output target renames:** + - `dist` → `loader-bundle` (default dir: `dist/loader-bundle/`) + - `dist-custom-elements` → `standalone` (default dir: `dist/standalone/`) + - `dist-hydrate-script` → `ssr` (default dir: `dist/ssr/`) + - `dist-collection` (sub-output) → `stencil-rebundle` (first-class output, default dir: `dist/stencil-rebundle/`, auto-generated in prod) + - `dist-types` (sub-output) → `types` (first-class output, default dir: `dist/types/`, auto-generated in prod) + - `collectionDir` and `typesDir` config options removed from `loader-bundle` config + - Run `stencil migrate` to automatically update your config +- `loader-bundle` and `ssr` output targets no longer generate CJS bundles by default. Add `cjs: true` to your output target config to restore CJS output. +- `ssr` no longer generates a `package.json` file. Use `exports` in your library's main `package.json` to expose the SSR script. +- **ES5 build output removed.** The `buildEs5` config option, `--es5` CLI flag, and all ES5-related output have been removed. Stencil now targets ES2017+ only. IE11 and Edge 18 and below are no longer supported. +- **@Component decorator `shadow`, `scoped`, and `formAssociated` properties removed.** Use the new unified `encapsulation` property instead: + - `shadow: true` → `encapsulation: { type: 'shadow' }` + - `shadow: { delegatesFocus: true }` → `encapsulation: { type: 'shadow', delegatesFocus: true }` + - `scoped: true` → `encapsulation: { type: 'scoped' }` + - Default (no encapsulation) → `encapsulation: { type: 'none' }` (optional, 'none' is default) + - **New feature:** `encapsulation: { type: 'shadow', mode: 'closed' }` enables closed shadow DOM + - **New feature:** Per-component slot patches via `encapsulation: { type: 'scoped', patches: ['children', 'clone', 'insert'] }` + - `formAssociated: true` → Use `@AttachInternals()` decorator instead (auto-sets `formAssociated: true`) + - To use `@AttachInternals` without form association: `@AttachInternals({ formAssociated: false })` + - Run `stencil migrate --dry-run` to preview automatic migration, or `stencil migrate` to apply changes +- **`buildDist` and `buildDocs` config options removed.** Use `skipInDev` on individual output targets for granular control. +- **`--esm` CLI flag removed.** Configure `skipInDev` on output targets instead. +- **`--prod` CLI flag removed.** Production is the default. Use `--dev` to opt into a development build. +- **`devMode` config option removed from `stencil.config.ts`.** Build mode is now exclusively controlled by the `--dev` CLI flag. +- **`isPrimaryPackageOutputTarget` removed from output targets.** Package.json validation now auto-detects based on configured outputs. +- **`validatePrimaryPackageOutputTarget` config option renamed to `validatePackageJson`.** +- **Export maps generation uses smart defaults.** Priority: `loader-bundle` > `standalone` for the root export. Types always come from the `types` output target. +- **`collection` field in package.json renamed to `stencilRebundle`.** +- **Output file extensions modernized:** + - ESM files now use `.js` extension (was `.esm.js`) + - CJS files now use `.cjs` extension (was `.cjs.js`) + - Backwards compat: forwarding module `.esm.js` generated for existing CDN consumers +- **Global styles and assets modernized:** + - New `global-style` output target (first-class, auto-generated when `globalStyle` config exists) + - New `assets` output target (first-class, auto-generated when components have `assetsDirs`) + - Unified location: `dist/assets/` for both global styles and component assets + - `copyAssets` option removed from `loader-bundle` and `www` output targets + - `extras.addGlobalStyleToComponents` removed - use `inject` option on `global-style` output target instead: + - `inject: 'none'` - don't inject, load stylesheet externally + - `inject: 'client'` - inject into components on client only + - `inject: 'all'` - inject into components on both client and SSR + - Auto-generated `global-style` (from `globalStyle` config) defaults to `inject: 'client'` (preserves v4 behavior) + - Explicitly configured `global-style` outputs default to `inject: 'none'` +- **`esmLoaderPath` config option renamed to `loaderPath`** in `loader-bundle` output target. + +--- + +## New Features + +- **`global-style` output target now supports explicit `input`** - specify CSS source file directly on output target instead of using `globalStyle` config +- **`global-style` output target now supports `fileName`** - customize output filename +- **`global-style` output target now supports `inject`** - control whether styles are injected into component shadow DOMs (`'none'`, `'client'`, `'all'`) +- **Multiple `global-style` outputs supported** - build separate CSS bundles from different input files, each with independent `inject` settings +- **`www` can now use standalone loader** + +--- + +## Tasks + +### 🛢️ Eliminate Barrel Exports in `src/utils` +- [ ] Use [barrel-breaker](https://github.com/nicolo-ribaudo/babel-plugin-transform-barrels) or similar tool +- [ ] The `src/utils/index.ts` barrel causes bundling issues (e.g., `minimatch` leaking into server/runner bundle) +- [ ] All imports should use direct paths + +### ⚠️ `*.sys` Patching (Assess) +- [ ] 40+ files still use `.sys.` patterns for in-memory file system operations +- [ ] Original plan: replace with TypeScript incremental APIs +- [ ] No reference implementation exists - needs investigation if this is still the right approach +- [ ] May be deferrable if not blocking other goals + +--- + +## 🚀 Watch Mode Fast Path (Planned) + +### Problem +Even single-file changes trigger full build pipeline (~500ms-1s). + +### Solution +Leverage `transpileModule()` for a "fast path" in watch mode: + +1. **Add shared context** to `transpileModule` - reuse existing `Program`/`TypeChecker` from watch build +2. **Change detection** - compare old vs new component metadata: + - API changed (props/events/methods)? → Full rebuild + - JSDoc changed + docs targets exist? → Regen docs only + - Neither? → Hot-swap module only +3. **Non-component fast path** - plain `.ts` files skip Stencil transforms entirely + +### Expected Impact +| Change Type | Current | With Fast Path | +|-------------|---------|----------------| +| Internal logic change | ~500ms-1s | **< 50ms** | +| JSDoc change (with docs) | ~500ms-1s | **< 100ms** | +| API change (new prop) | ~500ms-1s | ~500ms-1s (unchanged) | + +~80% of dev changes are internal logic → massive improvement for typical workflow. + +--- + +## Architecture Reference + +``` +packages/ +├── core/ @stencil/core (compiler + runtime) +├── cli/ @stencil/cli +├── mock-doc/ @stencil/mock-doc +└── dev-server/ @stencil/dev-server (planned) +``` + +**Build:** tsdown + pnpm workspaces | **Module format:** Pure ESM | **Node floor:** 22 LTS + +### Key Decisions +- Don't bundle TypeScript/terser/parse5 - use as normal dependencies +- Keep Terser over SWC for minification (SWC produces ~18KB vs Terser's ~11.8KB for runtime) + +--- + +## Build Commands + +```bash +pnpm run build # Build all packages +pnpm run dev # Watch mode +``` + +--- + +*Last updated: 2026-04-29* diff --git a/bin/stencil b/bin/stencil deleted file mode 100755 index 5370db38ea5..00000000000 --- a/bin/stencil +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -var minimumVersion = '16.0'; -var futureDeprecationMinVersion = '16.0'; -var recommendedVersion = '18.16'; -var currentVersion = process.versions.node; - -function isNodeLT(v) { - var check = v.split('.').map(Number); - var node = currentVersion.split('.').map(Number); - return node[0] < check[0] || (node[0] === check[0] && node[1] < check[1]); -} - -if (isNodeLT(minimumVersion)) { - console.error( - '\nYour current version of Node is v' + - currentVersion + - ', however Stencil requires v' + - minimumVersion + - '.0 or greater. It is recommended to use an Active LTS version of Node (https://nodejs.org/en/about/releases/).\n', - ); - process.exit(1); -} - -if (isNodeLT(futureDeprecationMinVersion)) { - console.warn( - '\nIn an upcoming major release of Stencil, Node v' + recommendedVersion + '.0 or higher will be required.\n', - ); -} else if (isNodeLT(recommendedVersion)) { - console.warn( - '\nYour current version of Node is v' + - currentVersion + - ", however Stencil's recommendation is v" + - recommendedVersion + - '.0 or greater. Note that future versions of Stencil will eventually remove support for older Node versions and an Active LTS version is recommended (https://nodejs.org/en/about/releases/).\n', - ); -} - -var cli = require('../cli/index.cjs'); -var nodeApi = require('../sys/node/index.js'); -var nodeLogger = nodeApi.createNodeLogger(); -var nodeSys = nodeApi.createNodeSys({ process: process, logger: nodeLogger }); - -nodeApi.setupNodeProcess({ process: process, logger: nodeLogger }); - -cli - .run({ - args: process.argv.slice(2), - logger: nodeLogger, - sys: nodeSys, - checkVersion: nodeApi.checkVersion, - }) - .catch(function (err) { - console.error('uncaught error', err); - process.exit(1); - }); diff --git a/cspell-code.json b/cspell-code.json index 2927c96cd28..3a1dc234cc1 100644 --- a/cspell-code.json +++ b/cspell-code.json @@ -8,11 +8,7 @@ } ], - "ignoreRegExpList": [ - "/`[a-zA-Z-_., /*']+`/g" - ], - "ignorePaths": ["**/node_modules/**", "**/third-party/**"], - "includeRegExpList": [ - "CStyleComment" - ] + "ignoreRegExpList": ["/`[a-zA-Z-_., /*']+`/g"], + "ignorePaths": ["**/node_modules/**"], + "includeRegExpList": ["CStyleComment"] } diff --git a/cspell-markdown.json b/cspell-markdown.json index 82261c88fbc..9aa02342b55 100644 --- a/cspell-markdown.json +++ b/cspell-markdown.json @@ -7,8 +7,6 @@ "addWords": true } ], - "ignoreRegExpList": [ - "/`[a-zA-Z-_., /*']+`/g" - ], + "ignoreRegExpList": ["/`[a-zA-Z-_., /*']+`/g"], "ignorePaths": ["**/node_modules/**", "**/third-party/**", "./CHANGELOG.md"] } diff --git a/cspell-wordlist.txt b/cspell-wordlist.txt index 721bbd8f9bc..adc2ea42b3a 100644 --- a/cspell-wordlist.txt +++ b/cspell-wordlist.txt @@ -3,6 +3,18 @@ !TurboRepo !Typescript !nodejs +addclass +connectedcallback +griff +insertbefore +linaria +MINIFYJS +OPTIMIZECSS +prehydrated +reparent +slotchange +textnode +utilises BUILDID Brotli Brunelle @@ -84,6 +96,7 @@ microtasks minifier myapp mycomponent +mylib myprop namespace nocheck @@ -139,6 +152,7 @@ vchildren vdom vermoji viewports +virtuals vkey vname vnode @@ -154,4 +168,8 @@ shadowrootdelegatesfocus jsxmode jsxdev jsxs -labelable \ No newline at end of file +labelable +lightningcss +cooldown +rebundle +regen \ No newline at end of file diff --git a/docs/compiler.md b/docs/compiler.md index cc9e91747f5..80615719094 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -1335,7 +1335,7 @@ import { Component } from '@stencil/core'; // Error: Cannot find module // Solution: Check your tsconfig.json { "compilerOptions": { - "moduleResolution": "node", + "moduleResolution": "bundler", "baseUrl": ".", "paths": { "@stencil/core": ["node_modules/@stencil/core"] @@ -1424,7 +1424,6 @@ describe('compiler', () => { { "scripts": { "build": "node scripts/build.js", - "build.prod": "node scripts/build.js --prod", "build.dev": "node scripts/build.js --dev", "watch": "node scripts/build.js --watch" } diff --git a/docs/hydrate.md b/docs/hydrate.md index 4baa2414c1a..cf6e4d4854c 100644 --- a/docs/hydrate.md +++ b/docs/hydrate.md @@ -48,7 +48,7 @@ This generates a hydrate module that can be imported in Node.js: ```typescript import { - hydrateDocument, + ssrDocument, renderToString, streamToString, createWindowFromHtml @@ -57,16 +57,16 @@ import { ### Core User APIs -#### hydrateDocument +#### ssrDocument Takes a DOM document and returns hydrated HTML: ```typescript -import { hydrateDocument, createWindowFromHtml } from 'yourpackage/hydrate'; +import { ssrDocument, createWindowFromHtml } from 'yourpackage/hydrate'; export async function hydrateComponents(template: string) { const win = createWindowFromHtml(template, Math.random().toString()) - const results = await hydrateDocument(win.document, { + const results = await ssrDocument(win.document, { url: 'https://example.com', userAgent: 'Node.js', cookie: 'session=abc123', @@ -100,7 +100,7 @@ Returns a readable stream for progressive rendering: ```typescript const stream = streamToString(htmlString, { serializeShadowRoot: 'scoped', - beforeHydrate: (doc) => { + beforeSsr: (doc) => { // Modify document before hydration } }); @@ -200,7 +200,7 @@ Generated hydrate app exports: ```typescript // hydrate/index.js module.exports = { - hydrateDocument, + ssrDocument, renderToString, createWindowFromHtml, serializeNodeToHtml @@ -209,18 +209,18 @@ module.exports = { ## Core APIs -### hydrateDocument +### ssrDocument **Location:** [`src/hydrate/runner/hydrate-document.ts`](../src/hydrate/runner/hydrate-document.ts) Hydrates an entire document: ```typescript -export const hydrateDocument = async ( +export const ssrDocument = async ( doc: Document, - options: HydrateDocumentOptions = {} -): Promise => { - const results: HydrateResults = { + options: SsrDocumentOptions = {} +): Promise => { + const results: SsrResults = { diagnostics: [], url: options.url || doc.location.href, title: doc.title, @@ -272,7 +272,7 @@ export const renderToString = async ( const doc = createDocument(html); // Hydrate the document - const hydrateResults = await hydrateDocument(doc, options); + const hydrateResults = await ssrDocument(doc, options); return { html: hydrateResults.html, @@ -595,7 +595,7 @@ const clientHydrate = ( **Location:** [`src/declarations/stencil-public-runtime.ts`](../src/declarations/stencil-public-runtime.ts) ```typescript -interface HydrateDocumentOptions { +interface SsrDocumentOptions { url?: string; userAgent?: string; cookie?: string; @@ -603,7 +603,7 @@ interface HydrateDocumentOptions { direction?: string; language?: string; buildId?: string; - clientHydrateAnnotations?: boolean; + clientSsrAnnotations?: boolean; constrainTimeouts?: boolean; timeout?: number; staticComponents?: string[]; @@ -620,7 +620,7 @@ interface HydrateDocumentOptions { ```typescript interface PrerenderConfig { entryUrls: string[]; - hydrateOptions?: HydrateDocumentOptions; + hydrateOptions?: SsrDocumentOptions; robotsTxt?: (opts: RobotsTxtOpts) => string; sitemapXml?: (opts: SitemapXmpOpts) => string; baseUrl?: string; diff --git a/docs/scripts.md b/docs/scripts.md index dd24455ae04..8e321c39e1f 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -239,10 +239,10 @@ Most Node.js dependencies are externalized except: ```bash # Development build -npm run build +npm run build -- --dev -# Production build -npm run build -- --prod +# Production build (default) +npm run build # Watch mode npm run build -- --watch diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index eefff0056c0..00000000000 --- a/jest.config.js +++ /dev/null @@ -1,68 +0,0 @@ -module.exports = { - moduleNameMapper: { - '@app-data': '/internal/app-data/index.cjs', - '@app-globals': '/internal/app-globals/index.cjs', - '@platform': '/internal/testing/index.js', - '@runtime': '/internal/testing/index.js', - '@stencil/core/cli': '/cli/index.cjs', - '@stencil/core/compiler': '/compiler/stencil.js', - '@stencil/core/mock-doc': '/mock-doc/index.cjs', - '@stencil/core/testing': '/testing/index.js', - '@sys-api-node': '/sys/node/index.js', - '@utils': '/src/utils', - '^typescript$': '/scripts/build/typescript-modified-for-jest.js', - '^@stencil/core/internal/app-data$': '/internal/app-data/index.cjs', - '^@stencil/core/internal/testing$': '/internal/testing/index.js', - }, - coverageDirectory: './coverage/', - coverageReporters: ['json', 'lcov', 'text', 'clover'], - coveragePathIgnorePatterns: ['^.*\\.stub\\.tsx?$'], - collectCoverageFrom: [ - '/scripts/**/*.{js,jsx,ts,tsx}', - '!/scripts/build/**/*.{js,jsx,ts,tsx}', - '/src/app-data/**/*.{js,jsx,ts,tsx}', - '/src/app-globals/**/*.{js,jsx,ts,tsx}', - '/src/cli/**/*.{js,jsx,ts,tsx}', - '/src/compiler/**/*.{js,jsx,ts,tsx}', - '/src/declarations/**/*.{js,jsx,ts,tsx}', - '/src/dev-server/**/*.{js,jsx,ts,tsx}', - '/src/hydrate/**/*.{js,jsx,ts,tsx}', - '/src/internal/**/*.{js,jsx,ts,tsx}', - '/src/mock-doc/**/*.{js,jsx,ts,tsx}', - '/src/runtime/**/*.{js,jsx,ts,tsx}', - '/src/screenshot/**/*.{js,jsx,ts,tsx}', - '/src/sys/node/**/*.{js,jsx,ts,tsx}', - '/src/testing/**/*.{js,jsx,ts,tsx}', - '/src/utils/**/*.{js,jsx,ts,tsx}', - ], - moduleFileExtensions: ['ts', 'tsx', 'js', 'mjs', 'jsx', 'json', 'd.ts'], - modulePathIgnorePatterns: ['/bin', '/www'], - setupFilesAfterEnv: ['/testing/jest-setuptestframework.js'], - testEnvironment: '/testing/jest-environment.js', - testPathIgnorePatterns: [ - '/.cache/', - '/.github/', - '/.stencil/', - '/.vscode/', - '/bin/', - '/build/', - '/cli/', - '/compiler/', - '/dev-server/', - '/dist/', - '/internal/', - '/mock-doc/', - '/node_modules/', - '/screenshot/', - '/sys/', - '/test/', - '/testing/', - ], - testRegex: '/(src|scripts)/.*\\.spec\\.(ts|tsx|js)$', - // TODO(STENCIL-307): Move away from Jasmine runner for internal Stencil tests as a part of the (internal) Jest 28+ upgrade - testRunner: 'jest-jasmine2', - transform: { - '^.+\\.(ts|tsx|jsx|css|mjs)$': '/testing/jest-preprocessor.js', - }, - watchPathIgnorePatterns: ['^.+\\.d\\.ts$'], -}; diff --git a/knip.json b/knip.json new file mode 100644 index 00000000000..d7e72e7f9b9 --- /dev/null +++ b/knip.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "ignoreWorkspaces": ["test/**"], + "ignoreBinaries": ["playwright", "stencil"], + "rules": { + "catalog": "off" + }, + "workspaces": { + "packages/core": { + "entry": [ + "build/version-utils.ts", + "src/runtime/bootstrap-loader.ts", + "src/server/runner/ssr-factory.ts", + "src/testing/platform/index.ts", + "src/testing/app-data.ts", + "src/index.d.mts", + "src/jsx-runtime.d.mts" + ], + "ignore": ["src/**/_test_/**"], + "ignoreDependencies": ["vitest-environment-stencil"] + }, + "packages/cli": {}, + "packages/mock-doc": {}, + "packages/dev-server": { + "ignore": ["src/server/worker-thread.js", "src/**/_test_/**"], + "ignoreDependencies": ["vitest-environment-stencil"] + } + } +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 1f715e01b58..00000000000 --- a/package-lock.json +++ /dev/null @@ -1,13899 +0,0 @@ -{ - "name": "@stencil/core", - "version": "4.43.4", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@stencil/core", - "version": "4.43.4", - "license": "MIT", - "bin": { - "stencil": "bin/stencil" - }, - "devDependencies": { - "@ionic/prettier-config": "^4.0.0", - "@jridgewell/source-map": "^0.3.6", - "@rollup/plugin-commonjs": "28.0.2", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "16.0.0", - "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.1.4", - "@types/eslint": "^8.4.6", - "@types/exit": "^0.1.31", - "@types/fs-extra": "^11.0.0", - "@types/graceful-fs": "^4.1.5", - "@types/jest": "^27.0.3", - "@types/listr": "^0.14.4", - "@types/node": "^24.6.2", - "@types/pixelmatch": "^5.2.4", - "@types/pngjs": "^6.0.1", - "@types/prompts": "^2.0.9", - "@types/semver": "^7.3.12", - "@types/ws": "^8.5.4", - "@types/yarnpkg__lockfile": "^1.1.5", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "@yarnpkg/lockfile": "^1.1.0", - "ansi-colors": "4.1.3", - "autoprefixer": "10.4.19", - "conventional-changelog-cli": "^5.0.0", - "cspell": "^8.0.0", - "css-what": "^7.0.0", - "dts-bundle-generator": "~9.5.0", - "esbuild": "^0.25.0", - "esbuild-plugin-replace": "^1.4.0", - "eslint": "^8.23.1", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-jest": "^28.0.0", - "eslint-plugin-jsdoc": "^50.0.0", - "eslint-plugin-simple-import-sort": "^12.0.0", - "eslint-plugin-wdio": "^8.24.12", - "execa": "9.3.0", - "exit": "^0.1.2", - "fs-extra": "^11.0.0", - "glob": "10.5.0", - "graceful-fs": "~4.2.6", - "jest": "^27.4.5", - "jest-cli": "^27.4.5", - "jest-environment-node": "^27.4.4", - "jquery": "https://github.com/jquery/jquery/tarball/c98597eaf5e144ee5e549cb41984687cd1033068", - "listr": "^0.14.3", - "magic-string": "^0.30.0", - "merge-source-map": "^1.1.0", - "mime-db": "^1.46.0", - "minimatch": "9.0.9", - "node-fetch": "3.3.2", - "open": "^9.0.0", - "open-in-editor": "2.2.0", - "parse5": "7.2.1", - "pixelmatch": "5.3.0", - "postcss": "^8.2.8", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", - "prettier": "3.3.1", - "prompts": "2.4.2", - "puppeteer": "^24.1.0", - "rimraf": "^6.0.1", - "rollup": "4.44.0", - "semver": "^7.3.7", - "terser": "5.37.0", - "tsx": "^4.19.2", - "typescript": "~5.8.3", - "webpack": "^5.75.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.10.0" - }, - "optionalDependencies": { - "@rollup/rollup-darwin-arm64": "4.44.0", - "@rollup/rollup-darwin-x64": "4.44.0", - "@rollup/rollup-linux-arm64-gnu": "4.44.0", - "@rollup/rollup-linux-arm64-musl": "4.44.0", - "@rollup/rollup-linux-x64-gnu": "4.44.0", - "@rollup/rollup-linux-x64-musl": "4.44.0", - "@rollup/rollup-win32-arm64-msvc": "4.44.0", - "@rollup/rollup-win32-x64-msvc": "4.44.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@conventional-changelog/git-client": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-1.0.1.tgz", - "integrity": "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/semver": "^7.5.5", - "semver": "^7.5.2" - }, - "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 - } - } - }, - "node_modules/@cspell/cspell-bundled-dicts": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-8.19.4.tgz", - "integrity": "sha512-2ZRcZP/ncJ5q953o8i+R0fb8+14PDt5UefUNMrFZZHvfTI0jukAASOQeLY+WT6ASZv6CgbPrApAdbppy9FaXYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/dict-ada": "^4.1.0", - "@cspell/dict-al": "^1.1.0", - "@cspell/dict-aws": "^4.0.10", - "@cspell/dict-bash": "^4.2.0", - "@cspell/dict-companies": "^3.1.15", - "@cspell/dict-cpp": "^6.0.8", - "@cspell/dict-cryptocurrencies": "^5.0.4", - "@cspell/dict-csharp": "^4.0.6", - "@cspell/dict-css": "^4.0.17", - "@cspell/dict-dart": "^2.3.0", - "@cspell/dict-data-science": "^2.0.8", - "@cspell/dict-django": "^4.1.4", - "@cspell/dict-docker": "^1.1.13", - "@cspell/dict-dotnet": "^5.0.9", - "@cspell/dict-elixir": "^4.0.7", - "@cspell/dict-en_us": "^4.4.3", - "@cspell/dict-en-common-misspellings": "^2.0.10", - "@cspell/dict-en-gb": "1.1.33", - "@cspell/dict-filetypes": "^3.0.11", - "@cspell/dict-flutter": "^1.1.0", - "@cspell/dict-fonts": "^4.0.4", - "@cspell/dict-fsharp": "^1.1.0", - "@cspell/dict-fullstack": "^3.2.6", - "@cspell/dict-gaming-terms": "^1.1.1", - "@cspell/dict-git": "^3.0.4", - "@cspell/dict-golang": "^6.0.20", - "@cspell/dict-google": "^1.0.8", - "@cspell/dict-haskell": "^4.0.5", - "@cspell/dict-html": "^4.0.11", - "@cspell/dict-html-symbol-entities": "^4.0.3", - "@cspell/dict-java": "^5.0.11", - "@cspell/dict-julia": "^1.1.0", - "@cspell/dict-k8s": "^1.0.10", - "@cspell/dict-kotlin": "^1.1.0", - "@cspell/dict-latex": "^4.0.3", - "@cspell/dict-lorem-ipsum": "^4.0.4", - "@cspell/dict-lua": "^4.0.7", - "@cspell/dict-makefile": "^1.0.4", - "@cspell/dict-markdown": "^2.0.10", - "@cspell/dict-monkeyc": "^1.0.10", - "@cspell/dict-node": "^5.0.7", - "@cspell/dict-npm": "^5.2.1", - "@cspell/dict-php": "^4.0.14", - "@cspell/dict-powershell": "^5.0.14", - "@cspell/dict-public-licenses": "^2.0.13", - "@cspell/dict-python": "^4.2.17", - "@cspell/dict-r": "^2.1.0", - "@cspell/dict-ruby": "^5.0.8", - "@cspell/dict-rust": "^4.0.11", - "@cspell/dict-scala": "^5.0.7", - "@cspell/dict-shell": "^1.1.0", - "@cspell/dict-software-terms": "^5.0.5", - "@cspell/dict-sql": "^2.2.0", - "@cspell/dict-svelte": "^1.0.6", - "@cspell/dict-swift": "^2.0.5", - "@cspell/dict-terraform": "^1.1.1", - "@cspell/dict-typescript": "^3.2.1", - "@cspell/dict-vue": "^3.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/cspell-json-reporter": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-8.19.4.tgz", - "integrity": "sha512-pOlUtLUmuDdTIOhDTvWxxta0Wm8RCD/p1V0qUqeP6/Ups1ajBI4FWEpRFd7yMBTUHeGeSNicJX5XeX7wNbAbLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-types": "8.19.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/cspell-pipe": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-8.19.4.tgz", - "integrity": "sha512-GNAyk+7ZLEcL2fCMT5KKZprcdsq3L1eYy3e38/tIeXfbZS7Sd1R5FXUe6CHXphVWTItV39TvtLiDwN/2jBts9A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/cspell-resolver": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-8.19.4.tgz", - "integrity": "sha512-S8vJMYlsx0S1D60glX8H2Jbj4mD8519VjyY8lu3fnhjxfsl2bDFZvF3ZHKsLEhBE+Wh87uLqJDUJQiYmevHjDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-directory": "^4.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/cspell-service-bus": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-8.19.4.tgz", - "integrity": "sha512-uhY+v8z5JiUogizXW2Ft/gQf3eWrh5P9036jN2Dm0UiwEopG/PLshHcDjRDUiPdlihvA0RovrF0wDh4ptcrjuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/cspell-types": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-8.19.4.tgz", - "integrity": "sha512-ekMWuNlFiVGfsKhfj4nmc8JCA+1ZltwJgxiKgDuwYtR09ie340RfXFF6YRd2VTW5zN7l4F1PfaAaPklVz6utSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/dict-ada": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.1.tgz", - "integrity": "sha512-E+0YW9RhZod/9Qy2gxfNZiHJjCYFlCdI69br1eviQQWB8yOTJX0JHXLs79kOYhSW0kINPVUdvddEBe6Lu6CjGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-al": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-al/-/dict-al-1.1.1.tgz", - "integrity": "sha512-sD8GCaZetgQL4+MaJLXqbzWcRjfKVp8x+px3HuCaaiATAAtvjwUQ5/Iubiqwfd1boIh2Y1/3EgM3TLQ7Q8e0wQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-aws": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.15.tgz", - "integrity": "sha512-aPY7VVR5Os4rz36EaqXBAEy14wR4Rqv+leCJ2Ug/Gd0IglJpM30LalF3e2eJChnjje3vWoEC0Rz3+e5gpZG+Kg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-bash": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-4.2.1.tgz", - "integrity": "sha512-SBnzfAyEAZLI9KFS7DUG6Xc1vDFuLllY3jz0WHvmxe8/4xV3ufFE3fGxalTikc1VVeZgZmxYiABw4iGxVldYEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/dict-shell": "1.1.1" - } - }, - "node_modules/@cspell/dict-companies": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.5.tgz", - "integrity": "sha512-H51R0w7c6RwJJPqH7Gs65tzP6ouZsYDEHmmol6MIIk0kQaOIBuFP2B3vIxHLUr2EPRVFZsMW8Ni7NmVyaQlwsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-cpp": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.12.tgz", - "integrity": "sha512-N4NsCTttVpMqQEYbf0VQwCj6np+pJESov0WieCN7R/0aByz4+MXEiDieWWisaiVi8LbKzs1mEj4ZTw5K/6O2UQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-cryptocurrencies": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-5.0.5.tgz", - "integrity": "sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-csharp": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.7.tgz", - "integrity": "sha512-H16Hpu8O/1/lgijFt2lOk4/nnldFtQ4t8QHbyqphqZZVE5aS4J/zD/WvduqnLY21aKhZS6jo/xF5PX9jyqPKUA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-css": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.18.tgz", - "integrity": "sha512-EF77RqROHL+4LhMGW5NTeKqfUd/e4OOv6EDFQ/UQQiFyWuqkEKyEz0NDILxOFxWUEVdjT2GQ2cC7t12B6pESwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-dart": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.1.tgz", - "integrity": "sha512-xoiGnULEcWdodXI6EwVyqpZmpOoh8RA2Xk9BNdR7DLamV/QMvEYn8KJ7NlRiTSauJKPNkHHQ5EVHRM6sTS7jdg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-data-science": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.9.tgz", - "integrity": "sha512-wTOFMlxv06veIwKdXUwdGxrQcK44Zqs426m6JGgHIB/GqvieZQC5n0UI+tUm5OCxuNyo4OV6mylT4cRMjtKtWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-django": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.5.tgz", - "integrity": "sha512-AvTWu99doU3T8ifoMYOMLW2CXKvyKLukPh1auOPwFGHzueWYvBBN+OxF8wF7XwjTBMMeRleVdLh3aWCDEX/ZWg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-docker": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.16.tgz", - "integrity": "sha512-UiVQ5RmCg6j0qGIxrBnai3pIB+aYKL3zaJGvXk1O/ertTKJif9RZikKXCEgqhaCYMweM4fuLqWSVmw3hU164Iw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-dotnet": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.10.tgz", - "integrity": "sha512-ooar8BP/RBNP1gzYfJPStKEmpWy4uv/7JCq6FOnJLeD1yyfG3d/LFMVMwiJo+XWz025cxtkM3wuaikBWzCqkmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-elixir": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-4.0.8.tgz", - "integrity": "sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-en_us": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.19.tgz", - "integrity": "sha512-JYYgzhGqSGuIMNY1cTlmq3zrNpehrExMHqLmLnSM2jEGFeHydlL+KLBwBYxMy4e73w+p1+o/rmAiGsMj9g3MCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-en-common-misspellings": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.6.tgz", - "integrity": "sha512-xV9yryOqZizbSqxRS7kSVRrxVEyWHUqwdY56IuT7eAWGyTCJNmitXzXa4p+AnEbhL+AB2WLynGVSbNoUC3ceFA==", - "dev": true, - "license": "CC BY-SA 4.0" - }, - "node_modules/@cspell/dict-en-gb": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", - "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-filetypes": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.13.tgz", - "integrity": "sha512-g6rnytIpQlMNKGJT1JKzWkC+b3xCliDKpQ3ANFSq++MnR4GaLiifaC4JkVON11Oh/UTplYOR1nY3BR4X30bswA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-flutter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-flutter/-/dict-flutter-1.1.1.tgz", - "integrity": "sha512-UlOzRcH2tNbFhZmHJN48Za/2/MEdRHl2BMkCWZBYs+30b91mWvBfzaN4IJQU7dUZtowKayVIF9FzvLZtZokc5A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-fonts": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.5.tgz", - "integrity": "sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-fsharp": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-fsharp/-/dict-fsharp-1.1.1.tgz", - "integrity": "sha512-imhs0u87wEA4/cYjgzS0tAyaJpwG7vwtC8UyMFbwpmtw+/bgss+osNfyqhYRyS/ehVCWL17Ewx2UPkexjKyaBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-fullstack": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.7.tgz", - "integrity": "sha512-IxEk2YAwAJKYCUEgEeOg3QvTL4XLlyArJElFuMQevU1dPgHgzWElFevN5lsTFnvMFA1riYsVinqJJX0BanCFEg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-gaming-terms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@cspell/dict-gaming-terms/-/dict-gaming-terms-1.1.2.tgz", - "integrity": "sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-git": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.7.tgz", - "integrity": "sha512-odOwVKgfxCQfiSb+nblQZc4ErXmnWEnv8XwkaI4sNJ7cNmojnvogYVeMqkXPjvfrgEcizEEA4URRD2Ms5PDk1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-golang": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.23.tgz", - "integrity": "sha512-oXqUh/9dDwcmVlfUF5bn3fYFqbUzC46lXFQmi5emB0vYsyQXdNWsqi6/yH3uE7bdRE21nP7Yo0mR1jjFNyLamg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-google": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-google/-/dict-google-1.0.9.tgz", - "integrity": "sha512-biL65POqialY0i4g6crj7pR6JnBkbsPovB2WDYkj3H4TuC/QXv7Pu5pdPxeUJA6TSCHI7T5twsO4VSVyRxD9CA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-haskell": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-4.0.6.tgz", - "integrity": "sha512-ib8SA5qgftExpYNjWhpYIgvDsZ/0wvKKxSP+kuSkkak520iPvTJumEpIE+qPcmJQo4NzdKMN8nEfaeci4OcFAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-html": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.12.tgz", - "integrity": "sha512-JFffQ1dDVEyJq6tCDWv0r/RqkdSnV43P2F/3jJ9rwLgdsOIXwQbXrz6QDlvQLVvNSnORH9KjDtenFTGDyzfCaA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-html-symbol-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.4.tgz", - "integrity": "sha512-afea+0rGPDeOV9gdO06UW183Qg6wRhWVkgCFwiO3bDupAoyXRuvupbb5nUyqSTsLXIKL8u8uXQlJ9pkz07oVXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-java": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-5.0.12.tgz", - "integrity": "sha512-qPSNhTcl7LGJ5Qp6VN71H8zqvRQK04S08T67knMq9hTA8U7G1sTKzLmBaDOFhq17vNX/+rT+rbRYp+B5Nwza1A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-julia": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-julia/-/dict-julia-1.1.1.tgz", - "integrity": "sha512-WylJR9TQ2cgwd5BWEOfdO3zvDB+L7kYFm0I9u0s9jKHWQ6yKmfKeMjU9oXxTBxIufhCXm92SKwwVNAC7gjv+yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-k8s": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-k8s/-/dict-k8s-1.0.12.tgz", - "integrity": "sha512-2LcllTWgaTfYC7DmkMPOn9GsBWsA4DZdlun4po8s2ysTP7CPEnZc1ZfK6pZ2eI4TsZemlUQQ+NZxMe9/QutQxg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-kotlin": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-kotlin/-/dict-kotlin-1.1.1.tgz", - "integrity": "sha512-J3NzzfgmxRvEeOe3qUXnSJQCd38i/dpF9/t3quuWh6gXM+krsAXP75dY1CzDmS8mrJAlBdVBeAW5eAZTD8g86Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-latex": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.4.tgz", - "integrity": "sha512-YdTQhnTINEEm/LZgTzr9Voz4mzdOXH7YX+bSFs3hnkUHCUUtX/mhKgf1CFvZ0YNM2afjhQcmLaR9bDQVyYBvpA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-lorem-ipsum": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-4.0.5.tgz", - "integrity": "sha512-9a4TJYRcPWPBKkQAJ/whCu4uCAEgv/O2xAaZEI0n4y1/l18Yyx8pBKoIX5QuVXjjmKEkK7hi5SxyIsH7pFEK9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-lua": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-4.0.8.tgz", - "integrity": "sha512-N4PkgNDMu9JVsRu7JBS/3E/dvfItRgk9w5ga2dKq+JupP2Y3lojNaAVFhXISh4Y0a6qXDn2clA6nvnavQ/jjLA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-makefile": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-makefile/-/dict-makefile-1.0.5.tgz", - "integrity": "sha512-4vrVt7bGiK8Rx98tfRbYo42Xo2IstJkAF4tLLDMNQLkQ86msDlYSKG1ZCk8Abg+EdNcFAjNhXIiNO+w4KflGAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-markdown": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.12.tgz", - "integrity": "sha512-ufwoliPijAgWkD/ivAMC+A9QD895xKiJRF/fwwknQb7kt7NozTLKFAOBtXGPJAB4UjhGBpYEJVo2elQ0FCAH9A==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@cspell/dict-css": "^4.0.18", - "@cspell/dict-html": "^4.0.12", - "@cspell/dict-html-symbol-entities": "^4.0.4", - "@cspell/dict-typescript": "^3.2.3" - } - }, - "node_modules/@cspell/dict-monkeyc": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.11.tgz", - "integrity": "sha512-7Q1Ncu0urALI6dPTrEbSTd//UK0qjRBeaxhnm8uY5fgYNFYAG+u4gtnTIo59S6Bw5P++4H3DiIDYoQdY/lha8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-node": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.8.tgz", - "integrity": "sha512-AirZcN2i84ynev3p2/1NCPEhnNsHKMz9zciTngGoqpdItUb2bDt1nJBjwlsrFI78GZRph/VaqTVFwYikmncpXg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-npm": { - "version": "5.2.17", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.17.tgz", - "integrity": "sha512-0yp7lBXtN3CtxBrpvTu/yAuPdOHR2ucKzPxdppc3VKO068waZNpKikn1NZCzBS3dIAFGVITzUPtuTXxt9cxnSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-php": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.0.15.tgz", - "integrity": "sha512-iepGB2gtToMWSTvybesn4/lUp4LwXcEm0s8vasJLP76WWVkq1zYjmeS+WAIzNgsuURyZ/9mGqhS0CWMuo74ODw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-powershell": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-5.0.15.tgz", - "integrity": "sha512-l4S5PAcvCFcVDMJShrYD0X6Huv9dcsQPlsVsBGbH38wvuN7gS7+GxZFAjTNxDmTY1wrNi1cCatSg6Pu2BW4rgg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-public-licenses": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.15.tgz", - "integrity": "sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-python": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.19.tgz", - "integrity": "sha512-9S2gTlgILp1eb6OJcVZeC8/Od83N8EqBSg5WHVpx97eMMJhifOzePkE0kDYjyHMtAFznCQTUu0iQEJohNQ5B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/dict-data-science": "^2.0.9" - } - }, - "node_modules/@cspell/dict-r": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-2.1.1.tgz", - "integrity": "sha512-71Ka+yKfG4ZHEMEmDxc6+blFkeTTvgKbKAbwiwQAuKl3zpqs1Y0vUtwW2N4b3LgmSPhV3ODVY0y4m5ofqDuKMw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-ruby": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.9.tgz", - "integrity": "sha512-H2vMcERMcANvQshAdrVx0XoWaNX8zmmiQN11dZZTQAZaNJ0xatdJoSqY8C8uhEMW89bfgpN+NQgGuDXW2vmXEw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-rust": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.12.tgz", - "integrity": "sha512-z2QiH+q9UlNhobBJArvILRxV8Jz0pKIK7gqu4TgmEYyjiu1TvnGZ1tbYHeu9w3I/wOP6UMDoCBTty5AlYfW0mw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-scala": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.8.tgz", - "integrity": "sha512-YdftVmumv8IZq9zu1gn2U7A4bfM2yj9Vaupydotyjuc+EEZZSqAafTpvW/jKLWji2TgybM1L2IhmV0s/Iv9BTw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-shell": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-shell/-/dict-shell-1.1.1.tgz", - "integrity": "sha512-T37oYxE7OV1x/1D4/13Y8JZGa1QgDCXV7AVt3HLXjn0Fe3TaNDvf5sU0fGnXKmBPqFFrHdpD3uutAQb1dlp15g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-software-terms": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.8.tgz", - "integrity": "sha512-iwCHLP11OmVHEX2MzE8EPxpPw7BelvldxWe5cJ3xXIDL8TjF2dBTs2noGcrqnZi15SLYIlO8897BIOa33WHHZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-sql": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-sql/-/dict-sql-2.2.1.tgz", - "integrity": "sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-svelte": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-svelte/-/dict-svelte-1.0.7.tgz", - "integrity": "sha512-hGZsGqP0WdzKkdpeVLBivRuSNzOTvN036EBmpOwxH+FTY2DuUH7ecW+cSaMwOgmq5JFSdTcbTNFlNC8HN8lhaQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-swift": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-2.0.6.tgz", - "integrity": "sha512-PnpNbrIbex2aqU1kMgwEKvCzgbkHtj3dlFLPMqW1vSniop7YxaDTtvTUO4zA++ugYAEL+UK8vYrBwDPTjjvSnA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-terraform": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-terraform/-/dict-terraform-1.1.3.tgz", - "integrity": "sha512-gr6wxCydwSFyyBKhBA2xkENXtVFToheqYYGFvlMZXWjviynXmh+NK/JTvTCk/VHk3+lzbO9EEQKee6VjrAUSbA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-typescript": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", - "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dict-vue": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-3.0.5.tgz", - "integrity": "sha512-Mqutb8jbM+kIcywuPQCCaK5qQHTdaByoEO2J9LKFy3sqAdiBogNkrplqUK0HyyRFgCfbJUgjz3N85iCMcWH0JA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspell/dynamic-import": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-8.19.4.tgz", - "integrity": "sha512-0LLghC64+SiwQS20Sa0VfFUBPVia1rNyo0bYeIDoB34AA3qwguDBVJJkthkpmaP1R2JeR/VmxmJowuARc4ZUxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/url": "8.19.4", - "import-meta-resolve": "^4.1.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@cspell/filetypes": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-8.19.4.tgz", - "integrity": "sha512-D9hOCMyfKtKjjqQJB8F80PWsjCZhVGCGUMiDoQpcta0e+Zl8vHgzwaC0Ai4QUGBhwYEawHGiWUd7Y05u/WXiNQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/strong-weak-map": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-8.19.4.tgz", - "integrity": "sha512-MUfFaYD8YqVe32SQaYLI24/bNzaoyhdBIFY5pVrvMo1ZCvMl8AlfI2OcBXvcGb5aS5z7sCNCJm11UuoYbLI1zw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@cspell/url": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-8.19.4.tgz", - "integrity": "sha512-Pa474iBxS+lxsAL4XkETPGIq3EgMLCEb9agj3hAd2VGMTCApaiUvamR4b+uGXIPybN70piFxvzrfoxsG2uIP6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.50.2", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz", - "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6", - "@typescript-eslint/types": "^8.11.0", - "comment-parser": "1.4.1", - "esquery": "^1.6.0", - "jsdoc-type-pratt-parser": "~4.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": { - "version": "8.46.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", - "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", - "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", - "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", - "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", - "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", - "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", - "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", - "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", - "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", - "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", - "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", - "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", - "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", - "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", - "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", - "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", - "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", - "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", - "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", - "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", - "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", - "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", - "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", - "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", - "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", - "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", - "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hutson/parse-repository-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-5.0.0.tgz", - "integrity": "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@ionic/prettier-config": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@ionic/prettier-config/-/prettier-config-4.0.0.tgz", - "integrity": "sha512-0DqL6CggVdgeJAWOLPUT73rF1VD5p0tVlCpC5GXz5vTIUBxNwsJ5085Q7wXjKiE5Odx3aOHGTcuRWCawFsLFag==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^2.4.0 || ^3.0.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "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" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/core/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@jest/core/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.12.tgz", - "integrity": "sha512-mP9iLFZwH+FapKJLeA7/fLqOlSUwYpMwjR1P5J23qd4e7qGJwecJccJqHYrjw33jmIZYV4dtiTHPD/J+1e7cEw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.3", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.3", - "tar-fs": "^3.1.1", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.2.tgz", - "integrity": "sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "fdir": "^6.2.0", - "is-reference": "1.2.1", - "magic-string": "^0.30.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.0.tgz", - "integrity": "sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz", - "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz", - "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz", - "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz", - "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz", - "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz", - "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz", - "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz", - "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz", - "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz", - "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz", - "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz", - "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz", - "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz", - "integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz", - "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz", - "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz", - "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz", - "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz", - "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz", - "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@samverschueren/stream-to-observable": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz", - "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-observable": "^0.3.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependenciesMeta": { - "rxjs": { - "optional": true - }, - "zen-observable": { - "optional": true - } - } - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/eslint": { - "version": "8.56.12", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", - "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/exit": { - "version": "0.1.33", - "resolved": "https://registry.npmjs.org/@types/exit/-/exit-0.1.33.tgz", - "integrity": "sha512-1/NNW0tyaodminOWDq8snoPHGvf4f9srYKVwCpOukpTesAbJmYSzxa9l+10fHl1xTFOF+l9JXmyRReS+qhSN/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/fs-extra": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", - "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "27.5.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", - "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-matcher-utils": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsonfile": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", - "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/listr": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/@types/listr/-/listr-0.14.9.tgz", - "integrity": "sha512-Ncsy/jtO/HZYrupLGcnp1BOswZVsNvggjIjnf2EZ1xECfU4hxcQ3FWvFEyR+/DXssz0HDm74Op/tEsyrB3eV5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "rxjs": "^6.5.1" - } - }, - "node_modules/@types/node": { - "version": "24.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", - "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.14.0" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/pixelmatch": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.6.tgz", - "integrity": "sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/pngjs": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz", - "integrity": "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prompts": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", - "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "kleur": "^3.0.3" - } - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "16.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", - "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yarnpkg__lockfile": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.9.tgz", - "integrity": "sha512-GD4Fk15UoP5NLCNor51YdfL9MSdldKCqOC9EssrRw3HVfar9wUZ5y8Lfnp+qVD6hIinLr8ygklDYnmlnlQo12Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "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" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "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==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-timsort": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "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" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@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-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bare-events": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.0.tgz", - "integrity": "sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.4.11.tgz", - "integrity": "sha512-Bejmm9zRMvMTRoHS+2adgmXw1ANZnCNx+B5dgZpGwlP1E3x6Yuxea8RToddHUbWtVV0iUMWqsgZr8+jcgUI2SA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.0.tgz", - "integrity": "sha512-c+RCqMSZbkz97Mw1LWR0gcOqwK82oyYKfLoHJ8k13ybi1+I80ffdDzUy0TdAburdrR/kI0/VuN8YgEnJqX+Nyw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/basic-ftp": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.2.tgz", - "integrity": "sha512-1tDrzKsdCg70WGvbFss/ulVAxupNauGnOlgpyjKzeQxzyllBLS0CGLV7tjIXTK3ZQA9/FBEm9qyFFN1bciA6pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001768", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", - "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk-template": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.2.tgz", - "integrity": "sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/chromium-bidi": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-9.1.0.tgz", - "integrity": "sha512-rlUzQ4WzIAWdIbY/viPShhZU2n21CxDUgazXVbw4Hu1MwaeUSEksSeM6DqPgpRjCLXRk702AVRxJxoOz0dw4OA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/clap/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clap/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/clear-module": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", - "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^2.0.0", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/comment-json": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz", - "integrity": "sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/comment-parser": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", - "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/conventional-changelog": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-6.0.0.tgz", - "integrity": "sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w==", - "dev": true, - "license": "MIT", - "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-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" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-5.0.0.tgz", - "integrity": "sha512-WfzCaAvSCFPkznnLgLnfacRAzjgqjLUjvf3MftfsJzQdDICqkOOpcMtdJF3wTerxSpv2IAAjX8doM3Vozqle3g==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-5.0.0.tgz", - "integrity": "sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog": "^6.0.0", - "meow": "^13.0.0", - "tempfile": "^5.0.0" - }, - "bin": { - "conventional-changelog": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-5.0.0.tgz", - "integrity": "sha512-8gsBDI5Y3vrKUCxN6Ue8xr6occZ5nsDEc4C7jO/EovFGozx8uttCAyfhRrvoUAWi2WMm3OmYs+0mPJU7kQdYWQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz", - "integrity": "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-core": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-8.0.0.tgz", - "integrity": "sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hutson/parse-repository-url": "^5.0.0", - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-parser": "^6.0.0", - "git-raw-commits": "^5.0.0", - "git-semver-tags": "^8.0.0", - "hosted-git-info": "^7.0.0", - "normalize-package-data": "^6.0.0", - "read-package-up": "^11.0.0", - "read-pkg": "^9.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-ember": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-5.0.0.tgz", - "integrity": "sha512-RPflVfm5s4cSO33GH/Ey26oxhiC67akcxSKL8CLRT3kQX2W3dbE19sSOM56iFqUJYEwv9mD9r6k79weWe1urfg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-6.0.0.tgz", - "integrity": "sha512-eiUyULWjzq+ybPjXwU6NNRflApDWlPEQEHvI8UAItYW/h22RKkMnOAtfCZxMmrcMO1OKUWtcf2MxKYMWe9zJuw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-express": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-5.0.0.tgz", - "integrity": "sha512-D8Q6WctPkQpvr2HNCCmwU5GkX22BVHM0r4EW8vN0230TSyS/d6VQJDAxGb84lbg0dFjpO22MwmsikKL++Oo/oQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-6.0.0.tgz", - "integrity": "sha512-2kxmVakyehgyrho2ZHBi90v4AHswkGzHuTaoH40bmeNqUt20yEkDOSpw8HlPBfvEQBwGtbE+5HpRwzj6ac2UfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-5.0.0.tgz", - "integrity": "sha512-gGNphSb/opc76n2eWaO6ma4/Wqu3tpa2w7i9WYqI6Cs2fncDSI2/ihOfMvXveeTTeld0oFvwMVNV+IYQIk3F3g==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz", - "integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", - "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-commits-filter": "^5.0.0", - "handlebars": "^4.7.7", - "meow": "^13.0.0", - "semver": "^7.5.2" - }, - "bin": { - "conventional-changelog-writer": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-commits-filter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-commits-parser": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.0.tgz", - "integrity": "sha512-uLnoLeIW4XaoFtH37qEcg/SXMJmKF4vi7V0H2rnPueg+VEtFGA/asSCNTcq4M/GQ6QmlzchAEtOoDTtKqWeHag==", - "dev": true, - "license": "MIT", - "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig/node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cspell": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-8.19.4.tgz", - "integrity": "sha512-toaLrLj3usWY0Bvdi661zMmpKW2DVLAG3tcwkAv4JBTisdIRn15kN/qZDrhSieUEhVgJgZJDH4UKRiq29mIFxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-json-reporter": "8.19.4", - "@cspell/cspell-pipe": "8.19.4", - "@cspell/cspell-types": "8.19.4", - "@cspell/dynamic-import": "8.19.4", - "@cspell/url": "8.19.4", - "chalk": "^5.4.1", - "chalk-template": "^1.1.0", - "commander": "^13.1.0", - "cspell-dictionary": "8.19.4", - "cspell-gitignore": "8.19.4", - "cspell-glob": "8.19.4", - "cspell-io": "8.19.4", - "cspell-lib": "8.19.4", - "fast-json-stable-stringify": "^2.1.0", - "file-entry-cache": "^9.1.0", - "semver": "^7.7.1", - "tinyglobby": "^0.2.13" - }, - "bin": { - "cspell": "bin.mjs", - "cspell-esm": "bin.mjs" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" - } - }, - "node_modules/cspell-config-lib": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-8.19.4.tgz", - "integrity": "sha512-LtFNZEWVrnpjiTNgEDsVN05UqhhJ1iA0HnTv4jsascPehlaUYVoyucgNbFeRs6UMaClJnqR0qT9lnPX+KO1OLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-types": "8.19.4", - "comment-json": "^4.2.5", - "yaml": "^2.7.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-dictionary": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-8.19.4.tgz", - "integrity": "sha512-lr8uIm7Wub8ToRXO9f6f7in429P1Egm3I+Ps3ZGfWpwLTCUBnHvJdNF/kQqF7PL0Lw6acXcjVWFYT7l2Wdst2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-pipe": "8.19.4", - "@cspell/cspell-types": "8.19.4", - "cspell-trie-lib": "8.19.4", - "fast-equals": "^5.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-gitignore": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-8.19.4.tgz", - "integrity": "sha512-KrViypPilNUHWZkMV0SM8P9EQVIyH8HvUqFscI7+cyzWnlglvzqDdV4N5f+Ax5mK+IqR6rTEX8JZbCwIWWV7og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/url": "8.19.4", - "cspell-glob": "8.19.4", - "cspell-io": "8.19.4" - }, - "bin": { - "cspell-gitignore": "bin.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-glob": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-8.19.4.tgz", - "integrity": "sha512-042uDU+RjAz882w+DXKuYxI2rrgVPfRQDYvIQvUrY1hexH4sHbne78+OMlFjjzOCEAgyjnm1ktWUCCmh08pQUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/url": "8.19.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-grammar": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-8.19.4.tgz", - "integrity": "sha512-lzWgZYTu/L7DNOHjxuKf8H7DCXvraHMKxtFObf8bAzgT+aBmey5fW2LviXUkZ2Lb2R0qQY+TJ5VIGoEjNf55ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-pipe": "8.19.4", - "@cspell/cspell-types": "8.19.4" - }, - "bin": { - "cspell-grammar": "bin.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-io": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-8.19.4.tgz", - "integrity": "sha512-W48egJqZ2saEhPWf5ftyighvm4mztxEOi45ILsKgFikXcWFs0H0/hLwqVFeDurgELSzprr12b6dXsr67dV8amg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-service-bus": "8.19.4", - "@cspell/url": "8.19.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-lib": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-8.19.4.tgz", - "integrity": "sha512-NwfdCCYtIBNQuZcoMlMmL3HSv2olXNErMi/aOTI9BBAjvCHjhgX5hbHySMZ0NFNynnN+Mlbu5kooJ5asZeB3KA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-bundled-dicts": "8.19.4", - "@cspell/cspell-pipe": "8.19.4", - "@cspell/cspell-resolver": "8.19.4", - "@cspell/cspell-types": "8.19.4", - "@cspell/dynamic-import": "8.19.4", - "@cspell/filetypes": "8.19.4", - "@cspell/strong-weak-map": "8.19.4", - "@cspell/url": "8.19.4", - "clear-module": "^4.1.2", - "comment-json": "^4.2.5", - "cspell-config-lib": "8.19.4", - "cspell-dictionary": "8.19.4", - "cspell-glob": "8.19.4", - "cspell-grammar": "8.19.4", - "cspell-io": "8.19.4", - "cspell-trie-lib": "8.19.4", - "env-paths": "^3.0.0", - "fast-equals": "^5.2.2", - "gensequence": "^7.0.0", - "import-fresh": "^3.3.1", - "resolve-from": "^5.0.0", - "vscode-languageserver-textdocument": "^1.0.12", - "vscode-uri": "^3.1.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cspell-trie-lib": { - "version": "8.19.4", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-8.19.4.tgz", - "integrity": "sha512-yIPlmGSP3tT3j8Nmu+7CNpkPh/gBO2ovdnqNmZV+LNtQmVxqFd2fH7XvR1TKjQyctSH1ip0P5uIdJmzY1uhaYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-pipe": "8.19.4", - "@cspell/cspell-types": "8.19.4", - "gensequence": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/css-what": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", - "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-is": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-browser/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/default-browser/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/default-browser/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.1508733", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1508733.tgz", - "integrity": "sha512-QJ1R5gtck6nDcdM+nlsaJXcelPEI7ZxSMw1ujHpO1c4+9l+Nue5qlebi9xO1Z2MGr92bFOQTW7/rrheh5hHxDg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "license": "MIT", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dts-bundle-generator": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz", - "integrity": "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "typescript": ">=5.0.2", - "yargs": "^17.6.0" - }, - "bin": { - "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", - "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.11", - "@esbuild/android-arm": "0.25.11", - "@esbuild/android-arm64": "0.25.11", - "@esbuild/android-x64": "0.25.11", - "@esbuild/darwin-arm64": "0.25.11", - "@esbuild/darwin-x64": "0.25.11", - "@esbuild/freebsd-arm64": "0.25.11", - "@esbuild/freebsd-x64": "0.25.11", - "@esbuild/linux-arm": "0.25.11", - "@esbuild/linux-arm64": "0.25.11", - "@esbuild/linux-ia32": "0.25.11", - "@esbuild/linux-loong64": "0.25.11", - "@esbuild/linux-mips64el": "0.25.11", - "@esbuild/linux-ppc64": "0.25.11", - "@esbuild/linux-riscv64": "0.25.11", - "@esbuild/linux-s390x": "0.25.11", - "@esbuild/linux-x64": "0.25.11", - "@esbuild/netbsd-arm64": "0.25.11", - "@esbuild/netbsd-x64": "0.25.11", - "@esbuild/openbsd-arm64": "0.25.11", - "@esbuild/openbsd-x64": "0.25.11", - "@esbuild/openharmony-arm64": "0.25.11", - "@esbuild/sunos-x64": "0.25.11", - "@esbuild/win32-arm64": "0.25.11", - "@esbuild/win32-ia32": "0.25.11", - "@esbuild/win32-x64": "0.25.11" - } - }, - "node_modules/esbuild-plugin-replace": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esbuild-plugin-replace/-/esbuild-plugin-replace-1.4.0.tgz", - "integrity": "sha512-lP3ZAyzyRa5JXoOd59lJbRKNObtK8pJ/RO7o6vdjwLi71GfbL32NR22ZuS7/cLZkr10/L1lutoLma8E4DLngYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.25.7" - } - }, - "node_modules/esbuild-plugin-replace/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", - "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-jest": { - "version": "28.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.14.0.tgz", - "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "engines": { - "node": "^16.10.0 || ^18.12.0 || >=20.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsdoc": { - "version": "50.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.8.0.tgz", - "integrity": "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.50.2", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.4.1", - "escape-string-regexp": "^4.0.0", - "espree": "^10.3.0", - "esquery": "^1.6.0", - "parse-imports-exports": "^0.2.4", - "semver": "^7.7.2", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-simple-import-sort": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", - "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0" - } - }, - "node_modules/eslint-plugin-wdio": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-8.37.0.tgz", - "integrity": "sha512-X217zXxSqj1IPWu3bxN7D/xEUmNk7Jg5lBf2JwYH3mCogaqL2tnHZnwt0EQ5D9oEejfEl2+4zqHSzhXq1X7F2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/eslint/node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/eslint/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/execa": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.0.tgz", - "integrity": "sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.3", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^7.0.0", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^5.2.0", - "pretty-ms": "^9.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fast-deep-equal": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-equals": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", - "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "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.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", - "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up-simple": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", - "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.3.1", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensequence": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-7.0.0.tgz", - "integrity": "sha512-47Frx13aZh01afHJTB3zTtKIlFI6vWY+MYCN9Qpew6i52rfKjnhCF/l1YlC8UmEMvvntZZ6z4PiCcmyuedR2aQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/gensync": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", - "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/git-raw-commits": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.0.tgz", - "integrity": "sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@conventional-changelog/git-client": "^1.0.0", - "meow": "^13.0.0" - }, - "bin": { - "git-raw-commits": "src/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/git-semver-tags": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-8.0.0.tgz", - "integrity": "sha512-N7YRIklvPH3wYWAR2vysaqGLPRcpwQ0GKdlqTiVN5w1UmCdaeY3K8s6DMKRCh54DDdzyt/OAB6C8jgVtb7Y2Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@conventional-changelog/git-client": "^1.0.0", - "meow": "^13.0.0" - }, - "bin": { - "git-semver-tags": "src/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-7.0.0.tgz", - "integrity": "sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/parent-module": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/index-to-position": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", - "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "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.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", - "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "symbol-observable": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-path-inside": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-wsl/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "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" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "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==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^27.5.1", - "import-local": "^3.0.2", - "jest-cli": "^27.5.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/jest-changed-files/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/jest-cli/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/jest-runtime/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runtime/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jest-runtime/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runtime/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/jest-runtime/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.5.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.5.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "leven": "^3.1.0", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.5.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jquery": { - "version": "4.0.0-pre", - "resolved": "https://github.com/jquery/jquery/tarball/c98597eaf5e144ee5e549cb41984687cd1033068", - "integrity": "sha512-U1l+pjigfVQzAfwDVYPWAoZvkWiJwlo1sYyGYS2/sf0LDUG2up+pBDgj2lug9S8jd7dvMBtqZpJ8GogXeYRYyA==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", - "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsdom/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/listr-update-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", - "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "listr": "^0.14.2" - } - }, - "node_modules/listr-update-renderer/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/listr-update-renderer/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/listr-update-renderer/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/listr-update-renderer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/listr-update-renderer/node_modules/figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/listr-update-renderer/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/listr-update-renderer/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/listr-verbose-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", - "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/listr-verbose-renderer/node_modules/ansi-styles": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/listr-verbose-renderer/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/listr-verbose-renderer/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/listr-verbose-renderer/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/listr-verbose-renderer/node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/listr-verbose-renderer/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/listr-verbose-renderer/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/listr/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-symbols/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-symbols/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/log-symbols/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nwsapi": { - "version": "2.2.22", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", - "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open-in-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/open-in-editor/-/open-in-editor-2.2.0.tgz", - "integrity": "sha512-ZQJDm2lmIgR2GkuwzjrlkVmT2KpDVp0Nnnb3LtYLe3Xi3cQhDa1vnh4IIlrT35a46OLZ8nlKJNOsx2B85FOS+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "clap": "^1.1.3", - "os-homedir": "~1.0.2" - }, - "bin": { - "oe": "bin/oe" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "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.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", - "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parse-imports-exports": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", - "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-statements": "1.0.11" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-statements": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pkg-dir": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.1.tgz", - "integrity": "sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer": { - "version": "24.25.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.25.0.tgz", - "integrity": "sha512-P3rUaom2w/Ubrnz3v3kSbxGkN7SpbtQeGRPb7iO86Bv/dAz2WUmGQBHr37W/Rp1fbAocMvu0rHFbCIJvjiNhGw==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.12", - "chromium-bidi": "9.1.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1508733", - "puppeteer-core": "24.25.0", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core": { - "version": "24.25.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.25.0.tgz", - "integrity": "sha512-8Xs6q3Ut+C8y7sAaqjIhzv1QykGWG4gc2mEZ2mYE7siZFuRp4xQVehOf8uQKSQAkeL7jXUs3mknEeiqnRqUKvQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.12", - "chromium-bidi": "9.1.0", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1508733", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.3.7", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, - "node_modules/read-package-up": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up-simple": "^1.0.0", - "read-pkg": "^9.0.0", - "type-fest": "^4.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-package-up/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz", - "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.0", - "@rollup/rollup-android-arm64": "4.44.0", - "@rollup/rollup-darwin-arm64": "4.44.0", - "@rollup/rollup-darwin-x64": "4.44.0", - "@rollup/rollup-freebsd-arm64": "4.44.0", - "@rollup/rollup-freebsd-x64": "4.44.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", - "@rollup/rollup-linux-arm-musleabihf": "4.44.0", - "@rollup/rollup-linux-arm64-gnu": "4.44.0", - "@rollup/rollup-linux-arm64-musl": "4.44.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", - "@rollup/rollup-linux-riscv64-gnu": "4.44.0", - "@rollup/rollup-linux-riscv64-musl": "4.44.0", - "@rollup/rollup-linux-s390x-gnu": "4.44.0", - "@rollup/rollup-linux-x64-gnu": "4.44.0", - "@rollup/rollup-linux-x64-musl": "4.44.0", - "@rollup/rollup-win32-arm64-msvc": "4.44.0", - "@rollup/rollup-win32-ia32-msvc": "4.44.0", - "@rollup/rollup-win32-x64-msvc": "4.44.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/run-applescript/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/run-applescript/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/run-applescript/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/tempfile": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-5.0.0.tgz", - "integrity": "sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "temp-dir": "^3.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", - "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude": { - "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==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/throat": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", - "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "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==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/typedarray-to-buffer": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "license": "MIT", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webdriver-bidi-protocol": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.7.tgz", - "integrity": "sha512-wIx5Gu/LLTeexxilpk8WxU2cpGAKlfbWRO5h+my6EMD1k5PYqM1qQO1MHUFf4f3KRnhBvpbZU7VkizAgeSEf7g==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "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==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index 4e49fba79ae..5c8207a32ce 100644 --- a/package.json +++ b/package.json @@ -1,264 +1,60 @@ { - "name": "@stencil/core", - "version": "4.43.4", - "license": "MIT", - "main": "./internal/stencil-core/index.cjs", - "module": "./internal/stencil-core/index.js", - "types": "./internal/stencil-core/index.d.ts", - "bin": { - "stencil": "bin/stencil" - }, - "files": [ - "!**/*.map", - "!**/*.stub.ts", - "!**/*.stub.tsx", - "bin/", - "cli/", - "compiler/", - "dev-server/", - "internal/", - "mock-doc/", - "screenshot/", - "sys/", - "testing/" - ], - "exports": { - ".": { - "types": "./internal/stencil-core/index.d.ts", - "import": "./internal/stencil-core/index.js", - "require": "./internal/stencil-core/index.cjs" - }, - "./jsx-runtime": { - "types": "./internal/stencil-core/jsx-runtime.d.ts", - "import": "./internal/stencil-core/jsx-runtime.js", - "require": "./internal/stencil-core/jsx-runtime.cjs" - }, - "./jsx-dev-runtime": { - "types": "./internal/stencil-core/jsx-dev-runtime.d.ts", - "import": "./internal/stencil-core/jsx-dev-runtime.js", - "require": "./internal/stencil-core/jsx-dev-runtime.cjs" - }, - "./cli": { - "import": "./cli/index.js", - "require": "./cli/index.cjs" - }, - "./internal": { - "import": "./internal/index.js", - "types": "./internal/index.d.ts" - }, - "./internal/client": { - "import": "./internal/client/index.js", - "require": "./internal/client/index.js" - }, - "./internal/testing": { - "import": "./internal/testing/index.js", - "require": "./internal/testing/index.js" - }, - "./internal/testing/jsx-runtime": { - "types": "./internal/testing/jsx-runtime.d.ts", - "import": "./internal/testing/jsx-runtime.js", - "require": "./internal/testing/jsx-runtime.js" - }, - "./internal/testing/jsx-dev-runtime": { - "types": "./internal/testing/jsx-dev-runtime.d.ts", - "import": "./internal/testing/jsx-dev-runtime.js", - "require": "./internal/testing/jsx-dev-runtime.js" - }, - "./internal/testing/*": { - "import": "./internal/testing/*" - }, - "./internal/app-data": { - "types": "./internal/app-data/index.d.ts", - "import": "./internal/app-data/index.js", - "require": "./internal/app-data/index.cjs" - }, - "./internal/app-globals": { - "import": "./internal/app-globals/index.js", - "require": "./internal/app-globals/index.js" - }, - "./mock-doc": { - "types": "./mock-doc/index.d.ts", - "import": "./mock-doc/index.js", - "require": "./mock-doc/index.cjs" - }, - "./compiler": { - "types": "./compiler/stencil.d.ts", - "import": "./compiler/stencil.js", - "require": "./compiler/stencil.js" - }, - "./compiler/*": { - "types": "./compiler/*", - "import": "./compiler/*", - "require": "./compiler/*" - }, - "./screenshot": { - "types": "./screenshot/index.d.ts", - "require": "./screenshot/index.js" - }, - "./sys/node": { - "types": "./sys/node/index.d.ts", - "import": "./sys/node/index.js", - "require": "./sys/node/index.js" - }, - "./sys/node/*": { - "import": "./sys/node/*", - "require": "./sys/node/*" - }, - "./testing": { - "types": "./testing/index.d.ts", - "import": "./testing/index.js", - "require": "./testing/index.js" - }, - "./testing/jest-preset": { - "require": "./testing/jest-preset.js" - }, - "./testing/*": { - "import": "./testing/*", - "require": "./testing/*" - } - }, - "scripts": { - "build": "npm run clean && npm run tsc.prod && npm run ts scripts/index.ts -- --prod --ci", - "build.watch": "npm run build -- --watch", - "build.updateSelectorEngine": "npm run ts scripts/updateSelectorEngine.ts", - "clean": "rimraf --max-retries=2 build/ cli/ compiler/ dev-server/ internal/ mock-doc/ sys/node/ sys/ testing/ && npm run clean:scripts && npm run clean.screenshots", - "clean.screenshots": "rimraf test/end-to-end/screenshot/builds test/end-to-end/screenshot/images", - "clean:scripts": "rimraf scripts/build", - "lint": "eslint bin/* scripts/*.ts scripts/**/*.ts src/*.ts src/**/*.ts src/**/*.tsx test/wdio/**/*.tsx", - "install.jest": "npx tsx ./src/testing/jest/install-dependencies.mts", - "prettier": "npm run prettier.base -- --write", - "prettier.base": "prettier --cache \"./({bin,scripts,src,test}/**/*.{ts,tsx,js,jsx})|bin/stencil|.github/(**/)?*.(yml|yaml)|*.js\"", - "prettier.dry-run": "npm run prettier.base -- --list-different", - "release.ci.prepare": "npm run ts scripts/index.ts -- --release --ci-prepare", - "release.ci": "NODE_OPTIONS=--max-old-space-size=4096 npm run ts scripts/index.ts -- --release --ci-publish", - "spellcheck": "npm run spellcheck.code && npm run spellcheck.markdown", - "spellcheck.code": "cspell --config cspell-code.json --no-progress \"src/**/*.ts\" \"src/**/*.tsx\" \"scripts/**/*.ts\"", - "spellcheck.markdown": "cspell --config cspell-markdown.json --no-progress \"*.md\" \"**/*.md\"", - "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --coverage", - "test.analysis": "cd test && npm ci && npm run analysis.build-and-analyze", - "test.bundle-size": "cd test/bundle-size && npm test", - "test.bundlers": "cd test && npm run bundlers", - "test.copytask": "cd test/copy-task && npm ci && npm run test", - "test.dist": "npm run ts scripts/index.ts -- --validate-build", - "test.end-to-end": "cd test/end-to-end && npm ci && npm test && npm run test.dist", - "test.jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", - "test.type-tests": "cd ./test/wdio && npm install && npm run build.test-sibling && npm run build.main && cd ../../ && tsc -p test/type-tests/tsconfig.json", - "test.wdio": "cd test/wdio && npm ci && npm run test", - "test.wdio.testOnly": "cd test/wdio && npm ci && npm run wdio", - "test.prod": "npm run test.dist && npm run test.end-to-end && npm run test.jest && npm run test.wdio && npm run test.testing && npm run test.analysis", - "test.testing": "node scripts/test/validate-testing.js", - "test.docs-build": "cd test && npm run build.docs-json && npm run build.docs-readme", - "test.watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch", - "test.watch-all": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watchAll --coverage", - "tsc.prod": "tsc", - "ts": "tsc --noEmit --project scripts/tsconfig.json && tsx" - }, - "devDependencies": { - "@ionic/prettier-config": "^4.0.0", - "@jridgewell/source-map": "^0.3.6", - "@rollup/plugin-commonjs": "28.0.2", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "16.0.0", - "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.1.4", - "@types/eslint": "^8.4.6", - "@types/exit": "^0.1.31", - "@types/fs-extra": "^11.0.0", - "@types/graceful-fs": "^4.1.5", - "@types/jest": "^27.0.3", - "@types/listr": "^0.14.4", - "@types/node": "^24.6.2", - "@types/pixelmatch": "^5.2.4", - "@types/pngjs": "^6.0.1", - "@types/prompts": "^2.0.9", - "@types/semver": "^7.3.12", - "@types/ws": "^8.5.4", - "@types/yarnpkg__lockfile": "^1.1.5", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "@yarnpkg/lockfile": "^1.1.0", - "ansi-colors": "4.1.3", - "autoprefixer": "10.4.19", - "conventional-changelog-cli": "^5.0.0", - "cspell": "^8.0.0", - "css-what": "^7.0.0", - "dts-bundle-generator": "~9.5.0", - "esbuild": "^0.25.0", - "esbuild-plugin-replace": "^1.4.0", - "eslint": "^8.23.1", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-jest": "^28.0.0", - "eslint-plugin-jsdoc": "^50.0.0", - "eslint-plugin-simple-import-sort": "^12.0.0", - "eslint-plugin-wdio": "^8.24.12", - "execa": "9.3.0", - "exit": "^0.1.2", - "fs-extra": "^11.0.0", - "glob": "10.5.0", - "graceful-fs": "~4.2.6", - "jest": "^27.4.5", - "jest-cli": "^27.4.5", - "jest-environment-node": "^27.4.4", - "jquery": "https://github.com/jquery/jquery/tarball/c98597eaf5e144ee5e549cb41984687cd1033068", - "listr": "^0.14.3", - "magic-string": "^0.30.0", - "merge-source-map": "^1.1.0", - "mime-db": "^1.46.0", - "minimatch": "9.0.9", - "node-fetch": "3.3.2", - "open": "^9.0.0", - "open-in-editor": "2.2.0", - "parse5": "7.2.1", - "pixelmatch": "5.3.0", - "postcss": "^8.2.8", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", - "prettier": "3.3.1", - "prompts": "2.4.2", - "puppeteer": "^24.1.0", - "rimraf": "^6.0.1", - "rollup": "4.44.0", - "semver": "^7.3.7", - "terser": "5.37.0", - "tsx": "^4.19.2", - "typescript": "~5.8.3", - "webpack": "^5.75.0", - "ws": "8.17.1" - }, - "optionalDependencies": { - "@rollup/rollup-darwin-arm64": "4.44.0", - "@rollup/rollup-darwin-x64": "4.44.0", - "@rollup/rollup-linux-arm64-gnu": "4.44.0", - "@rollup/rollup-linux-arm64-musl": "4.44.0", - "@rollup/rollup-linux-x64-gnu": "4.44.0", - "@rollup/rollup-linux-x64-musl": "4.44.0", - "@rollup/rollup-win32-arm64-msvc": "4.44.0", - "@rollup/rollup-win32-x64-msvc": "4.44.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.10.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/stenciljs/core.git" - }, - "author": "Ionic Team", - "homepage": "https://stenciljs.com/", + "name": "@stencil/monorepo", + "version": "5.0.0-alpha.5", + "private": true, "description": "A Compiler for Web Components and Progressive Web Apps", "keywords": [ - "web components", "components", - "stencil", - "ionic", - "webapp", "custom elements", + "ionic", + "progressive web app", "pwa", - "progressive web app" + "stencil", + "web components", + "webapp" ], - "prettier": "@ionic/prettier-config", + "homepage": "https://stenciljs.com/", + "license": "MIT", + "author": "StencilJs Contributors", + "repository": { + "type": "git", + "url": "git+https://github.com/stenciljs/core.git" + }, + "type": "module", + "scripts": { + "build": "pnpm --reporter=append-only --stream --filter './packages/*' run build", + "build:watch": "pnpm --parallel --stream --filter './packages/*' run build --watch", + "dev": "pnpm typecheck && pnpm build:watch", + "format": "oxfmt --write", + "format:check": "oxfmt --check", + "knip": "knip", + "spellcheck": "cspell -c cspell-code.json \"packages/**/*.ts\" && cspell -c cspell-markdown.json \"**/*.md\"", + "lint": "oxlint --fix packages/", + "lint:check": "oxlint packages/", + "test": "pnpm --stream --filter './packages/*' run test", + "test:watch": "pnpm --parallel --stream --filter './packages/*' run test --watch", + "typecheck": "pnpm --reporter=append-only --stream --filter './packages/*' run typecheck", + "changeset": "changeset", + "changeset:version": "changeset version", + "changeset:publish": "pnpm build && changeset publish", + "release:prerelease": "./scripts/release-prerelease.sh" + }, + "devDependencies": { + "@changesets/cli": "^2.29.0", + "@types/node": "^24", + "cspell": "^9.7.0", + "knip": "^6.1.0", + "oxfmt": "^0.42.0", + "oxlint": "^1.57.0" + }, "volta": { - "node": "22.2.0", - "npm": "10.8.1" + "node": "24.14.0", + "npm": "11.9.0" + }, + "packageManager": "pnpm@10.31.0", + "pnpm": { + "overrides": { + "@stencil/core": "workspace:*" + } } } diff --git a/packages/cli/bin/stencil.mjs b/packages/cli/bin/stencil.mjs new file mode 100755 index 00000000000..23b3d419356 --- /dev/null +++ b/packages/cli/bin/stencil.mjs @@ -0,0 +1,14 @@ +#!/usr/bin/env node + +// Set NODE_ENV to production by default (matches legacy webpack bundling behavior) +// This suppresses development-only warnings from dependencies like PostCSS +process.env.NODE_ENV ??= 'production'; + +import { run } from '@stencil/cli'; +import { createNodeLogger, createNodeSys } from '@stencil/core/sys/node'; + +run({ + args: process.argv.slice(2), + logger: createNodeLogger(), + sys: createNodeSys(), +}); diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 00000000000..acab0c861bf --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,61 @@ +{ + "name": "@stencil/cli", + "version": "5.0.0-alpha.5", + "description": "CLI for Stencil - Web component compiler", + "keywords": [ + "components", + "custom elements", + "ionic", + "progressive web app", + "pwa", + "stencil", + "web components", + "webapp" + ], + "homepage": "https://stenciljs.com/", + "license": "MIT", + "author": "StencilJs Contributors", + "repository": { + "type": "git", + "url": "git+https://github.com/stenciljs/core.git" + }, + "bin": { + "stencil": "./bin/stencil.mjs" + }, + "files": [ + "bin/", + "dist/" + ], + "type": "module", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + }, + "./cli": "./bin/stencil.mjs" + }, + "scripts": { + "build": "tsdown", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@stencil/dev-server": "workspace:*", + "prompts": "^2.4.2" + }, + "devDependencies": { + "@stencil/core": "workspace:*", + "@types/prompts": "^2.4.9", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "@stencil/core": "^5.0.0-0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/src/cli/test/ionic-config.spec.ts b/packages/cli/src/_test_/ionic-config.spec.ts similarity index 97% rename from src/cli/test/ionic-config.spec.ts rename to packages/cli/src/_test_/ionic-config.spec.ts index 4cd688a1c69..43139231253 100644 --- a/src/cli/test/ionic-config.spec.ts +++ b/packages/cli/src/_test_/ionic-config.spec.ts @@ -1,6 +1,7 @@ +import { createSystem } from '@stencil/core/compiler'; import { mockCompilerSystem } from '@stencil/core/testing'; +import { describe, it, beforeEach, expect } from 'vitest'; -import { createSystem } from '../../compiler/sys/stencil-sys'; import { defaultConfig, readConfig, updateConfig, writeConfig } from '../ionic-config'; import { UUID_REGEX } from '../telemetry/helpers'; diff --git a/packages/cli/src/_test_/merge-flags.spec.ts b/packages/cli/src/_test_/merge-flags.spec.ts new file mode 100644 index 00000000000..e0baecfd64b --- /dev/null +++ b/packages/cli/src/_test_/merge-flags.spec.ts @@ -0,0 +1,313 @@ +import { describe, it, expect } from 'vitest'; +import type { UnvalidatedConfig } from '@stencil/core/compiler'; + +import { createConfigFlags, type ConfigFlags } from '../config-flags'; +import { mergeFlags } from '../merge-flags'; + +describe('mergeFlags', () => { + const createFlags = (overrides: Partial = {}): ConfigFlags => { + return createConfigFlags(overrides); + }; + + describe('devMode (--dev)', () => { + it('sets devMode to true when --dev is true', () => { + const config: UnvalidatedConfig = {}; + const flags = createFlags({ dev: true }); + + const result = mergeFlags(config, flags); + + expect(result.devMode).toBe(true); + }); + + it('does not set devMode when --dev is absent (production is the default)', () => { + const config: UnvalidatedConfig = {}; + const flags = createFlags({}); + + const result = mergeFlags(config, flags); + + expect(result.devMode).toBeUndefined(); + }); + }); + + describe('logLevel (--verbose / --debug / --logLevel)', () => { + it('sets logLevel to debug when --debug is true', () => { + const config: Config = {}; + const flags = createFlags({ debug: true }); + + const result = mergeFlags(config, flags); + + expect(result.logLevel).toBe('debug'); + }); + + it('sets logLevel to debug when --verbose is true', () => { + const config: Config = {}; + const flags = createFlags({ verbose: true }); + + const result = mergeFlags(config, flags); + + expect(result.logLevel).toBe('debug'); + }); + + it('sets logLevel from --logLevel flag', () => { + const config: Config = {}; + const flags = createFlags({ logLevel: 'warn' }); + + const result = mergeFlags(config, flags); + + expect(result.logLevel).toBe('warn'); + }); + + it('--debug takes precedence over --logLevel', () => { + const config: Config = {}; + const flags = createFlags({ debug: true, logLevel: 'error' }); + + const result = mergeFlags(config, flags); + + expect(result.logLevel).toBe('debug'); + }); + + it('preserves config logLevel when no flags are set', () => { + const config: Config = { logLevel: 'info' }; + const flags = createFlags({}); + + const result = mergeFlags(config, flags); + + expect(result.logLevel).toBe('info'); + }); + }); + + describe('watch (--watch)', () => { + it('sets watch to true when --watch is true', () => { + const config: Config = {}; + const flags = createFlags({ watch: true }); + + const result = mergeFlags(config, flags); + + expect(result.watch).toBe(true); + }); + + it('sets watch to false when --watch is false', () => { + const config: Config = { watch: true }; + const flags = createFlags({ watch: false }); + + const result = mergeFlags(config, flags); + + expect(result.watch).toBe(false); + }); + + it('preserves config watch when flag is not set', () => { + const config: Config = { watch: true }; + const flags = createFlags({}); + + const result = mergeFlags(config, flags); + + expect(result.watch).toBe(true); + }); + }); + + describe('_docsFlag (--docs)', () => { + it('sets _docsFlag from --docs flag', () => { + const config: Config = {}; + const flags = createFlags({ docs: true }); + + const result = mergeFlags(config, flags); + + expect(result._docsFlag).toBe(true); + }); + }); + + describe('profile (--profile)', () => { + it('sets profile from --profile flag', () => { + const config: Config = {}; + const flags = createFlags({ profile: true }); + + const result = mergeFlags(config, flags); + + expect(result.profile).toBe(true); + }); + }); + + describe('writeLog (--log)', () => { + it('sets writeLog from --log flag', () => { + const config: Config = {}; + const flags = createFlags({ log: true }); + + const result = mergeFlags(config, flags); + + expect(result.writeLog).toBe(true); + }); + }); + + describe('enableCache (--cache)', () => { + it('sets enableCache from --cache flag', () => { + const config: Config = {}; + const flags = createFlags({ cache: true }); + + const result = mergeFlags(config, flags); + + expect(result.enableCache).toBe(true); + }); + + it('can disable cache with --cache false', () => { + const config: Config = { enableCache: true }; + const flags = createFlags({ cache: false }); + + const result = mergeFlags(config, flags); + + expect(result.enableCache).toBe(false); + }); + }); + + describe('ci (--ci)', () => { + it('sets ci from --ci flag', () => { + const config: Config = {}; + const flags = createFlags({ ci: true }); + + const result = mergeFlags(config, flags); + + expect(result.ci).toBe(true); + }); + }); + + describe('ssr (--ssr)', () => { + it('sets ssr from --ssr flag', () => { + const config: Config = {}; + const flags = createFlags({ ssr: true }); + + const result = mergeFlags(config, flags); + + expect(result.ssr).toBe(true); + }); + }); + + describe('prerender (--prerender)', () => { + it('sets prerender from --prerender flag', () => { + const config: Config = {}; + const flags = createFlags({ prerender: true }); + + const result = mergeFlags(config, flags); + + expect(result.prerender).toBe(true); + }); + }); + + describe('docsJsonPath (--docsJson)', () => { + it('sets docsJsonPath from --docsJson flag', () => { + const config: Config = {}; + const flags = createFlags({ docsJson: './docs.json' }); + + const result = mergeFlags(config, flags); + + expect(result.docsJsonPath).toBe('./docs.json'); + }); + }); + + describe('statsJsonPath (--stats)', () => { + it('sets statsJsonPath from --stats flag (string)', () => { + const config: Config = {}; + const flags = createFlags({ stats: './stats.json' }); + + const result = mergeFlags(config, flags); + + expect(result.statsJsonPath).toBe('./stats.json'); + }); + + it('sets statsJsonPath from --stats flag (boolean)', () => { + const config: Config = {}; + const flags = createFlags({ stats: true }); + + const result = mergeFlags(config, flags); + + expect(result.statsJsonPath).toBe(true); + }); + }); + + describe('generateServiceWorker (--serviceWorker)', () => { + it('sets generateServiceWorker from --serviceWorker flag', () => { + const config: Config = {}; + const flags = createFlags({ serviceWorker: true }); + + const result = mergeFlags(config, flags); + + expect(result.generateServiceWorker).toBe(true); + }); + }); + + describe('maxConcurrentWorkers (--maxWorkers)', () => { + it('sets maxConcurrentWorkers from --maxWorkers flag', () => { + const config: Config = {}; + const flags = createFlags({ maxWorkers: 4 }); + + const result = mergeFlags(config, flags); + + expect(result.maxConcurrentWorkers).toBe(4); + }); + + it('does not set maxConcurrentWorkers when --maxWorkers is a string', () => { + const config: Config = {}; + // maxWorkers can be a string like "50%" but only number values are merged + const flags = createFlags({ maxWorkers: '50%' as unknown as number }); + + const result = mergeFlags(config, flags); + + expect(result.maxConcurrentWorkers).toBeUndefined(); + }); + }); + + describe('dev server options', () => { + it('sets devServerAddress from --address flag', () => { + const config: Config = {}; + const flags = createFlags({ address: '0.0.0.0' }); + + const result = mergeFlags(config, flags); + + expect(result.devServerAddress).toBe('0.0.0.0'); + }); + + it('sets devServerPort from --port flag', () => { + const config: Config = {}; + const flags = createFlags({ port: 4444 }); + + const result = mergeFlags(config, flags); + + expect(result.devServerPort).toBe(4444); + }); + + it('sets devServerOpen from --open flag', () => { + const config: Config = {}; + const flags = createFlags({ open: true }); + + const result = mergeFlags(config, flags); + + expect(result.devServerOpen).toBe(true); + }); + }); + + describe('config preservation', () => { + it('preserves existing config values not affected by flags', () => { + const config: Config = { + namespace: 'my-app', + srcDir: './src', + taskQueue: 'async', + }; + const flags = createFlags({ dev: true }); + + const result = mergeFlags(config, flags); + + expect(result.namespace).toBe('my-app'); + expect(result.srcDir).toBe('./src'); + expect(result.taskQueue).toBe('async'); + expect(result.devMode).toBe(true); + }); + + it('does not mutate the original config', () => { + const config: Config = { devMode: false }; + const flags = createFlags({ dev: true }); + + const result = mergeFlags(config, flags); + + expect(config.devMode).toBe(false); + expect(result.devMode).toBe(true); + }); + }); +}); diff --git a/packages/cli/src/_test_/parse-flags.spec.ts b/packages/cli/src/_test_/parse-flags.spec.ts new file mode 100644 index 00000000000..883316cfaf8 --- /dev/null +++ b/packages/cli/src/_test_/parse-flags.spec.ts @@ -0,0 +1,465 @@ +import { LogLevel } from '@stencil/core/compiler'; +import { toDashCase } from '@stencil/core/compiler/utils'; +import { describe, it, expect } from 'vitest'; + +import { + BOOLEAN_CLI_FLAGS, + BOOLEAN_STRING_CLI_FLAGS, + BooleanStringCLIFlag, + NUMBER_CLI_FLAGS, + STRING_ARRAY_CLI_FLAGS, + STRING_CLI_FLAGS, + StringArrayCLIFlag, +} from '../config-flags'; +import { Empty, parseEqualsArg, parseFlags } from '../parse-flags'; + +describe('parseFlags', () => { + it('should get known and unknown args', () => { + const args = [ + 'serve', + '--address', + '127.0.0.1', + '--potatoArgument', + '--flimflammery', + 'test.spec.ts', + ]; + + const flags = parseFlags(args); + expect(flags.task).toBe('serve'); + expect(flags.args[0]).toBe('--address'); + expect(flags.args[1]).toBe('127.0.0.1'); + expect(flags.args[2]).toBe('--potatoArgument'); + expect(flags.args[3]).toBe('--flimflammery'); + expect(flags.args[4]).toBe('test.spec.ts'); + expect(flags.knownArgs).toEqual(['--address', '127.0.0.1']); + expect(flags.unknownArgs[0]).toBe('--potatoArgument'); + expect(flags.unknownArgs[1]).toBe('--flimflammery'); + expect(flags.unknownArgs[2]).toBe('test.spec.ts'); + }); + + it('should parse cli for dev server', () => { + // user command line args + // $ npm run serve --port 4444 + + // args.slice(2) + // [ 'serve', '--address', '127.0.0.1', '--port', '4444' ] + + const args = ['serve', '--address', '127.0.0.1', '--port', '4444']; + + const flags = parseFlags(args); + expect(flags.task).toBe('serve'); + expect(flags.address).toBe('127.0.0.1'); + expect(flags.port).toBe(4444); + expect(flags.knownArgs).toEqual(['--address', '127.0.0.1', '--port', '4444']); + }); + + it('should parse task', () => { + const flags = parseFlags(['build']); + expect(flags.task).toBe('build'); + }); + + it('should parse no task', () => { + const flags = parseFlags(['--flag']); + expect(flags.task).toBe(null); + }); + + /** + * these comprehensive tests of all the supported boolean args serve as + * regression tests against duplicating any of the arguments in the arrays. + * Because of the way that the arg parsing algorithm works having a dupe + * will result in a value like `[true, true]` being set on ConfigFlags, which + * will cause these tests to start failing. + */ + describe.each(BOOLEAN_CLI_FLAGS)('should parse boolean flag %s', (cliArg) => { + it('should parse arg', () => { + const flags = parseFlags([`--${cliArg}`]); + expect(flags.knownArgs).toEqual([`--${cliArg}`]); + expect(flags[cliArg]).toBe(true); + }); + + it(`should parse --no${cliArg}`, () => { + const negativeFlag = '--no' + cliArg.charAt(0).toUpperCase() + cliArg.slice(1); + const flags = parseFlags([negativeFlag]); + expect(flags.knownArgs).toEqual([negativeFlag]); + expect(flags[cliArg]).toBe(false); + }); + + it(`should override --${cliArg} with --no${cliArg}`, () => { + const negativeFlag = '--no' + cliArg.charAt(0).toUpperCase() + cliArg.slice(1); + const flags = parseFlags([`--${cliArg}`, negativeFlag]); + expect(flags.knownArgs).toEqual([`--${cliArg}`, negativeFlag]); + expect(flags[cliArg]).toBe(false); + }); + + it('should not set value if not present', () => { + const flags = parseFlags([]); + expect(flags.knownArgs).toEqual([]); + expect(flags[cliArg]).toBe(undefined); + }); + + it.each([true, false])(`should set the value with --${cliArg}=%p`, (value) => { + const flags = parseFlags([`--${cliArg}=${value}`]); + expect(flags.knownArgs).toEqual([`--${cliArg}`, String(value)]); + expect(flags[cliArg]).toBe(value); + }); + }); + + describe.each(STRING_CLI_FLAGS)('should parse string flag %s', (cliArg) => { + it(`should parse "--${cliArg} value"`, () => { + const flags = parseFlags([`--${cliArg}`, 'test-value']); + expect(flags.knownArgs).toEqual([`--${cliArg}`, 'test-value']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toBe('test-value'); + }); + + it(`should parse "--${cliArg}=value"`, () => { + const flags = parseFlags([`--${cliArg}=path/to/file.js`]); + expect(flags.knownArgs).toEqual([`--${cliArg}`, 'path/to/file.js']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toBe('path/to/file.js'); + }); + + it(`should parse "--${toDashCase(cliArg)} value"`, () => { + const flags = parseFlags([`--${toDashCase(cliArg)}`, 'path/to/file.js']); + expect(flags.knownArgs).toEqual([`--${toDashCase(cliArg)}`, 'path/to/file.js']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toBe('path/to/file.js'); + }); + + it(`should parse "--${toDashCase(cliArg)}=value"`, () => { + const flags = parseFlags([`--${toDashCase(cliArg)}=path/to/file.js`]); + expect(flags.knownArgs).toEqual([`--${toDashCase(cliArg)}`, 'path/to/file.js']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toBe('path/to/file.js'); + }); + }); + + it.each(NUMBER_CLI_FLAGS)('should parse number flag %s', (cliArg) => { + const flags = parseFlags([`--${cliArg}`, '42']); + expect(flags.knownArgs).toEqual([`--${cliArg}`, '42']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toBe(42); + }); + + it('should override --config with second --config', () => { + const args = ['--config', '/config-1.js', '--config', '/config-2.js']; + const flags = parseFlags(args); + expect(flags.config).toBe('/config-2.js'); + }); + + describe.each(BOOLEAN_STRING_CLI_FLAGS)( + 'boolean-string flag - %s', + (cliArg: BooleanStringCLIFlag) => { + it('parses a boolean-string flag as a boolean with no arg', () => { + const args = [`--${cliArg}`]; + const flags = parseFlags(args); + expect(flags[cliArg]).toBe(true); + expect(flags.knownArgs).toEqual([`--${cliArg}`]); + }); + + it(`parses a boolean-string flag as a falsy boolean with "no" arg - --no-${cliArg}`, () => { + const args = [`--no-${cliArg}`]; + const flags = parseFlags(args); + expect(flags[cliArg]).toBe(false); + expect(flags.knownArgs).toEqual([`--no-${cliArg}`]); + }); + + it(`parses a boolean-string flag as a falsy boolean with "no" arg - --no${ + cliArg.charAt(0).toUpperCase() + cliArg.slice(1) + }`, () => { + const negativeFlag = '--no' + cliArg.charAt(0).toUpperCase() + cliArg.slice(1); + const flags = parseFlags([negativeFlag]); + expect(flags[cliArg]).toBe(false); + expect(flags.knownArgs).toEqual([negativeFlag]); + }); + + it('parses a boolean-string flag as a string with a string arg', () => { + const args = [`--${cliArg}`, 'shell']; + const flags = parseFlags(args); + expect(flags[cliArg]).toBe('shell'); + expect(flags.knownArgs).toEqual([`--${cliArg}`, 'shell']); + }); + + it('parses a boolean-string flag as a string with a string arg using equality', () => { + const args = [`--${cliArg}=shell`]; + const flags = parseFlags(args); + expect(flags[cliArg]).toBe('shell'); + expect(flags.knownArgs).toEqual([`--${cliArg}`, 'shell']); + }); + }, + ); + + describe.each(['info', 'warn', 'error', 'debug'])('logLevel %s', (level) => { + it("should parse '--logLevel %s'", () => { + const args = ['--logLevel', level]; + const flags = parseFlags(args); + expect(flags.logLevel).toBe(level); + }); + + it('should parse --logLevel=%s', () => { + const args = [`--logLevel=${level}`]; + const flags = parseFlags(args); + expect(flags.logLevel).toBe(level); + }); + + it("should parse '--log-level %s'", () => { + const flags = parseFlags(['--log-level', level]); + expect(flags.logLevel).toBe(level); + }); + + it('should parse --log-level=%s', () => { + const flags = parseFlags([`--log-level=${level}`]); + expect(flags.logLevel).toBe(level); + }); + }); + + /** + * maxWorkers is (as of this writing) our only StringNumberCLIArg, meaning it + * may be a string (like "50%") or a number (like 4). For this reason we have + * some tests just for it. + */ + describe('maxWorkers', () => { + it.each([ + ['--maxWorkers', '4'], + ['--maxWorkers=4'], + ['--max-workers', '4'], + ['--maxWorkers', '4e+0'], + ['--maxWorkers', '40e-1'], + ])('should parse %p, %p', (...args) => { + const flags = parseFlags(args); + expect(flags.maxWorkers).toBe(4); + }); + + it('should parse --maxWorkers 4', () => { + const flags = parseFlags(['--maxWorkers', '4']); + expect(flags.maxWorkers).toBe(4); + }); + + it('should parse --maxWorkers=4', () => { + const flags = parseFlags(['--maxWorkers=4']); + expect(flags.maxWorkers).toBe(4); + }); + + it('should parse --max-workers 4', () => { + const flags = parseFlags(['--max-workers', '4']); + expect(flags.maxWorkers).toBe(4); + }); + + it('should parse --maxWorkers=50%', function () { + // see https://jestjs.io/docs/27.x/cli#--maxworkersnumstring + const flags = parseFlags(['--maxWorkers=50%']); + expect(flags.maxWorkers).toBe('50%'); + }); + + it('should parse --max-workers=1', () => { + const flags = parseFlags(['--max-workers=1']); + expect(flags.maxWorkers).toBe(1); + }); + + it('should not parse --max-workers', () => { + const flags = parseFlags([]); + expect(flags.maxWorkers).toBe(undefined); + }); + }); + + describe('aliases', () => { + describe('-p (alias for port)', () => { + it('should parse -p=4444', () => { + const flags = parseFlags(['-p=4444']); + expect(flags.port).toBe(4444); + }); + it('should parse -p 4444', () => { + const flags = parseFlags(['-p', '4444']); + expect(flags.port).toBe(4444); + }); + }); + + it('should parse -h (alias for help)', () => { + const flags = parseFlags(['-h']); + expect(flags.help).toBe(true); + }); + + it('should parse -v (alias for version)', () => { + const flags = parseFlags(['-v']); + expect(flags.version).toBe(true); + }); + + describe('-c alias for config', () => { + it('should parse -c /my-config.js', () => { + const flags = parseFlags(['-c', '/my-config.js']); + expect(flags.config).toBe('/my-config.js'); + expect(flags.knownArgs).toEqual(['--config', '/my-config.js']); + }); + + it('should parse -c=/my-config.js', () => { + const flags = parseFlags(['-c=/my-config.js']); + expect(flags.config).toBe('/my-config.js'); + expect(flags.knownArgs).toEqual(['--config', '/my-config.js']); + }); + }); + }); + + it('should parse many', () => { + const args = ['-v', '--help', '-c=./myconfig.json']; + const flags = parseFlags(args); + expect(flags.version).toBe(true); + expect(flags.help).toBe(true); + expect(flags.config).toBe('./myconfig.json'); + }); + + describe('parseEqualsArg', () => { + it.each([ + ['--fooBar=baz', '--fooBar', 'baz'], + ['--foo-bar=4', '--foo-bar', '4'], + ['--fooBar=twenty=3*4', '--fooBar', 'twenty=3*4'], + ['--fooBar', '--fooBar', Empty], + ['--foo-bar', '--foo-bar', Empty], + ['--foo-bar=""', '--foo-bar', '""'], + ])('should parse %s correctly', (testArg, expectedArg, expectedValue) => { + const [arg, value] = parseEqualsArg(testArg); + expect(arg).toBe(expectedArg); + expect(value).toEqual(expectedValue); + }); + }); + + describe.each(STRING_ARRAY_CLI_FLAGS)( + 'should parse string array flag %s', + (cliArg: StringArrayCLIFlag) => { + it(`should parse single value: "--${cliArg} test-value"`, () => { + const flags = parseFlags([`--${cliArg}`, 'test-value']); + expect(flags.knownArgs).toEqual([`--${cliArg}`, 'test-value']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['test-value']); + }); + + it(`should parse multiple values: "--${cliArg} test-value"`, () => { + const flags = parseFlags([`--${cliArg}`, 'test-value', `--${cliArg}`, 'second-test-value']); + expect(flags.knownArgs).toEqual([ + `--${cliArg}`, + 'test-value', + `--${cliArg}`, + 'second-test-value', + ]); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['test-value', 'second-test-value']); + }); + + it(`should parse "--${cliArg}=value"`, () => { + const flags = parseFlags([`--${cliArg}=path/to/file.js`]); + expect(flags.knownArgs).toEqual([`--${cliArg}`, 'path/to/file.js']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['path/to/file.js']); + }); + + it(`should parse multiple values: "--${cliArg}=test-value"`, () => { + const flags = parseFlags([`--${cliArg}=test-value`, `--${cliArg}=second-test-value`]); + expect(flags.knownArgs).toEqual([ + `--${cliArg}`, + 'test-value', + `--${cliArg}`, + 'second-test-value', + ]); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['test-value', 'second-test-value']); + }); + + it(`should parse "--${toDashCase(cliArg)} value"`, () => { + const flags = parseFlags([`--${toDashCase(cliArg)}`, 'path/to/file.js']); + expect(flags.knownArgs).toEqual([`--${toDashCase(cliArg)}`, 'path/to/file.js']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['path/to/file.js']); + }); + + it(`should parse multiple values: "--${toDashCase(cliArg)} test-value"`, () => { + const flags = parseFlags([ + `--${toDashCase(cliArg)}`, + 'test-value', + `--${toDashCase(cliArg)}`, + 'second-test-value', + ]); + expect(flags.knownArgs).toEqual([ + `--${toDashCase(cliArg)}`, + 'test-value', + `--${toDashCase(cliArg)}`, + 'second-test-value', + ]); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['test-value', 'second-test-value']); + }); + + it(`should parse "--${toDashCase(cliArg)}=value"`, () => { + const flags = parseFlags([`--${toDashCase(cliArg)}=path/to/file.js`]); + expect(flags.knownArgs).toEqual([`--${toDashCase(cliArg)}`, 'path/to/file.js']); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['path/to/file.js']); + }); + + it(`should parse multiple values: "--${toDashCase(cliArg)}=test-value"`, () => { + const flags = parseFlags([ + `--${toDashCase(cliArg)}=test-value`, + `--${toDashCase(cliArg)}=second-test-value`, + ]); + expect(flags.knownArgs).toEqual([ + `--${toDashCase(cliArg)}`, + 'test-value', + `--${toDashCase(cliArg)}`, + 'second-test-value', + ]); + expect(flags.unknownArgs).toEqual([]); + expect(flags[cliArg]).toEqual(['test-value', 'second-test-value']); + }); + }, + ); + + describe('error reporting', () => { + it('should throw if you pass no argument to a string flag', () => { + expect(() => { + parseFlags(['--config', '--someOtherFlag']); + }).toThrow( + 'when parsing CLI flag "--config": expected a string argument but received nothing', + ); + }); + + it('should throw if you pass no argument to a number flag', () => { + expect(() => { + parseFlags(['--port', '--someOtherFlag']); + }).toThrow('when parsing CLI flag "--port": expected a number argument but received nothing'); + }); + + it('should throw if you pass a non-number argument to a number flag', () => { + expect(() => { + parseFlags(['--port', 'stringy']); + }).toThrow('when parsing CLI flag "--port": expected a number but received "stringy"'); + }); + + it('should throw if you pass a bad number argument to a number flag', () => { + expect(() => { + parseFlags(['--port=NaN']); + }).toThrow('when parsing CLI flag "--port": expected a number but received "NaN"'); + }); + + it('should throw if you pass no argument to a string/number flag', () => { + expect(() => { + parseFlags(['--maxWorkers']); + }).toThrow( + 'when parsing CLI flag "--maxWorkers": expected a string or a number but received nothing', + ); + }); + + it('should throw if you pass an invalid log level for --logLevel', () => { + expect(() => { + parseFlags(['--logLevel', 'potato']); + }).toThrow( + 'when parsing CLI flag "--logLevel": expected to receive a valid log level but received "potato"', + ); + }); + + it('should throw if you pass no argument to --logLevel', () => { + expect(() => { + parseFlags(['--logLevel']); + }).toThrow( + 'when parsing CLI flag "--logLevel": expected to receive a valid log level but received nothing', + ); + }); + }); +}); diff --git a/packages/cli/src/_test_/run.spec.ts b/packages/cli/src/_test_/run.spec.ts new file mode 100644 index 00000000000..42bd30e6465 --- /dev/null +++ b/packages/cli/src/_test_/run.spec.ts @@ -0,0 +1,301 @@ +import * as coreCompiler from '@stencil/core/compiler'; +import { + mockCompilerSystem, + mockConfig, + mockLogger as createMockLogger, +} from '@stencil/core/testing'; +import { createTestingSystem } from '@stencil/core/testing'; +import { vi, type MockInstance, describe, it, beforeEach, expect, afterEach } from 'vitest'; +import type * as d from '@stencil/core/compiler'; + +import { createConfigFlags } from '../config-flags'; +import * as ParseFlags from '../parse-flags'; +import { run, runTask } from '../run'; +import * as BuildTask from '../task-build'; +import * as DocsTask from '../task-docs'; +import * as GenerateTask from '../task-generate'; +import * as HelpTask from '../task-help'; +import * as PrerenderTask from '../task-prerender'; +import * as ServeTask from '../task-serve'; +import * as TelemetryTask from '../task-telemetry'; + +describe('run', () => { + describe('run()', () => { + let cliInitOptions: d.CliInitOptions; + let mockLogger: d.Logger; + let mockSystem: d.CompilerSystem; + + let parseFlagsSpy: MockInstance; + + beforeEach(() => { + mockLogger = createMockLogger(); + mockSystem = createTestingSystem(); + + cliInitOptions = { + args: [], + logger: mockLogger, + sys: mockSystem, + }; + + parseFlagsSpy = vi.spyOn(ParseFlags, 'parseFlags'); + parseFlagsSpy.mockReturnValue( + createConfigFlags({ + // use the 'help' task as a reasonable default for all calls to this function. + // code paths that require a different task can always override this value as needed. + task: 'help', + }), + ); + }); + + afterEach(() => { + parseFlagsSpy.mockRestore(); + }); + + describe('help task', () => { + let taskHelpSpy: MockInstance; + + beforeEach(() => { + taskHelpSpy = vi.spyOn(HelpTask, 'taskHelp'); + taskHelpSpy.mockReturnValue(Promise.resolve()); + }); + + afterEach(() => { + taskHelpSpy.mockRestore(); + }); + + it("calls the help task when the 'task' field is set to 'help'", async () => { + await run(cliInitOptions); + + expect(taskHelpSpy).toHaveBeenCalledTimes(1); + expect(taskHelpSpy).toHaveBeenCalledWith( + { + task: 'help', + args: [], + knownArgs: [], + unknownArgs: [], + }, + mockLogger, + mockSystem, + ); + + taskHelpSpy.mockRestore(); + }); + + it("calls the help task when the 'task' field is set to null", async () => { + parseFlagsSpy.mockReturnValue( + createConfigFlags({ + task: null, + }), + ); + + await run(cliInitOptions); + + expect(taskHelpSpy).toHaveBeenCalledTimes(1); + expect(taskHelpSpy).toHaveBeenCalledWith( + { + task: 'help', + args: [], + knownArgs: [], + unknownArgs: [], + }, + mockLogger, + mockSystem, + ); + + taskHelpSpy.mockRestore(); + }); + + it("calls the help task when the 'help' field is set on flags", async () => { + parseFlagsSpy.mockReturnValue( + createConfigFlags({ + help: true, + }), + ); + + await run(cliInitOptions); + + expect(taskHelpSpy).toHaveBeenCalledTimes(1); + expect(taskHelpSpy).toHaveBeenCalledWith( + { + task: 'help', + args: [], + unknownArgs: [], + knownArgs: [], + }, + mockLogger, + mockSystem, + ); + + taskHelpSpy.mockRestore(); + }); + }); + }); + + describe('runTask()', () => { + let sys: d.CompilerSystem; + let unvalidatedConfig: d.UnvalidatedConfig; + + let taskBuildSpy: MockInstance; + let taskDocsSpy: MockInstance; + let taskGenerateSpy: MockInstance; + let taskHelpSpy: MockInstance; + let taskPrerenderSpy: MockInstance; + let taskServeSpy: MockInstance; + let taskTelemetrySpy: MockInstance; + + beforeEach(() => { + sys = mockCompilerSystem(); + sys.exit = vi.fn(); + + unvalidatedConfig = mockConfig({ outputTargets: [], sys, fsNamespace: 'testing' }); + + taskBuildSpy = vi.spyOn(BuildTask, 'taskBuild'); + taskBuildSpy.mockResolvedValue(); + + taskDocsSpy = vi.spyOn(DocsTask, 'taskDocs'); + taskDocsSpy.mockResolvedValue(); + + taskGenerateSpy = vi.spyOn(GenerateTask, 'taskGenerate'); + taskGenerateSpy.mockResolvedValue(); + + taskHelpSpy = vi.spyOn(HelpTask, 'taskHelp'); + taskHelpSpy.mockResolvedValue(); + + taskPrerenderSpy = vi.spyOn(PrerenderTask, 'taskPrerender'); + taskPrerenderSpy.mockResolvedValue(); + + taskServeSpy = vi.spyOn(ServeTask, 'taskServe'); + taskServeSpy.mockResolvedValue(); + + taskTelemetrySpy = vi.spyOn(TelemetryTask, 'taskTelemetry'); + taskTelemetrySpy.mockResolvedValue(); + }); + + afterEach(() => { + taskBuildSpy.mockRestore(); + taskDocsSpy.mockRestore(); + taskGenerateSpy.mockRestore(); + taskHelpSpy.mockRestore(); + taskPrerenderSpy.mockRestore(); + taskServeSpy.mockRestore(); + taskTelemetrySpy.mockRestore(); + }); + + describe('default configuration', () => { + describe('sys property', () => { + it('uses the sys argument if one is provided', async () => { + // remove the `CompilerSystem` on the config, just to be sure we don't accidentally use it + unvalidatedConfig.sys = undefined; + + await runTask(coreCompiler, unvalidatedConfig, 'build', sys); + + // first validate there was one call + expect(taskBuildSpy).toHaveBeenCalledTimes(1); + + // verify the sys was passed through to the validated config + const compilerSystemUsed: d.CompilerSystem = taskBuildSpy.mock.calls[0][1].sys; + expect(compilerSystemUsed).toBe(sys); + }); + }); + }); + + it('calls the build task', async () => { + await runTask(coreCompiler, unvalidatedConfig, 'build', sys); + + expect(taskBuildSpy).toHaveBeenCalledTimes(1); + // taskBuild now receives (coreCompiler, config, flags) + expect(taskBuildSpy.mock.calls[0][0]).toBe(coreCompiler); + expect(taskBuildSpy.mock.calls[0][1]).toHaveProperty('sys'); + expect(taskBuildSpy.mock.calls[0][2]).toHaveProperty('task', 'build'); + }); + + it('calls the docs task', async () => { + await runTask(coreCompiler, unvalidatedConfig, 'docs', sys); + + expect(taskDocsSpy).toHaveBeenCalledTimes(1); + // taskDocs receives (coreCompiler, config) + expect(taskDocsSpy.mock.calls[0][0]).toBe(coreCompiler); + expect(taskDocsSpy.mock.calls[0][1]).toHaveProperty('sys'); + }); + + describe('generate task', () => { + it("calls the generate task for the argument 'generate'", async () => { + await runTask(coreCompiler, unvalidatedConfig, 'generate', sys); + + expect(taskGenerateSpy).toHaveBeenCalledTimes(1); + // taskGenerate receives (config, flags) + expect(taskGenerateSpy.mock.calls[0][0]).toHaveProperty('sys'); + expect(taskGenerateSpy.mock.calls[0][1]).toHaveProperty('task', 'generate'); + }); + + it("calls the generate task for the argument 'g'", async () => { + await runTask(coreCompiler, unvalidatedConfig, 'g', sys); + + expect(taskGenerateSpy).toHaveBeenCalledTimes(1); + // taskGenerate receives (config, flags) + expect(taskGenerateSpy.mock.calls[0][0]).toHaveProperty('sys'); + expect(taskGenerateSpy.mock.calls[0][1]).toHaveProperty('task', 'g'); + }); + }); + + it('calls the help task', async () => { + await runTask(coreCompiler, unvalidatedConfig, 'help', sys); + + expect(taskHelpSpy).toHaveBeenCalledTimes(1); + // taskHelp receives (flags, logger, sys) + expect(taskHelpSpy.mock.calls[0][0]).toHaveProperty('task', 'help'); + expect(taskHelpSpy.mock.calls[0][2]).toBe(sys); + }); + + it('calls the prerender task', async () => { + await runTask(coreCompiler, unvalidatedConfig, 'prerender', sys); + + expect(taskPrerenderSpy).toHaveBeenCalledTimes(1); + // taskPrerender receives (coreCompiler, config, flags) + expect(taskPrerenderSpy.mock.calls[0][0]).toBe(coreCompiler); + expect(taskPrerenderSpy.mock.calls[0][1]).toHaveProperty('sys'); + expect(taskPrerenderSpy.mock.calls[0][2]).toHaveProperty('task', 'prerender'); + }); + + it('calls the serve task', async () => { + await runTask(coreCompiler, unvalidatedConfig, 'serve', sys); + + expect(taskServeSpy).toHaveBeenCalledTimes(1); + // taskServe receives (config, flags) + expect(taskServeSpy.mock.calls[0][0]).toHaveProperty('sys'); + expect(taskServeSpy.mock.calls[0][1]).toHaveProperty('task', 'serve'); + }); + + describe('telemetry task', () => { + it('calls the telemetry task when a compiler system is present', async () => { + await runTask(coreCompiler, unvalidatedConfig, 'telemetry', sys); + + expect(taskTelemetrySpy).toHaveBeenCalledTimes(1); + // taskTelemetry receives (flags, sys, logger) + expect(taskTelemetrySpy.mock.calls[0][0]).toHaveProperty('task', 'telemetry'); + expect(taskTelemetrySpy.mock.calls[0][1]).toBe(sys); + }); + }); + + it('defaults to the help task for an unaccounted for task name', async () => { + // info is a valid task name, but isn't used in the `switch` statement of `runTask` + await runTask(coreCompiler, unvalidatedConfig, 'info', sys); + + expect(taskHelpSpy).toHaveBeenCalledTimes(1); + // taskHelp receives (flags, logger, sys) + expect(taskHelpSpy.mock.calls[0][0]).toHaveProperty('task', 'info'); + expect(taskHelpSpy.mock.calls[0][2]).toBe(sys); + }); + + it('defaults to the provided task if no flags exist on the provided config', async () => { + unvalidatedConfig = mockConfig({ flags: undefined, sys }); + + await runTask(coreCompiler, unvalidatedConfig, 'help', sys); + + expect(taskHelpSpy).toHaveBeenCalledTimes(1); + // taskHelp receives (flags, logger, sys) + expect(taskHelpSpy.mock.calls[0][0]).toHaveProperty('task', 'help'); + expect(taskHelpSpy.mock.calls[0][2]).toBe(sys); + }); + }); +}); diff --git a/packages/cli/src/_test_/task-generate.spec.ts b/packages/cli/src/_test_/task-generate.spec.ts new file mode 100644 index 00000000000..c45bfbbdb1a --- /dev/null +++ b/packages/cli/src/_test_/task-generate.spec.ts @@ -0,0 +1,196 @@ +import * as utils from '@stencil/core/compiler/utils'; +import { mockCompilerSystem, mockValidatedConfig } from '@stencil/core/testing'; +import { vi, describe, it, expect, afterEach, afterAll } from 'vitest'; +import type * as d from '@stencil/core/compiler'; + +import { createConfigFlags, type ConfigFlags } from '../config-flags'; +import { BoilerplateFile, getBoilerplateByExtension, taskGenerate } from '../task-generate'; + +const promptMock = vi.hoisted(() => vi.fn().mockResolvedValue('my-component')); + +vi.mock('prompts', () => ({ + prompt: promptMock, +})); + +let formatToPick = 'css'; + +const setup = async (plugins: any[] = []) => { + const sys = mockCompilerSystem(); + const flags = createConfigFlags({ task: 'generate', unknownArgs: [] }); + const config: d.ValidatedConfig = mockValidatedConfig({ + configPath: '/testing-path', + srcDir: '/src', + sys, + plugins, + }); + + // set up some mocks / spies + config.sys.exit = vi.fn(); + const errorSpy = vi.spyOn(config.logger, 'error'); + const validateTagSpy = vi.spyOn(utils, 'validateComponentTag').mockReturnValue(undefined); + + // mock prompt usage: tagName and filesToGenerate are the keys used for + // different calls, so we can cheat here and just do a single + // mockResolvedValue + let format = formatToPick; + promptMock.mockImplementation((params) => { + if (params.name === 'sassFormat') { + format = 'sass'; + return { sassFormat: 'sass' }; + } + return { + tagName: 'my-component', + filesToGenerate: [format, 'spec.tsx', 'e2e.ts'], + }; + }); + + return { config, flags, errorSpy, validateTagSpy }; +}; + +/** + * Little test helper function which just temporarily silences + * console.log calls, so we can avoid spewing a bunch of stuff. + * @param config the user-supplied config to forward to `taskGenerate` + * @param flags the CLI flags to forward to `taskGenerate` + */ +async function silentGenerate(config: d.ValidatedConfig, flags: ConfigFlags): Promise { + const tmp = console.log; + console.log = vi.fn(); + await taskGenerate(config, flags); + console.log = tmp; +} + +describe('generate task', () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.clearAllMocks(); + vi.resetModules(); + formatToPick = 'css'; + }); + + afterAll(() => { + vi.resetAllMocks(); + }); + + it('should exit with an error if no `configPath` is supplied', async () => { + const { config, flags, errorSpy } = await setup(); + config.configPath = undefined; + await taskGenerate(config, flags); + expect(config.sys.exit).toHaveBeenCalledWith(1); + expect(errorSpy).toHaveBeenCalledWith( + 'Please run this command in your root directory (i. e. the one containing stencil.config.ts).', + ); + }); + + it('should exit with an error if no `srcDir` is supplied', async () => { + const { config, flags, errorSpy } = await setup(); + // @ts-expect-error force srcDir to be undefined to trigger the error case + config.srcDir = undefined; + await taskGenerate(config, flags); + expect(config.sys.exit).toHaveBeenCalledWith(1); + expect(errorSpy).toHaveBeenCalledWith("Stencil's srcDir was not specified."); + }); + + it('should exit with an error if the component name does not validate', async () => { + const { config, flags, errorSpy, validateTagSpy } = await setup(); + validateTagSpy.mockReturnValue('error error error'); + await taskGenerate(config, flags); + expect(config.sys.exit).toHaveBeenCalledWith(1); + expect(errorSpy).toHaveBeenCalledWith('error error error'); + }); + + it.each([true, false])( + 'should create a directory for the generated components', + async (includeTests) => { + const { config, flags } = await setup(); + if (!includeTests) { + promptMock.mockResolvedValue({ + tagName: 'my-component', + // simulate the user picking only the css option + filesToGenerate: ['css'], + }); + } + + const createDirSpy = vi.spyOn(config.sys, 'createDir'); + await silentGenerate(config, flags); + expect(createDirSpy).toHaveBeenCalledWith( + includeTests + ? `${config.srcDir}/components/my-component/test` + : `${config.srcDir}/components/my-component`, + { recursive: true }, + ); + }, + ); + + it('should generate the files the user picked', async () => { + const { config, flags } = await setup(); + const writeFileSpy = vi.spyOn(config.sys, 'writeFile'); + await silentGenerate(config, flags); + const userChoices: ReadonlyArray = [ + { extension: 'tsx', path: '/src/components/my-component/my-component.tsx' }, + { extension: 'css', path: '/src/components/my-component/my-component.css' }, + { extension: 'spec.tsx', path: '/src/components/my-component/test/my-component.spec.tsx' }, + { extension: 'e2e.ts', path: '/src/components/my-component/test/my-component.e2e.ts' }, + ]; + + userChoices.forEach((file) => { + expect(writeFileSpy).toHaveBeenCalledWith( + file.path, + getBoilerplateByExtension('my-component', file.extension, true, 'css'), + ); + }); + }); + + it('should error without writing anything if a to-be-generated file is already present', async () => { + const { config, flags, errorSpy } = await setup(); + vi.spyOn(config.sys, 'readFile').mockResolvedValue('some file contents'); + await silentGenerate(config, flags); + expect(errorSpy).toHaveBeenCalledWith( + 'Generating code would overwrite the following files:', + '\t/src/components/my-component/my-component.tsx', + '\t/src/components/my-component/my-component.css', + '\t/src/components/my-component/test/my-component.spec.tsx', + '\t/src/components/my-component/test/my-component.e2e.ts', + ); + expect(config.sys.exit).toHaveBeenCalledWith(1); + }); + + it('should generate files for sass projects', async () => { + const { config, flags } = await setup([{ name: 'sass' }]); + const writeFileSpy = vi.spyOn(config.sys, 'writeFile'); + await silentGenerate(config, flags); + const userChoices: ReadonlyArray = [ + { extension: 'tsx', path: '/src/components/my-component/my-component.tsx' }, + { extension: 'sass', path: '/src/components/my-component/my-component.sass' }, + { extension: 'spec.tsx', path: '/src/components/my-component/test/my-component.spec.tsx' }, + { extension: 'e2e.ts', path: '/src/components/my-component/test/my-component.e2e.ts' }, + ]; + + userChoices.forEach((file) => { + expect(writeFileSpy).toHaveBeenCalledWith( + file.path, + getBoilerplateByExtension('my-component', file.extension, true, 'sass'), + ); + }); + }); + + it('should generate files for less projects', async () => { + formatToPick = 'less'; + const { config, flags } = await setup([{ name: 'less' }]); + const writeFileSpy = vi.spyOn(config.sys, 'writeFile'); + await silentGenerate(config, flags); + const userChoices: ReadonlyArray = [ + { extension: 'tsx', path: '/src/components/my-component/my-component.tsx' }, + { extension: 'less', path: '/src/components/my-component/my-component.less' }, + { extension: 'spec.tsx', path: '/src/components/my-component/test/my-component.spec.tsx' }, + { extension: 'e2e.ts', path: '/src/components/my-component/test/my-component.e2e.ts' }, + ]; + + userChoices.forEach((file) => { + expect(writeFileSpy).toHaveBeenCalledWith( + file.path, + getBoilerplateByExtension('my-component', file.extension, true, 'less'), + ); + }); + }); +}); diff --git a/packages/cli/src/_test_/task-migrate.spec.ts b/packages/cli/src/_test_/task-migrate.spec.ts new file mode 100644 index 00000000000..fd2c756a8ac --- /dev/null +++ b/packages/cli/src/_test_/task-migrate.spec.ts @@ -0,0 +1,363 @@ +import { mockCompilerSystem, mockValidatedConfig } from '@stencil/core/testing'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import type * as d from '@stencil/core/compiler'; + +import { createConfigFlags } from '../config-flags'; +import { detectMigrations, taskMigrate } from '../task-migrate'; + +// Mock migration rules module +const mockRules = vi.hoisted(() => [ + { + id: 'test-rule', + name: 'Test Migration Rule', + description: 'A test migration rule', + fromVersion: '4.x', + toVersion: '5.x', + detect: vi.fn(), + transform: vi.fn(), + }, +]); + +vi.mock('../migrations', () => ({ + getRulesForVersionUpgrade: vi.fn((from: string, to: string) => { + if (from === '4' && to === '5') { + return mockRules; + } + return []; + }), +})); + +// Mock TypeScript's getParsedCommandLineOfConfigFile +vi.mock('typescript', async () => { + const actual = await vi.importActual('typescript'); + return { + ...actual, + default: { + ...actual, + getParsedCommandLineOfConfigFile: vi.fn(() => ({ + fileNames: ['/test/src/components/my-component.tsx'], + errors: [], + })), + }, + }; +}); + +const mockCoreCompiler = { + version: '5.0.0', +} as any; + +interface SetupOptions { + dryRun?: boolean; + fileContent?: string | null; + detectMatches?: Array<{ node: any; message: string; line: number; column: number }>; +} + +const setup = async (options: SetupOptions = {}) => { + const { dryRun = false, fileContent = null, detectMatches = [] } = options; + + const sys = mockCompilerSystem(); + const flags = createConfigFlags({ task: 'migrate', dryRun }); + const config: d.ValidatedConfig = mockValidatedConfig({ + configPath: '/test/stencil.config.ts', + rootDir: '/test', + sys, + }); + + // Mock sys methods + config.sys.exit = vi.fn(); + vi.spyOn(config.sys, 'readFile').mockImplementation(async (path: string) => { + if (path.endsWith('tsconfig.json')) { + return '{}'; + } + return fileContent; + }); + vi.spyOn(config.sys, 'writeFile').mockResolvedValue({} as any); + + // Mock logger methods + const infoSpy = vi.spyOn(config.logger, 'info'); + + // Configure mock rule behavior + mockRules[0].detect.mockReturnValue(detectMatches); + mockRules[0].transform.mockImplementation((_sourceFile: any, _matches: any) => { + return 'transformed content'; + }); + + return { config, flags, infoSpy, sys }; +}; + +describe('task-migrate', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('when no migrations are needed', () => { + it('should report that code is up to date', async () => { + const { config, flags, infoSpy } = await setup({ + fileContent: 'const x = 1;', + detectMatches: [], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('No migrations needed')); + }); + + it('should not write any files', async () => { + const { config, flags, sys } = await setup({ + fileContent: 'const x = 1;', + detectMatches: [], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + // writeFile should not be called for component files + const writeFileCalls = (sys.writeFile as any).mock.calls.filter((call: any[]) => + call[0].endsWith('.tsx'), + ); + expect(writeFileCalls).toHaveLength(0); + }); + }); + + describe('when migrations are found', () => { + const migrationMatch = { + node: {}, + message: 'Found deprecated API', + line: 10, + column: 5, + }; + + it('should show detected migrations', async () => { + const { config, flags, infoSpy } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('Line 10')); + }); + + it('should apply migrations and write files', async () => { + const { config, flags, sys } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(sys.writeFile).toHaveBeenCalledWith( + '/test/src/components/my-component.tsx', + 'transformed content', + ); + }); + + it('should show success message', async () => { + const { config, flags, infoSpy } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('Successfully migrated')); + }); + + it('should show migration summary', async () => { + const { config, flags, infoSpy } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('Migration Summary')); + }); + }); + + describe('with --dry-run flag', () => { + const migrationMatch = { + node: {}, + message: 'Found deprecated API', + line: 10, + column: 5, + }; + + it('should not modify files', async () => { + const { config, flags, sys } = await setup({ + dryRun: true, + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + // writeFile should not be called for component files + const writeFileCalls = (sys.writeFile as any).mock.calls.filter((call: any[]) => + call[0].endsWith('.tsx'), + ); + expect(writeFileCalls).toHaveLength(0); + }); + + it('should show dry run message', async () => { + const { config, flags, infoSpy } = await setup({ + dryRun: true, + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('Dry run mode')); + }); + + it('should show hint to run without --dry-run', async () => { + const { config, flags, infoSpy } = await setup({ + dryRun: true, + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith( + expect.stringContaining('Run without --dry-run to apply the migrations'), + ); + }); + + it('should still show what would be migrated', async () => { + const { config, flags, infoSpy } = await setup({ + dryRun: true, + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('Line 10')); + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('Found deprecated API')); + }); + }); + + describe('edge cases', () => { + it('should handle no TypeScript files found', async () => { + const ts = await import('typescript'); + // Use mockReturnValueOnce to avoid affecting other tests + vi.mocked(ts.default.getParsedCommandLineOfConfigFile).mockReturnValueOnce({ + fileNames: [], + errors: [], + options: {}, + } as any); + + const { config, flags, infoSpy } = await setup({ + fileContent: null, + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('No migrations needed')); + }); + + it('should handle empty file content', async () => { + const { config, flags, sys } = await setup({ + fileContent: null, + detectMatches: [], + }); + + await taskMigrate(mockCoreCompiler, config, flags); + + // Should not crash and should not write any files + const writeFileCalls = (sys.writeFile as any).mock.calls.filter((call: any[]) => + call[0].endsWith('.tsx'), + ); + expect(writeFileCalls).toHaveLength(0); + }); + }); + + describe('detectMigrations', () => { + it('should return hasMigrations: false when no migrations found', async () => { + const { config } = await setup({ + fileContent: 'const x = 1;', + detectMatches: [], + }); + + const result = await detectMigrations(mockCoreCompiler, config); + + expect(result.hasMigrations).toBe(false); + expect(result.totalMatches).toBe(0); + expect(result.filesAffected).toBe(0); + expect(result.migrations).toHaveLength(0); + }); + + it('should return hasMigrations: true when migrations are found', async () => { + const migrationMatch = { + node: {}, + message: 'Found deprecated API', + line: 10, + column: 5, + }; + + const { config } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + const result = await detectMigrations(mockCoreCompiler, config); + + expect(result.hasMigrations).toBe(true); + expect(result.totalMatches).toBe(2); + expect(result.filesAffected).toBe(2); + expect(result.migrations).toHaveLength(2); + }); + + it('should include migration details', async () => { + const migrationMatch = { + node: {}, + message: 'Found deprecated API', + line: 10, + column: 5, + }; + + const { config } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + const result = await detectMigrations(mockCoreCompiler, config); + + expect(result.migrations[0].filePath).toBe('/test/src/components/my-component.tsx'); + expect(result.migrations[0].rule.id).toBe('test-rule'); + expect(result.migrations[0].matches).toHaveLength(1); + }); + + it('should not modify any files', async () => { + const migrationMatch = { + node: {}, + message: 'Found deprecated API', + line: 10, + column: 5, + }; + + const { config, sys } = await setup({ + fileContent: '@Component({ shadow: true }) class MyComponent {}', + detectMatches: [migrationMatch], + }); + + await detectMigrations(mockCoreCompiler, config); + + // writeFile should never be called during detection + expect(sys.writeFile).not.toHaveBeenCalled(); + }); + + it('should include rules in result', async () => { + const { config } = await setup({ + fileContent: 'const x = 1;', + detectMatches: [], + }); + + const result = await detectMigrations(mockCoreCompiler, config); + + expect(result.rules).toHaveLength(1); + expect(result.rules[0].id).toBe('test-rule'); + }); + }); +}); diff --git a/packages/cli/src/check-version.ts b/packages/cli/src/check-version.ts new file mode 100644 index 00000000000..d7189ee925b --- /dev/null +++ b/packages/cli/src/check-version.ts @@ -0,0 +1,45 @@ +import { isFunction } from '@stencil/core/compiler/utils'; +import type { ValidatedConfig } from '@stencil/core/compiler'; + +import type { ConfigFlags } from './config-flags'; + +/** + * Retrieve a reference to the active `CompilerSystem`'s `checkVersion` function + * @param config the Stencil configuration associated with the currently compiled project + * @param currentVersion the Stencil compiler's version string + * @param flags the CLI flags (owned by CLI, not part of core config) + * @returns a reference to `checkVersion`, or `null` if one does not exist on the current `CompilerSystem` + */ +export const startCheckVersion = async ( + config: ValidatedConfig, + currentVersion: string, + flags: ConfigFlags, +): Promise<(() => void) | null> => { + if ( + config.devMode && + !flags.ci && + !currentVersion.includes('-dev.') && + isFunction(config.sys.checkVersion) + ) { + return config.sys.checkVersion(config.logger, currentVersion); + } + return null; +}; + +/** + * Print the results of running the provided `versionChecker`. + * + * Does not print if no `versionChecker` is provided. + * + * @param versionChecker the function to invoke. + */ +export const printCheckVersionResults = async ( + versionChecker: Promise<(() => void) | null>, +): Promise => { + if (versionChecker) { + const checkVersionResults = await versionChecker; + if (isFunction(checkVersionResults)) { + checkVersionResults(); + } + } +}; diff --git a/packages/cli/src/config-flags.ts b/packages/cli/src/config-flags.ts new file mode 100644 index 00000000000..5c9c8630be5 --- /dev/null +++ b/packages/cli/src/config-flags.ts @@ -0,0 +1,224 @@ +import type { LogLevel } from '@stencil/core/compiler'; + +import type { TaskCommand } from './types'; + +/** + * All the Boolean options supported by the Stencil CLI + */ +export const BOOLEAN_CLI_FLAGS = [ + 'build', + 'cache', + 'checkVersion', + 'ci', + 'compare', + 'debug', + 'dev', + 'devtools', + 'docs', + 'dryRun', + 'help', + 'log', + 'open', + 'prerender', + 'prerenderExternal', + 'profile', + 'serviceWorker', + 'serve', + 'skipNodeCheck', + 'ssr', + 'verbose', + 'version', + 'watch', +] as const; + +/** + * All the Number options supported by the Stencil CLI + */ +export const NUMBER_CLI_FLAGS = ['port'] as const; + +/** + * All the String options supported by the Stencil CLI + */ +export const STRING_CLI_FLAGS = [ + 'address', + 'config', + 'docsApi', + 'docsJson', + 'emulate', + 'root', +] as const; + +export const STRING_ARRAY_CLI_FLAGS = [] as const; + +/** + * All the CLI arguments which may have string or number values + * + * `maxWorkers` controls the number of concurrent workers for Stencil builds. + * Supports both string (e.g., "50%") and number values. + */ +export const STRING_NUMBER_CLI_FLAGS = ['maxWorkers'] as const; + +/** + * All the CLI arguments which may have boolean or string values. + */ +export const BOOLEAN_STRING_CLI_FLAGS = [ + /** + * `stats` is an argument that can optionally accept a file path where stats should be written. + * When used as a boolean (--stats), it defaults to 'stencil-stats.json'. + * When used with a path (--stats dist/stats.json), it writes to that path. + */ + 'stats', +] as const; + +/** + * All the LogLevel-type options supported by the Stencil CLI + * + * This is a bit silly since there's only one such argument atm, + * but this approach lets us make sure that we're handling all + * our arguments in a type-safe way. + */ +export const LOG_LEVEL_CLI_FLAGS = ['logLevel'] as const; + +/** + * A type which gives the members of a `ReadonlyArray` as + * an enum-like type which can be used for e.g. keys in a `Record` + * (as in the `AliasMap` type below) + */ +type ArrayValuesAsUnion> = T[number]; + +type BooleanCLIFlag = ArrayValuesAsUnion; +type StringCLIFlag = ArrayValuesAsUnion; +export type StringArrayCLIFlag = ArrayValuesAsUnion; +type NumberCLIFlag = ArrayValuesAsUnion; +type StringNumberCLIFlag = ArrayValuesAsUnion; +export type BooleanStringCLIFlag = ArrayValuesAsUnion; +type LogCLIFlag = ArrayValuesAsUnion; + +type KnownCLIFlag = + | BooleanCLIFlag + | StringCLIFlag + | StringArrayCLIFlag + | NumberCLIFlag + | StringNumberCLIFlag + | BooleanStringCLIFlag + | LogCLIFlag; + +type AliasMap = Partial>; + +/** + * For a small subset of CLI options we support a short alias e.g. `'h'` for `'help'` + */ +export const CLI_FLAG_ALIASES: AliasMap = { + c: 'config', + h: 'help', + p: 'port', + v: 'version', +}; + +/** + * A regular expression which can be used to match a CLI flag for one of our + * short aliases. + */ +export const CLI_FLAG_REGEX = new RegExp(`^-[chpv]{1}$`); + +/** + * Given two types `K` and `T` where `K` extends `ReadonlyArray`, + * construct a type which maps the strings in `K` as keys to values of type `T`. + * + * Because we use types derived this way to construct an interface (`ConfigFlags`) + * for which we want optional keys, we make all the properties optional (w/ `'?'`) + * and possibly null. + */ +type ObjectFromKeys, T> = { + [key in K[number]]?: T | null; +}; + +/** + * Type containing the possible Boolean configuration flags, to be included + * in ConfigFlags, below + */ +type BooleanConfigFlags = ObjectFromKeys; + +/** + * Type containing the possible String configuration flags, to be included + * in ConfigFlags, below + */ +type StringConfigFlags = ObjectFromKeys; + +/** + * Type containing the possible String Array configuration flags. This is + * one of the 'constituent types' for `ConfigFlags`. + */ +type StringArrayConfigFlags = ObjectFromKeys; + +/** + * Type containing the possible numeric configuration flags, to be included + * in ConfigFlags, below + */ +type NumberConfigFlags = ObjectFromKeys; + +/** + * Type containing the configuration flags which may be set to either string + * or number values. + */ +type StringNumberConfigFlags = ObjectFromKeys; + +/** + * Type containing the configuration flags which may be set to either string + * or boolean values. + */ +type BooleanStringConfigFlags = ObjectFromKeys; + +/** + * Type containing the possible LogLevel configuration flags, to be included + * in ConfigFlags, below + */ +type LogLevelFlags = ObjectFromKeys; + +/** + * The configuration flags which can be set by the user on the command line. + * This interface captures both known arguments (which are enumerated and then + * parsed according to their types) and unknown arguments which the user may + * pass at the CLI. + * + * Note that this interface is constructed by extending `BooleanConfigFlags`, + * `StringConfigFlags`, etc. These types are in turn constructed from types + * extending `ReadonlyArray` which we declare in another module. This + * allows us to record our known CLI arguments in one place, using a + * `ReadonlyArray` to get both a type-level representation of what CLI + * options we support and a runtime list of strings which can be used to match + * on actual flags passed by the user. + */ +export interface ConfigFlags + extends + BooleanConfigFlags, + StringConfigFlags, + StringArrayConfigFlags, + NumberConfigFlags, + StringNumberConfigFlags, + BooleanStringConfigFlags, + LogLevelFlags { + task: TaskCommand | null; + args: string[]; + knownArgs: string[]; + unknownArgs: string[]; +} + +/** + * Helper function for initializing a `ConfigFlags` object. Provide any overrides + * for default values and off you go! + * + * @param init an object with any overrides for default values + * @returns a complete CLI flag object + */ +export const createConfigFlags = (init: Partial = {}): ConfigFlags => { + const flags: ConfigFlags = { + task: null, + args: [], + knownArgs: [], + unknownArgs: [], + ...init, + }; + + return flags; +}; diff --git a/src/cli/find-config.ts b/packages/cli/src/find-config.ts similarity index 83% rename from src/cli/find-config.ts rename to packages/cli/src/find-config.ts index 5a1c2a75de9..fa2a864b83c 100644 --- a/src/cli/find-config.ts +++ b/packages/cli/src/find-config.ts @@ -1,12 +1,11 @@ -import { buildError, isString, normalizePath, result } from '@utils'; - -import type { CompilerSystem, Diagnostic } from '../declarations'; +import { buildError, isString, normalizePath, result } from '@stencil/core/compiler/utils'; +import type { CompilerSystem, Diagnostic } from '@stencil/core/compiler'; /** * An object containing the {@link CompilerSystem} used to find the configuration file, as well as the location on disk * to search for a Stencil configuration */ -export type FindConfigOptions = { +type FindConfigOptions = { sys: CompilerSystem; configPath?: string | null; }; @@ -14,7 +13,7 @@ export type FindConfigOptions = { /** * The results of attempting to find a Stencil configuration file on disk */ -export type FindConfigResults = { +type FindConfigResults = { configPath: string; rootDir: string; }; @@ -24,7 +23,9 @@ export type FindConfigResults = { * @param opts the options needed to find the configuration file * @returns the results of attempting to find a configuration file on disk */ -export const findConfig = async (opts: FindConfigOptions): Promise> => { +export const findConfig = async ( + opts: FindConfigOptions, +): Promise> => { const sys = opts.sys; const cwd = sys.getCurrentDirectory(); const rootDir = normalizePath(cwd); @@ -67,8 +68,8 @@ export const findConfig = async (opts: FindConfigOptions): Promise process.env.JEST_WORKER_ID !== undefined; + +export const defaultConfig = (sys: d.CompilerSystem) => + sys.resolvePath(`${sys.homeDir()}/.ionic/${isTest() ? 'tmp-config.json' : 'config.json'}`); + +const defaultConfigDirectory = (sys: d.CompilerSystem) => + sys.resolvePath(`${sys.homeDir()}/.ionic`); + +/** + * Reads an Ionic configuration file from disk, parses it, and performs any necessary corrections to it if certain + * values are deemed to be malformed + * @param sys The system where the command is invoked + * @returns the config read from disk that has been potentially been updated + */ +export async function readConfig(sys: d.CompilerSystem) { + let config = await readJson(sys, defaultConfig(sys)); + + if (!config) { + config = { + 'tokens.telemetry': uuidv4(), + 'telemetry.stencil': true, + }; + + await writeConfig(sys, config); + } else if (!config['tokens.telemetry'] || !UUID_REGEX.test(config['tokens.telemetry'])) { + const newUuid = uuidv4(); + await writeConfig(sys, { ...config, 'tokens.telemetry': newUuid }); + config['tokens.telemetry'] = newUuid; + } + + return config; +} + +/** + * Writes an Ionic configuration file to disk. + * @param sys The system where the command is invoked + * @param config The config passed into the Stencil command + * @returns boolean If the command was successful + */ +export async function writeConfig( + sys: d.CompilerSystem, + config: TelemetryConfig, +): Promise { + let result = false; + try { + await sys.createDir(defaultConfigDirectory(sys), { recursive: true }); + await sys.writeFile(defaultConfig(sys), JSON.stringify(config, null, 2)); + result = true; + } catch (error) { + console.error( + `Stencil Telemetry: couldn't write configuration file to ${defaultConfig(sys)} - ${error}.`, + ); + } + + return result; +} + +/** + * Update a subset of the Ionic config. + * @param sys The system where the command is invoked + * @param newOptions The new options to save + * @returns boolean If the command was successful + */ +export async function updateConfig( + sys: d.CompilerSystem, + newOptions: TelemetryConfig, +): Promise { + const config = await readConfig(sys); + return await writeConfig(sys, Object.assign(config, newOptions)); +} diff --git a/src/cli/load-compiler.ts b/packages/cli/src/load-compiler.ts similarity index 78% rename from src/cli/load-compiler.ts rename to packages/cli/src/load-compiler.ts index 96fbcb7bf19..4566feb9691 100644 --- a/src/cli/load-compiler.ts +++ b/packages/cli/src/load-compiler.ts @@ -1,4 +1,4 @@ -import type { CompilerSystem } from '../declarations'; +import type { CompilerSystem } from '@stencil/core/compiler'; export const loadCoreCompiler = async (sys: CompilerSystem): Promise => { return await sys.dynamicImport!(sys.getCompilerExecutingPath()); diff --git a/src/cli/logs.ts b/packages/cli/src/logs.ts similarity index 90% rename from src/cli/logs.ts rename to packages/cli/src/logs.ts index fadf4098ccc..18fbccafd48 100644 --- a/src/cli/logs.ts +++ b/packages/cli/src/logs.ts @@ -1,6 +1,8 @@ -import type { CompilerSystem, Logger, TaskCommand, ValidatedConfig } from '../declarations'; +import type { CompilerSystem, Logger, ValidatedConfig } from '@stencil/core/compiler'; + import type { ConfigFlags } from './config-flags'; import type { CoreCompiler } from './load-compiler'; +import type { TaskCommand } from './types'; /** * Log the name of this package (`@stencil/core`) to an output stream @@ -31,7 +33,11 @@ export const startupLog = (logger: Logger, task: TaskCommand): void => { * @param task the current task * @param coreCompiler the compiler instance to derive version information from */ -export const startupLogVersion = (logger: Logger, task: TaskCommand, coreCompiler: CoreCompiler): void => { +export const startupLogVersion = ( + logger: Logger, + task: TaskCommand, + coreCompiler: CoreCompiler, +): void => { if (task === 'info' || task === 'serve' || task === 'version') { return; } @@ -117,12 +123,6 @@ export const startupCompilerLog = (coreCompiler: CoreCompiler, config: Validated } if (config.devMode && !isDebug) { - if (config.buildEs5) { - logger.warn( - `Generating ES5 during development is a very task expensive, initial and incremental builds will be much slower. Drop the '--es5' flag and use a modern browser for development.`, - ); - } - if (!config.enableCache) { logger.warn(`Disabling cache during development will slow down incremental builds.`); } diff --git a/packages/cli/src/merge-flags.ts b/packages/cli/src/merge-flags.ts new file mode 100644 index 00000000000..a204621aa3d --- /dev/null +++ b/packages/cli/src/merge-flags.ts @@ -0,0 +1,104 @@ +import type { UnvalidatedConfig } from '@stencil/core/compiler'; + +import type { ConfigFlags } from './config-flags'; + +/** + * Merge CLI flags into a Stencil configuration object. + * + * This function applies command-line flags to the config, with CLI flags + * taking precedence over config file values. This is the canonical place + * where flag values are translated into config properties. + * + * @param config The config object (from stencil.config.ts or empty) + * @param flags The parsed CLI flags + * @returns The config with flags merged in + */ +export const mergeFlags = (config: UnvalidatedConfig, flags: ConfigFlags): UnvalidatedConfig => { + const merged: UnvalidatedConfig = { ...config }; + + // --dev → devMode (production is the default; --dev is the explicit opt-in) + if (flags.dev === true) { + merged.devMode = true; + } + + // --verbose / --debug → logLevel + if (flags.debug === true || flags.verbose === true) { + merged.logLevel = 'debug'; + } else if (flags.logLevel) { + merged.logLevel = flags.logLevel; + } + + // --watch → watch + if (typeof flags.watch === 'boolean') { + merged.watch = flags.watch; + } + + // --docs → _docsFlag (internal flag to force docs in dev mode) + // This is processed during output target validation to set skipInDev: false on docs targets + if (flags.docs === true) { + merged._docsFlag = true; + } + + // --profile → profile + if (typeof flags.profile === 'boolean') { + merged.profile = flags.profile; + } + + // --log → writeLog + if (typeof flags.log === 'boolean') { + merged.writeLog = flags.log; + } + + // --cache → enableCache + if (typeof flags.cache === 'boolean') { + merged.enableCache = flags.cache; + } + + // --ci → ci + if (typeof flags.ci === 'boolean') { + merged.ci = flags.ci; + } + + // --ssr → ssr + if (typeof flags.ssr === 'boolean') { + merged.ssr = flags.ssr; + } + + // --prerender → prerender + if (typeof flags.prerender === 'boolean') { + merged.prerender = flags.prerender; + } + + // --docsJson → docsJsonPath + if (typeof flags.docsJson === 'string') { + merged.docsJsonPath = flags.docsJson; + } + + // --stats → statsJsonPath + if (flags.stats) { + merged.statsJsonPath = flags.stats; + } + + // --serviceWorker → generateServiceWorker + if (typeof flags.serviceWorker === 'boolean') { + merged.generateServiceWorker = flags.serviceWorker; + } + + // --maxWorkers → maxConcurrentWorkers + if (typeof flags.maxWorkers === 'number') { + merged.maxConcurrentWorkers = flags.maxWorkers; + } + + // Dev server overrides + if (typeof flags.address === 'string') { + merged.devServerAddress = flags.address; + } + if (typeof flags.port === 'number') { + merged.devServerPort = flags.port; + } + if (typeof flags.open === 'boolean') { + merged.devServerOpen = flags.open; + } + + return merged; +}; diff --git a/packages/cli/src/migrations/_test_/encapsulation-api.spec.ts b/packages/cli/src/migrations/_test_/encapsulation-api.spec.ts new file mode 100644 index 00000000000..5bef58c8bbb --- /dev/null +++ b/packages/cli/src/migrations/_test_/encapsulation-api.spec.ts @@ -0,0 +1,334 @@ +import ts from 'typescript'; +import { describe, expect, it } from 'vitest'; + +import { encapsulationApiRule } from '../rules/encapsulation-api'; + +/** + * Helper to create a TypeScript source file from code string + */ +function createSourceFile(code: string): ts.SourceFile { + return ts.createSourceFile('test.tsx', code, ts.ScriptTarget.Latest, true); +} + +describe('encapsulation-api migration rule', () => { + describe('metadata', () => { + it('should have correct rule metadata', () => { + expect(encapsulationApiRule.id).toBe('encapsulation-api'); + expect(encapsulationApiRule.name).toBe('Encapsulation API'); + expect(encapsulationApiRule.fromVersion).toBe('4.x'); + expect(encapsulationApiRule.toVersion).toBe('5.x'); + }); + }); + + describe('detect', () => { + it('should detect shadow: true', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + shadow: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain("'shadow'"); + }); + + it('should detect shadow: false', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + shadow: false + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + }); + + it('should detect shadow with options object', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + shadow: { delegatesFocus: true } + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + }); + + it('should detect scoped: true', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + scoped: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain("'scoped'"); + }); + + it('should detect both shadow and scoped in same file', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'cmp-a', + shadow: true + }) + export class CmpA {} + + @Component({ + tag: 'cmp-b', + scoped: true + }) + export class CmpB {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(2); + }); + + it('should not detect when using new encapsulation API', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + encapsulation: { type: 'shadow' } + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + + it('should not detect components without shadow or scoped', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component' + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + + it('should provide correct line numbers', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + shadow: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].line).toBe(4); // shadow: true is on line 4 + }); + + it('should detect shadow: true with aliased Component import', () => { + const code = ` + import { Component as Cmp } from '@stencil/core'; + @Cmp({ + tag: 'my-component', + shadow: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain("'shadow'"); + }); + + it('should detect scoped: true with aliased Component import', () => { + const code = ` + import { Component as StencilComponent, h } from '@stencil/core'; + @StencilComponent({ + tag: 'my-component', + scoped: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain("'scoped'"); + }); + + it('should not detect non-Stencil decorators with same name as alias', () => { + const code = ` + import { Component as Cmp } from '@stencil/core'; + import { SomeDecorator } from 'other-library'; + @SomeDecorator({ + shadow: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + }); + + describe('transform', () => { + it('should transform shadow: true to encapsulation: { type: "shadow" }', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + shadow: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("encapsulation: { type: 'shadow' }"); + expect(result).not.toContain('shadow: true'); + }); + + it('should transform shadow: false by removing it', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + shadow: false +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).not.toContain('shadow'); + expect(result).not.toContain('encapsulation'); + }); + + it('should transform shadow with delegatesFocus', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + shadow: { delegatesFocus: true } +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("encapsulation: { type: 'shadow', delegatesFocus: true }"); + expect(result).not.toContain('shadow: {'); + }); + + it('should transform shadow with slotAssignment', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + shadow: { slotAssignment: 'manual' } +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'shadow'"); + expect(result).toContain("slotAssignment: 'manual'"); + }); + + it('should transform shadow with multiple options', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + shadow: { delegatesFocus: true, slotAssignment: 'manual' } +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'shadow'"); + expect(result).toContain('delegatesFocus: true'); + expect(result).toContain("slotAssignment: 'manual'"); + }); + + it('should transform scoped: true to encapsulation: { type: "scoped" }', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + scoped: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("encapsulation: { type: 'scoped' }"); + expect(result).not.toContain('scoped: true'); + }); + + it('should transform scoped: false by removing it', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + scoped: false +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).not.toContain('scoped'); + expect(result).not.toContain('encapsulation'); + }); + + it('should preserve other component options', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + styleUrl: 'my-component.css', + shadow: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = encapsulationApiRule.detect(sourceFile); + const result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("tag: 'my-component'"); + expect(result).toContain("styleUrl: 'my-component.css'"); + expect(result).toContain("encapsulation: { type: 'shadow' }"); + }); + + it('should return original text when no matches', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component' +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const result = encapsulationApiRule.transform(sourceFile, []); + + expect(result).toBe(code); + }); + }); +}); diff --git a/packages/cli/src/migrations/_test_/form-associated.spec.ts b/packages/cli/src/migrations/_test_/form-associated.spec.ts new file mode 100644 index 00000000000..258a20dfca8 --- /dev/null +++ b/packages/cli/src/migrations/_test_/form-associated.spec.ts @@ -0,0 +1,345 @@ +import ts from 'typescript'; +import { describe, expect, it } from 'vitest'; + +import { formAssociatedRule } from '../rules/form-associated'; + +/** + * Helper to create a TypeScript source file from code string + */ +function createSourceFile(code: string): ts.SourceFile { + return ts.createSourceFile('test.tsx', code, ts.ScriptTarget.Latest, true); +} + +describe('form-associated migration rule', () => { + describe('metadata', () => { + it('should have correct rule metadata', () => { + expect(formAssociatedRule.id).toBe('form-associated'); + expect(formAssociatedRule.name).toBe('Form Associated'); + expect(formAssociatedRule.fromVersion).toBe('4.x'); + expect(formAssociatedRule.toVersion).toBe('5.x'); + }); + }); + + describe('detect', () => { + it('should detect formAssociated: true', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + formAssociated: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('formAssociated'); + }); + + it('should detect formAssociated with shadow', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + shadow: true, + formAssociated: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + }); + + it('should not detect when formAssociated is not present', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'my-component', + shadow: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + + it('should detect when @AttachInternals already exists', () => { + const code = ` + import { Component, AttachInternals } from '@stencil/core'; + @Component({ + tag: 'my-component', + formAssociated: true + }) + export class MyComponent { + @AttachInternals() internals: ElementInternals; + } + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('already has @AttachInternals'); + }); + + it('should provide correct line numbers', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].line).toBe(4); // formAssociated: true is on line 4 + }); + + it('should detect multiple components with formAssociated', () => { + const code = ` + import { Component } from '@stencil/core'; + @Component({ + tag: 'cmp-a', + formAssociated: true + }) + export class CmpA {} + + @Component({ + tag: 'cmp-b', + formAssociated: true + }) + export class CmpB {} + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(2); + }); + + it('should detect formAssociated with aliased Component import', () => { + const code = ` + import { Component as Cmp, h } from '@stencil/core'; + @Cmp({ + tag: 'my-component', + formAssociated: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('formAssociated'); + }); + + it('should detect existing @AttachInternals with aliased import', () => { + const code = ` + import { Component as Cmp, AttachInternals as ElInternals } from '@stencil/core'; + @Cmp({ + tag: 'my-component', + formAssociated: true + }) + export class MyComponent { + @ElInternals() internals: ElementInternals; + } + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('already has @AttachInternals'); + }); + + it('should not detect non-Stencil decorators with same name as alias', () => { + const code = ` + import { Component as Cmp } from '@stencil/core'; + import { SomeDecorator } from 'other-library'; + @SomeDecorator({ + formAssociated: true + }) + export class MyComponent {} + `; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + }); + + describe('transform', () => { + it('should add @AttachInternals and remove formAssociated', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain('@AttachInternals()'); + expect(result).toContain('internals: ElementInternals'); + expect(result).not.toContain('formAssociated'); + }); + + it('should add AttachInternals to imports', () => { + const code = `import { Component, h } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain('AttachInternals'); + expect(result).toMatch( + /import\s*\{[^}]*AttachInternals[^}]*\}\s*from\s*['"]@stencil\/core['"]/, + ); + }); + + it('should not duplicate AttachInternals import if already present', () => { + const code = `import { Component, AttachInternals } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent { + @AttachInternals() internals: ElementInternals; +}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + // Should only have one AttachInternals in imports + const importMatches = result.match(/AttachInternals/g); + // One in import, one in decorator usage + expect(importMatches!.length).toBe(2); + }); + + it('should only remove formAssociated when @AttachInternals already exists', () => { + const code = `import { Component, AttachInternals } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent { + @AttachInternals() internals: ElementInternals; +}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).not.toContain('formAssociated'); + // Should still have the existing @AttachInternals + expect(result).toContain('@AttachInternals()'); + }); + + it('should preserve other component options', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + styleUrl: 'my-component.css', + formAssociated: true +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain("tag: 'my-component'"); + expect(result).toContain("styleUrl: 'my-component.css'"); + expect(result).not.toContain('formAssociated'); + }); + + it('should handle trailing comma after formAssociated', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true, +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).not.toContain('formAssociated'); + // Should be valid syntax + expect(() => createSourceFile(result)).not.toThrow(); + }); + + it('should insert @AttachInternals with correct indentation', () => { + const code = `import { Component, Prop } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent { + @Prop() value: string; +}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + // Should have @AttachInternals before @Prop + const attachIndex = result.indexOf('@AttachInternals'); + const propIndex = result.indexOf('@Prop'); + expect(attachIndex).toBeLessThan(propIndex); + }); + + it('should return original text when no matches', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component' +}) +export class MyComponent {}`; + const sourceFile = createSourceFile(code); + const result = formAssociatedRule.transform(sourceFile, []); + + expect(result).toBe(code); + }); + + it('should handle component with extends clause', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent extends BaseComponent { +}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain('@AttachInternals()'); + expect(result).toContain('extends BaseComponent'); + // Should be inside the class body, not before extends + const classBodyStart = result.indexOf('{', result.indexOf('extends BaseComponent')); + const attachIndex = result.indexOf('@AttachInternals'); + expect(attachIndex).toBeGreaterThan(classBodyStart); + }); + + it('should handle component with implements clause', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-component', + formAssociated: true +}) +export class MyComponent implements SomeInterface { +}`; + const sourceFile = createSourceFile(code); + const matches = formAssociatedRule.detect(sourceFile); + const result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain('@AttachInternals()'); + expect(result).toContain('implements SomeInterface'); + }); + }); +}); diff --git a/packages/cli/src/migrations/_test_/global-style-inject.spec.ts b/packages/cli/src/migrations/_test_/global-style-inject.spec.ts new file mode 100644 index 00000000000..7499009d4d5 --- /dev/null +++ b/packages/cli/src/migrations/_test_/global-style-inject.spec.ts @@ -0,0 +1,291 @@ +import ts from 'typescript'; +import { describe, it, expect } from 'vitest'; + +import { globalStyleInjectRule } from '../rules/global-style-inject'; + +describe('global-style-inject migration', () => { + describe('detect', () => { + it('should detect addGlobalStyleToComponents: true', () => { + const source = ` +export const config: Config = { + extras: { + addGlobalStyleToComponents: true, + }, +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('addGlobalStyleToComponents'); + expect(matches[0].message).toContain('removed'); + expect(matches[0].message).toContain('inject'); + }); + + it('should detect addGlobalStyleToComponents: false', () => { + const source = ` +export const config: Config = { + extras: { + addGlobalStyleToComponents: false, + }, +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('addGlobalStyleToComponents'); + }); + + it("should detect addGlobalStyleToComponents: 'client'", () => { + const source = ` +export const config: Config = { + extras: { + addGlobalStyleToComponents: 'client', + }, +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('addGlobalStyleToComponents'); + }); + + it('should not detect addGlobalStyleToComponents outside of extras', () => { + const source = ` +export const config: Config = { + addGlobalStyleToComponents: true, +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + + it('should not detect when extras does not contain addGlobalStyleToComponents', () => { + const source = ` +export const config: Config = { + extras: { + someOtherOption: true, + }, +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + + it('should detect with other extras options present', () => { + const source = ` +export const config: Config = { + extras: { + enableImportInjection: true, + addGlobalStyleToComponents: true, + experimentalSlotFixes: true, + }, +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + + expect(matches).toHaveLength(1); + expect(matches[0].message).toContain('addGlobalStyleToComponents'); + }); + }); + + describe('transform', () => { + it('should remove addGlobalStyleToComponents: true and add global-style with inject: all', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + addGlobalStyleToComponents: true, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).toContain("type: 'global-style'"); + expect(result).toContain("inject: 'all'"); + }); + + it("should remove addGlobalStyleToComponents: 'client' and add global-style with inject: client", () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + addGlobalStyleToComponents: 'client', + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).toContain("type: 'global-style'"); + expect(result).toContain("inject: 'client'"); + }); + + it('should remove addGlobalStyleToComponents: false without adding global-style output target', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + addGlobalStyleToComponents: false, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).not.toContain("type: 'global-style'"); + }); + + it('should clean up empty extras object after removal', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + addGlobalStyleToComponents: false, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('extras'); + }); + + it('should preserve other extras options after removal', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + enableImportInjection: true, + addGlobalStyleToComponents: true, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).toContain('extras'); + expect(result).toContain('enableImportInjection: true'); + expect(result).toContain("type: 'global-style'"); + expect(result).toContain("inject: 'all'"); + }); + + it('should not add global-style if one already exists', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + { type: 'global-style', fileName: 'custom.css' }, + ], + extras: { + addGlobalStyleToComponents: true, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + // Should only have one global-style, not add another + const globalStyleCount = (result.match(/type: 'global-style'/g) || []).length; + expect(globalStyleCount).toBe(1); + }); + + it('should handle config with no outputTargets array', () => { + const source = `export const config: Config = { + extras: { + addGlobalStyleToComponents: true, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + // Should remove the property but not crash when there's no outputTargets + expect(result).not.toContain('addGlobalStyleToComponents'); + }); + + it('should handle trailing comma after addGlobalStyleToComponents', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + addGlobalStyleToComponents: true, + enableImportInjection: true, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).toContain('enableImportInjection: true'); + // Ensure no syntax errors (double commas, etc.) + expect(result).not.toMatch(/,\s*,/); + }); + + it('should handle addGlobalStyleToComponents as last property with leading comma', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], + extras: { + enableImportInjection: true, + addGlobalStyleToComponents: true + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).toContain('enableImportInjection: true'); + }); + + it('should return source unchanged when no matches', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'www' }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).toBe(source); + }); + + it('should add global-style output target to empty outputTargets array', () => { + const source = `export const config: Config = { + outputTargets: [], + extras: { + addGlobalStyleToComponents: true, + }, +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = globalStyleInjectRule.detect(sourceFile); + const result = globalStyleInjectRule.transform(sourceFile, matches); + + expect(result).not.toContain('addGlobalStyleToComponents'); + expect(result).toContain("type: 'global-style'"); + expect(result).toContain("inject: 'all'"); + }); + }); +}); diff --git a/packages/cli/src/migrations/_test_/index.spec.ts b/packages/cli/src/migrations/_test_/index.spec.ts new file mode 100644 index 00000000000..a4e7f2ceafd --- /dev/null +++ b/packages/cli/src/migrations/_test_/index.spec.ts @@ -0,0 +1,525 @@ +import ts from 'typescript'; +import { describe, expect, it } from 'vitest'; + +import { getRulesForVersionUpgrade, getStencilCoreImportMap, isStencilDecorator } from '../index'; +import { buildDistDocsRule } from '../rules/build-dist-docs'; +import { encapsulationApiRule } from '../rules/encapsulation-api'; +import { formAssociatedRule } from '../rules/form-associated'; +import { outputTargetRenamesRule } from '../rules/output-target-renames'; + +/** + * Helper to create a TypeScript source file from code string + */ +function createSourceFile(code: string): ts.SourceFile { + return ts.createSourceFile('test.tsx', code, ts.ScriptTarget.Latest, true); +} + +describe('migrations/index', () => { + describe('getStencilCoreImportMap', () => { + it('should return empty map when no @stencil/core import', () => { + const code = ` + import { something } from 'other-library'; + `; + const sourceFile = createSourceFile(code); + const importMap = getStencilCoreImportMap(sourceFile); + + expect(importMap.size).toBe(0); + }); + + it('should map non-aliased imports to themselves', () => { + const code = ` + import { Component, h, Prop } from '@stencil/core'; + `; + const sourceFile = createSourceFile(code); + const importMap = getStencilCoreImportMap(sourceFile); + + expect(importMap.get('Component')).toBe('Component'); + expect(importMap.get('h')).toBe('h'); + expect(importMap.get('Prop')).toBe('Prop'); + }); + + it('should map aliased imports to original names', () => { + const code = ` + import { Component as Cmp, Prop as Input } from '@stencil/core'; + `; + const sourceFile = createSourceFile(code); + const importMap = getStencilCoreImportMap(sourceFile); + + expect(importMap.get('Cmp')).toBe('Component'); + expect(importMap.get('Input')).toBe('Prop'); + // Original names should not be in the map + expect(importMap.has('Component')).toBe(false); + expect(importMap.has('Prop')).toBe(false); + }); + + it('should handle mixed aliased and non-aliased imports', () => { + const code = ` + import { Component as Cmp, h, Prop as Input, State } from '@stencil/core'; + `; + const sourceFile = createSourceFile(code); + const importMap = getStencilCoreImportMap(sourceFile); + + expect(importMap.get('Cmp')).toBe('Component'); + expect(importMap.get('h')).toBe('h'); + expect(importMap.get('Input')).toBe('Prop'); + expect(importMap.get('State')).toBe('State'); + }); + }); + + describe('isStencilDecorator', () => { + it('should return true for non-aliased decorator', () => { + const importMap = new Map([['Component', 'Component']]); + expect(isStencilDecorator('Component', 'Component', importMap)).toBe(true); + }); + + it('should return true for aliased decorator', () => { + const importMap = new Map([['Cmp', 'Component']]); + expect(isStencilDecorator('Cmp', 'Component', importMap)).toBe(true); + }); + + it('should return false for non-matching decorator', () => { + const importMap = new Map([['Component', 'Component']]); + expect(isStencilDecorator('SomeOther', 'Component', importMap)).toBe(false); + }); + + it('should return false for empty import map', () => { + const importMap = new Map(); + expect(isStencilDecorator('Component', 'Component', importMap)).toBe(false); + }); + }); + + describe('getRulesForVersionUpgrade', () => { + it('should return rules for 4.x to 5.x upgrade', () => { + const rules = getRulesForVersionUpgrade('4', '5'); + + expect(rules.length).toBeGreaterThan(0); + expect(rules.every((r) => r.fromVersion.startsWith('4'))).toBe(true); + expect(rules.every((r) => r.toVersion.startsWith('5'))).toBe(true); + }); + + it('should include encapsulation-api rule for v4 to v5', () => { + const rules = getRulesForVersionUpgrade('4', '5'); + const encapsulationRule = rules.find((r) => r.id === 'encapsulation-api'); + + expect(encapsulationRule).toBeDefined(); + expect(encapsulationRule!.name).toBe('Encapsulation API'); + }); + + it('should include form-associated rule for v4 to v5', () => { + const rules = getRulesForVersionUpgrade('4', '5'); + const formAssociatedRule = rules.find((r) => r.id === 'form-associated'); + + expect(formAssociatedRule).toBeDefined(); + expect(formAssociatedRule!.name).toBe('Form Associated'); + }); + + it('should return empty array for non-existent version upgrade', () => { + const rules = getRulesForVersionUpgrade('99', '100'); + + expect(rules).toEqual([]); + }); + + it('should return empty array for downgrade', () => { + const rules = getRulesForVersionUpgrade('5', '4'); + + expect(rules).toEqual([]); + }); + + it('should handle partial version matching', () => { + // Rules have fromVersion: '4.x', toVersion: '5.x' + // Should match when we pass just '4' and '5' + const rules = getRulesForVersionUpgrade('4', '5'); + + expect(rules.length).toBeGreaterThan(0); + }); + + it('should return encapsulation-api rule before form-associated rule', () => { + const rules = getRulesForVersionUpgrade('4', '5'); + const encapsulationIndex = rules.findIndex((r) => r.id === 'encapsulation-api'); + const formAssociatedIndex = rules.findIndex((r) => r.id === 'form-associated'); + + expect(encapsulationIndex).toBeGreaterThanOrEqual(0); + expect(formAssociatedIndex).toBeGreaterThanOrEqual(0); + expect(encapsulationIndex).toBeLessThan(formAssociatedIndex); + }); + }); + + describe('rule interaction: encapsulation-api + form-associated', () => { + /** + * These tests verify that when multiple migration rules act on the same + * @Component decorator, they work correctly in sequence. This simulates + * how task-migrate.ts applies rules: detect → transform → re-parse → next rule. + */ + + it('should correctly migrate component with both shadow and formAssociated', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-form', + shadow: true, + formAssociated: true +}) +export class MyForm {}`; + + // Step 1: Apply encapsulation-api rule first + let sourceFile = createSourceFile(code); + let matches = encapsulationApiRule.detect(sourceFile); + expect(matches).toHaveLength(1); + + let result = encapsulationApiRule.transform(sourceFile, matches); + expect(result).toContain("encapsulation: { type: 'shadow' }"); + expect(result).not.toContain('shadow: true'); + // formAssociated should still be there + expect(result).toContain('formAssociated: true'); + + // Step 2: Re-parse and apply form-associated rule + sourceFile = createSourceFile(result); + matches = formAssociatedRule.detect(sourceFile); + expect(matches).toHaveLength(1); + + result = formAssociatedRule.transform(sourceFile, matches); + expect(result).toContain("encapsulation: { type: 'shadow' }"); + expect(result).toContain('@AttachInternals()'); + expect(result).toContain('internals: ElementInternals'); + expect(result).not.toContain('formAssociated'); + + // Verify the result is valid TypeScript + const finalSourceFile = createSourceFile(result); + expect(finalSourceFile.statements.length).toBeGreaterThan(0); + }); + + it('should handle shadow with delegatesFocus and formAssociated', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-form', + shadow: { delegatesFocus: true }, + formAssociated: true +}) +export class MyForm {}`; + + // Apply encapsulation-api rule + let sourceFile = createSourceFile(code); + let matches = encapsulationApiRule.detect(sourceFile); + let result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'shadow'"); + expect(result).toContain('delegatesFocus: true'); + + // Apply form-associated rule + sourceFile = createSourceFile(result); + matches = formAssociatedRule.detect(sourceFile); + result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain('@AttachInternals()'); + expect(result).not.toContain('formAssociated'); + // Encapsulation should be preserved + expect(result).toContain("type: 'shadow'"); + expect(result).toContain('delegatesFocus: true'); + }); + + it('should handle scoped and formAssociated', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'my-form', + scoped: true, + formAssociated: true +}) +export class MyForm {}`; + + // Apply encapsulation-api rule + let sourceFile = createSourceFile(code); + let matches = encapsulationApiRule.detect(sourceFile); + let result = encapsulationApiRule.transform(sourceFile, matches); + + expect(result).toContain("encapsulation: { type: 'scoped' }"); + expect(result).not.toContain('scoped: true'); + + // Apply form-associated rule + sourceFile = createSourceFile(result); + matches = formAssociatedRule.detect(sourceFile); + result = formAssociatedRule.transform(sourceFile, matches); + + expect(result).toContain('@AttachInternals()'); + expect(result).not.toContain('formAssociated'); + expect(result).toContain("encapsulation: { type: 'scoped' }"); + }); + + it('should handle multiple components with different combinations', () => { + const code = `import { Component } from '@stencil/core'; +@Component({ + tag: 'cmp-a', + shadow: true +}) +export class CmpA {} + +@Component({ + tag: 'cmp-b', + formAssociated: true +}) +export class CmpB {} + +@Component({ + tag: 'cmp-c', + shadow: true, + formAssociated: true +}) +export class CmpC {}`; + + // Apply encapsulation-api rule (should find 2 matches: cmp-a and cmp-c) + let sourceFile = createSourceFile(code); + let matches = encapsulationApiRule.detect(sourceFile); + expect(matches).toHaveLength(2); + + let result = encapsulationApiRule.transform(sourceFile, matches); + + // Apply form-associated rule (should find 2 matches: cmp-b and cmp-c) + sourceFile = createSourceFile(result); + matches = formAssociatedRule.detect(sourceFile); + expect(matches).toHaveLength(2); + + result = formAssociatedRule.transform(sourceFile, matches); + + // Verify all transformations are correct + // CmpA: only shadow → encapsulation + // CmpB: only formAssociated → @AttachInternals + // CmpC: both shadow and formAssociated → both transformations + expect(result).not.toContain('shadow: true'); + expect(result).not.toContain('formAssociated'); + expect((result.match(/encapsulation:/g) || []).length).toBe(2); // CmpA and CmpC + expect((result.match(/@AttachInternals\(\)/g) || []).length).toBe(2); // CmpB and CmpC + }); + + it('should preserve component options order and formatting', () => { + const code = `import { Component, h } from '@stencil/core'; +@Component({ + tag: 'my-form', + styleUrl: 'my-form.css', + shadow: true, + formAssociated: true, + assetsDirs: ['assets'] +}) +export class MyForm { + render() { + return
Hello
; + } +}`; + + // Apply both rules sequentially + let sourceFile = createSourceFile(code); + let matches = encapsulationApiRule.detect(sourceFile); + let result = encapsulationApiRule.transform(sourceFile, matches); + + sourceFile = createSourceFile(result); + matches = formAssociatedRule.detect(sourceFile); + result = formAssociatedRule.transform(sourceFile, matches); + + // All original options should be preserved + expect(result).toContain("tag: 'my-form'"); + expect(result).toContain("styleUrl: 'my-form.css'"); + expect(result).toContain("assetsDirs: ['assets']"); + expect(result).toContain("encapsulation: { type: 'shadow' }"); + expect(result).toContain('@AttachInternals()'); + expect(result).toContain('render()'); + }); + }); + + describe('rule interaction: build-dist-docs + output-target-renames', () => { + /** + * These tests verify that when both rules act on the same stencil.config.ts file, + * they work correctly in sequence. The order is critical: + * - build-dist-docs runs FIRST (looks for 'dist', 'dist-custom-elements', etc.) + * - output-target-renames runs SECOND (renames 'dist' → 'loader-bundle', etc.) + */ + + it('should return build-dist-docs rule before output-target-renames rule', () => { + const rules = getRulesForVersionUpgrade('4', '5'); + const buildDistDocsIndex = rules.findIndex((r) => r.id === 'build-dist-docs'); + const outputTargetRenamesIndex = rules.findIndex((r) => r.id === 'output-target-renames'); + + expect(buildDistDocsIndex).toBeGreaterThanOrEqual(0); + expect(outputTargetRenamesIndex).toBeGreaterThanOrEqual(0); + expect(buildDistDocsIndex).toBeLessThan(outputTargetRenamesIndex); + }); + + it('should add skipInDev to dist targets then rename them', () => { + const code = `export const config = { + buildDist: true, + outputTargets: [ + { type: 'dist', dir: 'dist' }, + { type: 'dist-custom-elements', dir: 'components' }, + ], +};`; + + // Step 1: Apply build-dist-docs rule first + let sourceFile = createSourceFile(code); + let matches = buildDistDocsRule.detect(sourceFile); + expect(matches).toHaveLength(1); // buildDist: true + + let result = buildDistDocsRule.transform(sourceFile, matches); + expect(result).not.toContain('buildDist'); + expect(result).toContain('skipInDev: false'); // Added to dist targets + // Types should still be old names at this point + expect(result).toContain("type: 'dist'"); + expect(result).toContain("type: 'dist-custom-elements'"); + + // Step 2: Re-parse and apply output-target-renames rule + sourceFile = createSourceFile(result); + matches = outputTargetRenamesRule.detect(sourceFile); + expect(matches).toHaveLength(2); // dist and dist-custom-elements + + result = outputTargetRenamesRule.transform(sourceFile, matches); + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'standalone'"); + expect(result).not.toContain("type: 'dist'"); + expect(result).not.toContain("type: 'dist-custom-elements'"); + // skipInDev should still be there + expect(result).toContain('skipInDev: false'); + + // Verify the result is valid TypeScript + const finalSourceFile = createSourceFile(result); + expect(finalSourceFile.statements.length).toBeGreaterThan(0); + }); + + it('should handle buildDist: true with collectionDir and typesDir extraction', () => { + const code = `export const config = { + buildDist: true, + outputTargets: [ + { + type: 'dist', + dir: 'dist', + collectionDir: 'collection', + typesDir: 'types', + }, + ], +};`; + + // Apply build-dist-docs rule + let sourceFile = createSourceFile(code); + let matches = buildDistDocsRule.detect(sourceFile); + let result = buildDistDocsRule.transform(sourceFile, matches); + + expect(result).not.toContain('buildDist'); + expect(result).toContain('skipInDev: false'); + + // Apply output-target-renames rule + sourceFile = createSourceFile(result); + matches = outputTargetRenamesRule.detect(sourceFile); + result = outputTargetRenamesRule.transform(sourceFile, matches); + + // Should have renamed and extracted + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'stencil-rebundle'"); + expect(result).toContain("type: 'types'"); + expect(result).not.toContain('collectionDir'); + expect(result).not.toContain('typesDir'); + expect(result).toContain('skipInDev: false'); + }); + + it('should handle buildDocs: true with docs output targets', () => { + const code = `export const config = { + buildDocs: true, + outputTargets: [ + { type: 'dist' }, + { type: 'docs-readme' }, + { type: 'docs-json', file: 'docs.json' }, + ], +};`; + + // Apply build-dist-docs rule + let sourceFile = createSourceFile(code); + let matches = buildDistDocsRule.detect(sourceFile); + expect(matches).toHaveLength(1); // buildDocs: true + + let result = buildDistDocsRule.transform(sourceFile, matches); + expect(result).not.toContain('buildDocs'); + // skipInDev should be added to docs targets + expect((result.match(/skipInDev: false/g) || []).length).toBe(2); // docs-readme and docs-json + + // Apply output-target-renames rule + sourceFile = createSourceFile(result); + matches = outputTargetRenamesRule.detect(sourceFile); + expect(matches).toHaveLength(1); // Only 'dist' gets renamed, docs-* stay the same + + result = outputTargetRenamesRule.transform(sourceFile, matches); + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'docs-readme'"); // Not renamed + expect(result).toContain("type: 'docs-json'"); // Not renamed + }); + + it('should handle complex config with both buildDist and buildDocs', () => { + const code = `export const config = { + namespace: 'my-lib', + buildDist: true, + buildDocs: true, + outputTargets: [ + { + type: 'dist', + dir: 'dist', + collectionDir: 'collection', + }, + { + type: 'dist-custom-elements', + isPrimaryPackageOutputTarget: true, + generateTypeDeclarations: true, + }, + { type: 'dist-hydrate-script' }, + { type: 'docs-readme' }, + ], +};`; + + // Apply build-dist-docs rule first + let sourceFile = createSourceFile(code); + let matches = buildDistDocsRule.detect(sourceFile); + expect(matches).toHaveLength(2); // buildDist and buildDocs + + let result = buildDistDocsRule.transform(sourceFile, matches); + expect(result).not.toContain('buildDist'); + expect(result).not.toContain('buildDocs'); + + // Apply output-target-renames rule + sourceFile = createSourceFile(result); + matches = outputTargetRenamesRule.detect(sourceFile); + result = outputTargetRenamesRule.transform(sourceFile, matches); + + // All renames should be applied + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("type: 'ssr'"); + expect(result).toContain("type: 'stencil-rebundle'"); // Extracted from collectionDir + expect(result).toContain("type: 'docs-readme'"); // Not renamed + + // Removed properties + expect(result).not.toContain('collectionDir'); + expect(result).not.toContain('isPrimaryPackageOutputTarget'); + expect(result).not.toContain('generateTypeDeclarations'); + + // Preserved properties + expect(result).toContain("namespace: 'my-lib'"); + }); + + it('should handle config that already uses new names with buildDist', () => { + // Edge case: user has partially migrated (new names) but still has buildDist + const code = `export const config = { + buildDist: true, + outputTargets: [ + { type: 'loader-bundle' }, + { type: 'standalone' }, + ], +};`; + + // build-dist-docs recognizes BOTH old and new names + let sourceFile = createSourceFile(code); + let matches = buildDistDocsRule.detect(sourceFile); + expect(matches).toHaveLength(1); // buildDist: true + + let result = buildDistDocsRule.transform(sourceFile, matches); + expect(result).not.toContain('buildDist'); + // skipInDev should be added to new names too + expect((result.match(/skipInDev: false/g) || []).length).toBe(2); + + // output-target-renames has nothing to do + sourceFile = createSourceFile(result); + matches = outputTargetRenamesRule.detect(sourceFile); + expect(matches).toHaveLength(0); // Already using new names + }); + }); +}); diff --git a/packages/cli/src/migrations/_test_/output-target-renames.spec.ts b/packages/cli/src/migrations/_test_/output-target-renames.spec.ts new file mode 100644 index 00000000000..33fa16c8ec6 --- /dev/null +++ b/packages/cli/src/migrations/_test_/output-target-renames.spec.ts @@ -0,0 +1,457 @@ +import ts from 'typescript'; +import { describe, it, expect } from 'vitest'; + +import { outputTargetRenamesRule } from '../rules/output-target-renames'; + +describe('output-target-renames migration', () => { + describe('detect', () => { + it('should detect old output target type names', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { type: 'dist' }, + { type: 'dist-custom-elements' }, + { type: 'dist-hydrate-script' }, + { type: 'dist-collection' }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + expect(matches).toHaveLength(4); + expect(matches[0].message).toContain("'dist' → 'loader-bundle'"); + expect(matches[1].message).toContain("'dist-custom-elements' → 'standalone'"); + expect(matches[2].message).toContain("'dist-hydrate-script' → 'ssr'"); + expect(matches[3].message).toContain("'dist-collection' → 'stencil-rebundle'"); + }); + + it('should detect collectionDir property', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { + type: 'dist', + collectionDir: 'my-collection', + }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + expect(matches.length).toBeGreaterThan(0); + const collectionMatch = matches.find((m) => m.message.includes('collectionDir')); + expect(collectionMatch).toBeDefined(); + expect(collectionMatch?.message).toContain("'stencil-rebundle'"); + }); + + it('should detect typesDir property', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { + type: 'dist', + typesDir: 'my-types', + }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + expect(matches.length).toBeGreaterThan(0); + const typesMatch = matches.find((m) => m.message.includes('typesDir')); + expect(typesMatch).toBeDefined(); + expect(typesMatch?.message).toContain("'types'"); + }); + + it('should not detect new output target names', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { type: 'loader-bundle' }, + { type: 'standalone' }, + { type: 'ssr' }, + { type: 'stencil-rebundle' }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + expect(matches).toHaveLength(0); + }); + + it('should detect isPrimaryPackageOutputTarget property', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { + type: 'dist-custom-elements', + isPrimaryPackageOutputTarget: true, + }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + const primaryMatch = matches.find((m) => m.message.includes('isPrimaryPackageOutputTarget')); + expect(primaryMatch).toBeDefined(); + expect(primaryMatch?.message).toContain('removed in v5'); + expect(primaryMatch?.message).toContain('auto-detects'); + }); + + it('should detect generateTypeDeclarations property', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { + type: 'dist-custom-elements', + generateTypeDeclarations: true, + }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + const typeDecsMatch = matches.find((m) => m.message.includes('generateTypeDeclarations')); + expect(typeDecsMatch).toBeDefined(); + expect(typeDecsMatch?.message).toContain('removed in v5'); + expect(typeDecsMatch?.message).toContain('types'); + }); + + it('should detect esmLoaderPath property', () => { + const source = ` +export const config: Config = { + outputTargets: [ + { + type: 'dist', + esmLoaderPath: './my-loader', + }, + ], +}; +`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + + const loaderPathMatch = matches.find((m) => m.message.includes('esmLoaderPath')); + expect(loaderPathMatch).toBeDefined(); + expect(loaderPathMatch?.message).toContain('loaderPath'); + }); + }); + + describe('transform', () => { + it('should rename dist to loader-bundle', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'dist', dir: 'my-dist' }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).not.toContain("type: 'dist'"); + }); + + it('should rename dist-custom-elements to standalone', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'dist-custom-elements' }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'standalone'"); + expect(result).not.toContain("type: 'dist-custom-elements'"); + }); + + it('should rename dist-hydrate-script to ssr', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'dist-hydrate-script' }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'ssr'"); + expect(result).not.toContain("type: 'dist-hydrate-script'"); + }); + + it('should rename multiple output targets', () => { + const source = `export const config: Config = { + outputTargets: [ + { type: 'dist' }, + { type: 'dist-custom-elements' }, + { type: 'dist-hydrate-script' }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("type: 'ssr'"); + expect(result).not.toContain("type: 'dist'"); + expect(result).not.toContain("type: 'dist-custom-elements'"); + expect(result).not.toContain("type: 'dist-hydrate-script'"); + }); + + it('should extract collectionDir to separate output target', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist', + collectionDir: 'my-collection', + dir: 'my-dist', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).not.toContain('collectionDir'); + expect(result).toContain("type: 'stencil-rebundle'"); + expect(result).toContain("dir: 'my-collection'"); + }); + + it('should extract typesDir to separate output target', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist', + typesDir: 'my-types', + dir: 'my-dist', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).not.toContain('typesDir'); + expect(result).toContain("type: 'types'"); + expect(result).toContain("dir: 'my-types'"); + }); + + it('should extract both collectionDir and typesDir', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist', + collectionDir: 'my-collection', + typesDir: 'my-types', + dir: 'my-dist', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).not.toContain('collectionDir'); + expect(result).not.toContain('typesDir'); + expect(result).toContain("type: 'stencil-rebundle'"); + expect(result).toContain("type: 'types'"); + }); + + it('should preserve other properties in output target', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist', + dir: 'my-dist', + buildDir: 'build', + empty: false, + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("dir: 'my-dist'"); + expect(result).toContain("buildDir: 'build'"); + expect(result).toContain('empty: false'); + }); + + it('should handle complex real-world config', () => { + const source = `export const config: Config = { + namespace: 'my-lib', + outputTargets: [ + { + type: 'dist', + dir: 'dist', + collectionDir: 'collection', + typesDir: 'types', + }, + { + type: 'dist-custom-elements', + dir: 'dist/components', + }, + { + type: 'dist-hydrate-script', + dir: 'dist/hydrate', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("type: 'ssr'"); + expect(result).toContain("type: 'stencil-rebundle'"); + expect(result).toContain("type: 'types'"); + expect(result).not.toContain('collectionDir'); + expect(result).not.toContain('typesDir'); + expect(result).not.toContain("type: 'dist'"); + expect(result).not.toContain("type: 'dist-custom-elements'"); + expect(result).not.toContain("type: 'dist-hydrate-script'"); + }); + + it('should remove isPrimaryPackageOutputTarget property', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist-custom-elements', + isPrimaryPackageOutputTarget: true, + dir: 'dist/components', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("dir: 'dist/components'"); + expect(result).not.toContain('isPrimaryPackageOutputTarget'); + }); + + it('should remove generateTypeDeclarations property', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist-custom-elements', + generateTypeDeclarations: true, + dir: 'dist/components', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("dir: 'dist/components'"); + expect(result).not.toContain('generateTypeDeclarations'); + }); + + it('should remove both isPrimaryPackageOutputTarget and generateTypeDeclarations', () => { + const source = `export const config: Config = { + outputTargets: [ + { + type: 'dist-custom-elements', + isPrimaryPackageOutputTarget: true, + generateTypeDeclarations: false, + dir: 'dist/components', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("dir: 'dist/components'"); + expect(result).not.toContain('isPrimaryPackageOutputTarget'); + expect(result).not.toContain('generateTypeDeclarations'); + }); + + it('should handle complex config with all removed properties', () => { + const source = `export const config: Config = { + namespace: 'my-lib', + outputTargets: [ + { + type: 'dist', + dir: 'dist', + collectionDir: 'collection', + typesDir: 'types', + }, + { + type: 'dist-custom-elements', + dir: 'dist/components', + isPrimaryPackageOutputTarget: true, + generateTypeDeclarations: true, + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + expect(result).toContain("type: 'standalone'"); + expect(result).toContain("type: 'stencil-rebundle'"); + expect(result).toContain("type: 'types'"); + expect(result).not.toContain('collectionDir'); + expect(result).not.toContain('typesDir'); + expect(result).not.toContain('isPrimaryPackageOutputTarget'); + expect(result).not.toContain('generateTypeDeclarations'); + }); + + it('should rename esmLoaderPath to loaderPath and adjust path depth', () => { + const source1 = `export const config: Config = { + outputTargets: [ + { + type: 'dist', + esmLoaderPath: '../loader', + dir: 'my-dist', + }, + ], +};`; + const sourceFile = ts.createSourceFile('test.ts', source1, ts.ScriptTarget.Latest, true); + const matches = outputTargetRenamesRule.detect(sourceFile); + const result = outputTargetRenamesRule.transform(sourceFile, matches); + + expect(result).toContain("type: 'loader-bundle'"); + // Path is adjusted: '../loader' -> '../../loader' (prepend ../ for deeper default dir) + expect(result).toContain("loaderPath: '../../loader'"); + expect(result).not.toContain('esmLoaderPath'); + + const source2 = `export const config: Config = { + outputTargets: [ + { + type: 'dist', + esmLoaderPath: 'my-loader', + dir: 'my-dist', + }, + ], +};`; + const sourceFile2 = ts.createSourceFile('test.ts', source2, ts.ScriptTarget.Latest, true); + const matches2 = outputTargetRenamesRule.detect(sourceFile2); + const result2 = outputTargetRenamesRule.transform(sourceFile2, matches2); + + expect(result2).toContain("type: 'loader-bundle'"); + // Path is adjusted: 'my-loader' -> '../my-loader' (prepend ../ for deeper default dir) + expect(result2).toContain("loaderPath: '../my-loader'"); + expect(result2).not.toContain('esmLoaderPath'); + }); + }); +}); diff --git a/packages/cli/src/migrations/index.ts b/packages/cli/src/migrations/index.ts new file mode 100644 index 00000000000..977ec7d0fc5 --- /dev/null +++ b/packages/cli/src/migrations/index.ts @@ -0,0 +1,134 @@ +import ts from 'typescript'; + +import { buildDistDocsRule } from './rules/build-dist-docs'; +import { devModeRule } from './rules/dev-mode'; +import { encapsulationApiRule } from './rules/encapsulation-api'; +import { formAssociatedRule } from './rules/form-associated'; +import { globalStyleInjectRule } from './rules/global-style-inject'; +import { outputTargetRenamesRule } from './rules/output-target-renames'; + +/** + * Build a map of local import names to their original names from @stencil/core. + * Handles aliased imports like `import { Component as Cmp } from '@stencil/core'`. + * Also handles multiple imports from @stencil/core (e.g., separate type and value imports). + * + * @param sourceFile The TypeScript source file to analyze + * @returns Map where keys are local names and values are original imported names + */ +export const getStencilCoreImportMap = (sourceFile: ts.SourceFile): Map => { + const importMap = new Map(); + + for (const statement of sourceFile.statements) { + if ( + ts.isImportDeclaration(statement) && + ts.isStringLiteral(statement.moduleSpecifier) && + statement.moduleSpecifier.text === '@stencil/core' + ) { + const namedBindings = statement.importClause?.namedBindings; + if (namedBindings && ts.isNamedImports(namedBindings)) { + for (const element of namedBindings.elements) { + // element.name is the local name (what's used in code) + // element.propertyName is the original name (if aliased), otherwise undefined + const localName = element.name.text; + const originalName = element.propertyName?.text ?? element.name.text; + importMap.set(localName, originalName); + } + } + // Don't break - there may be multiple imports from @stencil/core + // (e.g., a type-only import and a value import) + } + } + + return importMap; +}; + +/** + * Check if a decorator identifier refers to a specific @stencil/core export. + * Handles aliased imports like `import { Component as Cmp } from '@stencil/core'`. + * + * @param decoratorName The identifier used in the decorator + * @param expectedOriginalName The original export name (e.g., 'Component') + * @param importMap The import map from getStencilCoreImportMap + * @returns True if the decorator refers to the expected export + */ +export const isStencilDecorator = ( + decoratorName: string, + expectedOriginalName: string, + importMap: Map, +): boolean => { + return importMap.get(decoratorName) === expectedOriginalName; +}; + +/** + * Represents a match found by a migration rule during detection. + */ +export interface MigrationMatch { + /** The AST node that matched */ + node: ts.Node; + /** Human-readable message describing what needs to be migrated */ + message: string; + /** Line number in the source file (1-indexed) */ + line: number; + /** Column number in the source file (1-indexed) */ + column: number; +} + +/** + * Interface for pluggable migration rules. + * Each rule can detect deprecated patterns and transform them to the new API. + */ +export interface MigrationRule { + /** Unique identifier for the rule */ + id: string; + /** Human-readable name */ + name: string; + /** Description of what this rule migrates */ + description: string; + /** Source version (e.g., '4.x') */ + fromVersion: string; + /** Target version (e.g., '5.x') */ + toVersion: string; + + /** + * Detect if this rule applies to a source file. + * @param sourceFile The TypeScript source file to check + * @returns Array of matches found, empty if rule doesn't apply + */ + detect(sourceFile: ts.SourceFile): MigrationMatch[]; + + /** + * Apply the transformation to a source file. + * @param sourceFile The TypeScript source file to transform + * @param matches The matches found during detection + * @returns The transformed source code as a string + */ + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string; +} + +/** + * Registry of all available migration rules. + * Rules are applied in order, so add new rules at the end. + */ +const migrationRules: MigrationRule[] = [ + encapsulationApiRule, + formAssociatedRule, + buildDistDocsRule, + outputTargetRenamesRule, + devModeRule, + globalStyleInjectRule, +]; + +/** + * Get all migration rules for a specific version upgrade. + * @param fromVersion Source version (e.g., '4') + * @param toVersion Target version (e.g., '5') + * @returns Filtered list of applicable rules + */ +export const getRulesForVersionUpgrade = ( + fromVersion: string, + toVersion: string, +): MigrationRule[] => { + return migrationRules.filter( + (rule) => rule.fromVersion.startsWith(fromVersion) && rule.toVersion.startsWith(toVersion), + ); +}; diff --git a/packages/cli/src/migrations/rules/build-dist-docs.ts b/packages/cli/src/migrations/rules/build-dist-docs.ts new file mode 100644 index 00000000000..afed89ff57c --- /dev/null +++ b/packages/cli/src/migrations/rules/build-dist-docs.ts @@ -0,0 +1,205 @@ +import ts from 'typescript'; + +import type { MigrationMatch, MigrationRule } from '../index'; + +/** + * Migration rule for buildDist and buildDocs removal. + * + * In Stencil v5, these global config options are replaced with per-output-target + * `skipInDev` property. This migration: + * - Detects `buildDist` or `buildDocs` in stencil.config.ts + * - Removes the property + * - For `buildDist: true` or `buildDocs: true`, adds `skipInDev: false` to relevant output targets + */ +export const buildDistDocsRule: MigrationRule = { + id: 'build-dist-docs', + name: 'buildDist/buildDocs Removal', + description: 'Remove deprecated buildDist and buildDocs config options', + fromVersion: '4.x', + toVersion: '5.x', + + detect(sourceFile: ts.SourceFile): MigrationMatch[] { + const matches: MigrationMatch[] = []; + + const visit = (node: ts.Node) => { + // Look for property assignments: buildDist: ... or buildDocs: ... + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) { + const propName = node.name.text; + if (propName === 'buildDist' || propName === 'buildDocs') { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + const isTrue = node.initializer.kind === ts.SyntaxKind.TrueKeyword; + matches.push({ + node, + message: `Deprecated '${propName}' found${isTrue ? ' (set to true)' : ''} - will be removed and skipInDev added to output targets`, + line: line + 1, + column: character + 1, + }); + } + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return matches; + }, + + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string { + if (matches.length === 0) { + return sourceFile.getFullText(); + } + + let text = sourceFile.getFullText(); + + // Determine which output target types need skipInDev: false + const targetTypesNeedingSkipInDev: string[] = []; + + for (const match of matches) { + const prop = match.node as ts.PropertyAssignment; + const propName = (prop.name as ts.Identifier).text; + const isTrue = prop.initializer.kind === ts.SyntaxKind.TrueKeyword; + + if (isTrue) { + if (propName === 'buildDist') { + // Include both old and new names to handle partially migrated configs + targetTypesNeedingSkipInDev.push( + // Old names (v4) + 'dist', + 'dist-custom-elements', + 'dist-hydrate-script', + // New names (v5) - in case user already migrated type names but still has buildDist + 'loader-bundle', + 'standalone', + 'ssr', + ); + } else if (propName === 'buildDocs') { + targetTypesNeedingSkipInDev.push( + 'docs-readme', + 'docs-json', + 'docs-custom', + 'docs-vscode', + 'docs-custom-elements-manifest', + ); + } + } + } + + // Find output targets that need skipInDev: false added + const outputTargetsToModify: ts.ObjectLiteralExpression[] = []; + + const findOutputTargets = (node: ts.Node) => { + // Look for outputTargets: [...] + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'outputTargets' && + ts.isArrayLiteralExpression(node.initializer) + ) { + for (const element of node.initializer.elements) { + if (ts.isObjectLiteralExpression(element)) { + // Check if this output target has a type that needs skipInDev + const typeProp = element.properties.find( + (p) => + ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'type', + ) as ts.PropertyAssignment | undefined; + + if (typeProp && ts.isStringLiteral(typeProp.initializer)) { + const targetType = typeProp.initializer.text; + if (targetTypesNeedingSkipInDev.includes(targetType)) { + // Check if skipInDev is already set + const hasSkipInDev = element.properties.some( + (p) => + ts.isPropertyAssignment(p) && + ts.isIdentifier(p.name) && + p.name.text === 'skipInDev', + ); + if (!hasSkipInDev) { + outputTargetsToModify.push(element); + } + } + } + } + } + } + ts.forEachChild(node, findOutputTargets); + }; + + findOutputTargets(sourceFile); + + // Sort all modifications by position (descending) to preserve positions + const allModifications: Array<{ + type: 'remove' | 'addSkipInDev'; + start: number; + end: number; + node: ts.Node; + }> = []; + + // Add removals for buildDist/buildDocs + for (const match of matches) { + const prop = match.node as ts.PropertyAssignment; + const start = prop.getStart(); + let end = prop.getEnd(); + + // Handle trailing comma + const afterProp = text.slice(end).match(/^\s*,/); + if (afterProp) { + end = end + afterProp[0].length; + } + + allModifications.push({ type: 'remove', start, end, node: prop }); + } + + // Add skipInDev insertions + for (const target of outputTargetsToModify) { + // Find the last property in the object to insert after + const lastProp = target.properties[target.properties.length - 1]; + if (lastProp) { + allModifications.push({ + type: 'addSkipInDev', + start: lastProp.getEnd(), + end: lastProp.getEnd(), + node: target, + }); + } + } + + // Sort by position descending + allModifications.sort((a, b) => b.start - a.start); + + // Apply modifications + for (const mod of allModifications) { + if (mod.type === 'remove') { + text = text.slice(0, mod.start) + text.slice(mod.end); + } else if (mod.type === 'addSkipInDev') { + // Detect indentation from the object + const target = mod.node as ts.ObjectLiteralExpression; + const firstProp = target.properties[0]; + let indent = ' '; + if (firstProp) { + const propStart = firstProp.getStart(); + const lineStart = text.lastIndexOf('\n', propStart) + 1; + const leadingWhitespace = text.slice(lineStart, propStart); + if (/^\s+$/.test(leadingWhitespace)) { + indent = leadingWhitespace; + } + } + + // Check if there's a trailing comma after the last property value + const afterLastProp = text.slice(mod.start).match(/^(\s*,)?/); + const hasTrailingComma = afterLastProp && afterLastProp[1]; + const skipLength = hasTrailingComma ? afterLastProp[0].length : 0; + + // Insert the new property, replacing any trailing comma + text = + text.slice(0, mod.start) + + `,\n${indent}skipInDev: false,` + + text.slice(mod.start + skipLength); + } + } + + // Clean up any empty lines left behind + text = text.replace(/,\s*\n\s*\n/g, ',\n'); + text = text.replace(/{\s*\n\s*\n/g, '{\n'); + + return text; + }, +}; diff --git a/packages/cli/src/migrations/rules/dev-mode.ts b/packages/cli/src/migrations/rules/dev-mode.ts new file mode 100644 index 00000000000..81cc2ed5d27 --- /dev/null +++ b/packages/cli/src/migrations/rules/dev-mode.ts @@ -0,0 +1,67 @@ +import ts from 'typescript'; + +import type { MigrationMatch, MigrationRule } from '../index'; + +/** + * Migration rule for `devMode` config option removal. + * + * In Stencil v5, `devMode` is no longer a user-settable config option in `stencil.config.ts`. + * Build mode is controlled exclusively by the `--dev` CLI flag (default is production). + * This migration removes `devMode` from the config object. + */ +export const devModeRule: MigrationRule = { + id: 'dev-mode', + name: 'devMode Config Removal', + description: 'Remove deprecated devMode config option - use --dev CLI flag instead', + fromVersion: '4.x', + toVersion: '5.x', + + detect(sourceFile: ts.SourceFile): MigrationMatch[] { + const matches: MigrationMatch[] = []; + + const visit = (node: ts.Node) => { + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) { + if (node.name.text === 'devMode') { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + matches.push({ + node, + message: `Deprecated 'devMode' config option found - build mode is now set via the --dev CLI flag (production is the default)`, + line: line + 1, + column: character + 1, + }); + } + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return matches; + }, + + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string { + if (matches.length === 0) { + return sourceFile.getFullText(); + } + + let text = sourceFile.getFullText(); + + // Remove matches in reverse order to preserve character offsets + for (const match of [...matches].reverse()) { + const node = match.node as ts.PropertyAssignment; + const start = node.getFullStart(); + const end = node.getEnd(); + + // Also consume a trailing comma if present + let removeEnd = end; + const afterNode = text.slice(end); + const trailingComma = afterNode.match(/^\s*,/); + if (trailingComma) { + removeEnd = end + trailingComma[0].length; + } + + text = text.slice(0, start) + text.slice(removeEnd); + } + + return text; + }, +}; diff --git a/packages/cli/src/migrations/rules/encapsulation-api.ts b/packages/cli/src/migrations/rules/encapsulation-api.ts new file mode 100644 index 00000000000..14eda18c5ec --- /dev/null +++ b/packages/cli/src/migrations/rules/encapsulation-api.ts @@ -0,0 +1,137 @@ +import ts from 'typescript'; + +import { + getStencilCoreImportMap, + isStencilDecorator, + type MigrationMatch, + type MigrationRule, +} from '../index'; + +/** + * Migration rule for the @Component encapsulation API change. + * + * Migrates: + * - `shadow: true` → `encapsulation: { type: 'shadow' }` + * - `shadow: { delegatesFocus: true }` → `encapsulation: { type: 'shadow', delegatesFocus: true }` + * - `scoped: true` → `encapsulation: { type: 'scoped' }` + */ +export const encapsulationApiRule: MigrationRule = { + id: 'encapsulation-api', + name: 'Encapsulation API', + description: 'Migrate shadow/scoped properties to new encapsulation API', + fromVersion: '4.x', + toVersion: '5.x', + + detect(sourceFile: ts.SourceFile): MigrationMatch[] { + const matches: MigrationMatch[] = []; + const importMap = getStencilCoreImportMap(sourceFile); + + const visit = (node: ts.Node) => { + // Look for @Component decorator (handles aliased imports like `Component as Cmp`) + if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) { + const decoratorName = node.expression.expression; + if ( + ts.isIdentifier(decoratorName) && + isStencilDecorator(decoratorName.text, 'Component', importMap) + ) { + const [arg] = node.expression.arguments; + if (arg && ts.isObjectLiteralExpression(arg)) { + // Check for deprecated properties + for (const prop of arg.properties) { + if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { + const propName = prop.name.text; + if (propName === 'shadow' || propName === 'scoped') { + const { line, character } = sourceFile.getLineAndCharacterOfPosition( + prop.getStart(), + ); + matches.push({ + node: prop, + message: `Deprecated '${propName}' property found - migrate to 'encapsulation' API`, + line: line + 1, + column: character + 1, + }); + } + } + } + } + } + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return matches; + }, + + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string { + if (matches.length === 0) { + return sourceFile.getFullText(); + } + + let text = sourceFile.getFullText(); + + // Process matches in reverse order to preserve positions + const sortedMatches = [...matches].sort((a, b) => { + const posA = (a.node as ts.Node).getStart(); + const posB = (b.node as ts.Node).getStart(); + return posB - posA; + }); + + for (const match of sortedMatches) { + const prop = match.node as ts.PropertyAssignment; + const propName = (prop.name as ts.Identifier).text; + const start = prop.getStart(); + const end = prop.getEnd(); + + let replacement: string; + + if (propName === 'shadow') { + if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) { + replacement = "encapsulation: { type: 'shadow' }"; + } else if (ts.isObjectLiteralExpression(prop.initializer)) { + // Extract options from the object + const options: string[] = []; + for (const innerProp of prop.initializer.properties) { + if (ts.isPropertyAssignment(innerProp) && ts.isIdentifier(innerProp.name)) { + const optName = innerProp.name.text; + const optValue = innerProp.initializer.getText(); + options.push(`${optName}: ${optValue}`); + } + } + if (options.length > 0) { + replacement = `encapsulation: { type: 'shadow', ${options.join(', ')} }`; + } else { + replacement = "encapsulation: { type: 'shadow' }"; + } + } else { + // shadow: false or other - just remove it + replacement = ''; + } + } else if (propName === 'scoped') { + if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) { + replacement = "encapsulation: { type: 'scoped' }"; + } else { + // scoped: false - just remove it + replacement = ''; + } + } else { + continue; + } + + // Handle trailing comma + let endPos = end; + const afterProp = text.slice(end).match(/^\s*,/); + if (afterProp && replacement === '') { + endPos = end + afterProp[0].length; + } else if (afterProp && replacement !== '') { + // Keep the comma + } else if (!afterProp && replacement !== '') { + // Check if there's a comma before this prop that we should handle + } + + text = text.slice(0, start) + replacement + text.slice(endPos); + } + + return text; + }, +}; diff --git a/packages/cli/src/migrations/rules/form-associated.ts b/packages/cli/src/migrations/rules/form-associated.ts new file mode 100644 index 00000000000..a1dee31ea4b --- /dev/null +++ b/packages/cli/src/migrations/rules/form-associated.ts @@ -0,0 +1,222 @@ +import ts from 'typescript'; + +import { + getStencilCoreImportMap, + isStencilDecorator, + type MigrationMatch, + type MigrationRule, +} from '../index'; + +interface FormAssociatedMatch extends MigrationMatch { + classBodyStart: number; + hasAttachInternals: boolean; + indent: string; + needsImport: boolean; + stencilImportEnd: number; +} + +/** + * Migration rule for formAssociated → @AttachInternals. + * + * Migrates: + * - `formAssociated: true` in @Component → Adds `@AttachInternals() internals: ElementInternals;` + */ +export const formAssociatedRule: MigrationRule = { + id: 'form-associated', + name: 'Form Associated', + description: 'Migrate formAssociated to @AttachInternals decorator', + fromVersion: '4.x', + toVersion: '5.x', + + detect(sourceFile: ts.SourceFile): MigrationMatch[] { + const matches: FormAssociatedMatch[] = []; + const importMap = getStencilCoreImportMap(sourceFile); + + // Check if AttachInternals is already imported (handles aliases) + let hasAttachInternalsImport = false; + let stencilImportEnd = 0; + + for (const statement of sourceFile.statements) { + if ( + ts.isImportDeclaration(statement) && + ts.isStringLiteral(statement.moduleSpecifier) && + statement.moduleSpecifier.text === '@stencil/core' + ) { + stencilImportEnd = statement.getEnd(); + // Check if any import resolves to AttachInternals + for (const [, originalName] of importMap) { + if (originalName === 'AttachInternals') { + hasAttachInternalsImport = true; + break; + } + } + break; + } + } + + const visit = (node: ts.Node) => { + // Look for class declarations with @Component decorator (handles aliased imports) + if (ts.isClassDeclaration(node)) { + const decorators = ts.getDecorators(node); + if (!decorators) { + ts.forEachChild(node, visit); + return; + } + + // Find @Component decorator (handles aliased imports like `Component as Cmp`) + let componentDecorator: ts.Decorator | undefined; + let formAssociatedProp: ts.PropertyAssignment | undefined; + + for (const decorator of decorators) { + if ( + ts.isCallExpression(decorator.expression) && + ts.isIdentifier(decorator.expression.expression) && + isStencilDecorator(decorator.expression.expression.text, 'Component', importMap) + ) { + componentDecorator = decorator; + const [arg] = decorator.expression.arguments; + if (arg && ts.isObjectLiteralExpression(arg)) { + for (const prop of arg.properties) { + if ( + ts.isPropertyAssignment(prop) && + ts.isIdentifier(prop.name) && + prop.name.text === 'formAssociated' + ) { + formAssociatedProp = prop; + break; + } + } + } + break; + } + } + + if (componentDecorator && formAssociatedProp) { + // Check if class already has @AttachInternals (handles aliased imports) + let hasAttachInternals = false; + for (const member of node.members) { + if (ts.isPropertyDeclaration(member) || ts.isMethodDeclaration(member)) { + const memberDecorators = ts.getDecorators(member); + if (memberDecorators) { + for (const d of memberDecorators) { + if ( + ts.isCallExpression(d.expression) && + ts.isIdentifier(d.expression.expression) && + isStencilDecorator(d.expression.expression.text, 'AttachInternals', importMap) + ) { + hasAttachInternals = true; + break; + } + } + } + } + } + + // Find class body start (opening brace) - must be AFTER class name/heritage clauses + // node.getText() includes decorators, so we can't just indexOf('{') + let searchStart = node.name ? node.name.getEnd() : node.getStart(sourceFile); + + // Skip past heritage clauses (extends/implements) + if (node.heritageClauses) { + for (const clause of node.heritageClauses) { + searchStart = Math.max(searchStart, clause.getEnd()); + } + } + + // Find the { after the class name/heritage + const textAfterName = sourceFile.getFullText().slice(searchStart); + const braceMatch = textAfterName.match(/\s*\{/); + if (!braceMatch) { + ts.forEachChild(node, visit); + return; + } + const classBodyStart = searchStart + braceMatch[0].length; + + // Determine indentation from first member or default + let indent = ' '; + if (node.members.length > 0) { + const firstMember = node.members[0]; + const memberStart = firstMember.getStart(sourceFile); + const textBefore = sourceFile.getFullText().slice(classBodyStart, memberStart); + const indentMatch = textBefore.match(/\n(\s+)/); + if (indentMatch) { + indent = indentMatch[1]; + } + } + + const { line, character } = sourceFile.getLineAndCharacterOfPosition( + formAssociatedProp.getStart(), + ); + + matches.push({ + node: formAssociatedProp, + message: hasAttachInternals + ? "Remove 'formAssociated' (already has @AttachInternals)" + : "Migrate 'formAssociated' to @AttachInternals decorator", + line: line + 1, + column: character + 1, + classBodyStart, + hasAttachInternals, + indent, + needsImport: !hasAttachInternalsImport && !hasAttachInternals, + stencilImportEnd, + }); + } + } + + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return matches; + }, + + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string { + if (matches.length === 0) { + return sourceFile.getFullText(); + } + + let text = sourceFile.getFullText(); + const typedMatches = matches as FormAssociatedMatch[]; + + // Sort by position descending (process from end to start) + const sortedMatches = [...typedMatches].sort((a, b) => { + return b.classBodyStart - a.classBodyStart; + }); + + for (const match of sortedMatches) { + // First, add @AttachInternals if needed (do this first since it's later in file) + if (!match.hasAttachInternals) { + const newMember = `\n${match.indent}@AttachInternals() internals: ElementInternals;\n`; + text = text.slice(0, match.classBodyStart) + newMember + text.slice(match.classBodyStart); + } + + // Then remove formAssociated property (earlier in file, so positions still valid) + const prop = match.node as ts.PropertyAssignment; + const start = prop.getStart(); + let end = prop.getEnd(); + + // Handle trailing comma + const afterProp = text.slice(end).match(/^\s*,/); + if (afterProp) { + end = end + afterProp[0].length; + } + + text = text.slice(0, start) + text.slice(end); + } + + // Add AttachInternals to import if needed (only once, check first match) + const firstMatch = typedMatches[0]; + if (firstMatch?.needsImport && firstMatch.stencilImportEnd > 0) { + // Find the import statement and add AttachInternals to it + const importMatch = text.match(/import\s*\{([^}]*)\}\s*from\s*['"]@stencil\/core['"]/); + if (importMatch) { + const existingImports = importMatch[1]; + const newImports = existingImports.trimEnd() + ', AttachInternals'; + text = text.replace(importMatch[0], `import {${newImports}} from '@stencil/core'`); + } + } + + return text; + }, +}; diff --git a/packages/cli/src/migrations/rules/global-style-inject.ts b/packages/cli/src/migrations/rules/global-style-inject.ts new file mode 100644 index 00000000000..08ebfd91716 --- /dev/null +++ b/packages/cli/src/migrations/rules/global-style-inject.ts @@ -0,0 +1,246 @@ +import ts from 'typescript'; + +import type { MigrationMatch, MigrationRule } from '../index'; + +/** + * Migration rule for `extras.addGlobalStyleToComponents` → `global-style` output target `inject`. + * + * In v5, the `addGlobalStyleToComponents` extras option is removed. Instead, configure + * the `inject` option on the `global-style` output target. + * + * Migration mapping: + * - `addGlobalStyleToComponents: true` → `inject: 'all'` + * - `addGlobalStyleToComponents: 'client'` → `inject: 'client'` + * - `addGlobalStyleToComponents: false` → omit (default is 'none') + * + * Note: This migration only acts on explicitly set `addGlobalStyleToComponents`. + * Users who relied on the implicit v4 default ('client') will need to manually + * add `inject: 'client'` to their global-style output target if desired. + */ +export const globalStyleInjectRule: MigrationRule = { + id: 'global-style-inject', + name: 'Global Style Inject Migration', + description: + 'Migrate extras.addGlobalStyleToComponents to global-style output target inject option', + fromVersion: '4.x', + toVersion: '5.x', + + detect(sourceFile: ts.SourceFile): MigrationMatch[] { + const matches: MigrationMatch[] = []; + + const visit = (node: ts.Node) => { + // Look for extras.addGlobalStyleToComponents + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'addGlobalStyleToComponents' + ) { + // Check if this is inside an 'extras' object + const parent = node.parent; + if (ts.isObjectLiteralExpression(parent)) { + const grandparent = parent.parent; + if ( + ts.isPropertyAssignment(grandparent) && + ts.isIdentifier(grandparent.name) && + grandparent.name.text === 'extras' + ) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + matches.push({ + node, + message: `extras.addGlobalStyleToComponents is removed. Use 'inject' on global-style output target instead.`, + line: line + 1, + column: character + 1, + }); + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + + return matches; + }, + + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string { + if (matches.length === 0) { + return sourceFile.getFullText(); + } + + let text = sourceFile.getFullText(); + + // Get the matched node (already validated as PropertyAssignment in extras) + const matchNode = matches[0].node; + if (!ts.isPropertyAssignment(matchNode)) { + return text; + } + + // Determine inject value from the node's initializer + let injectValue: 'all' | 'client' | null = null; + if (matchNode.initializer.kind === ts.SyntaxKind.TrueKeyword) { + injectValue = 'all'; + } else if ( + ts.isStringLiteral(matchNode.initializer) && + matchNode.initializer.text === 'client' + ) { + injectValue = 'client'; + } + // false or other values → null (default 'none', no injection needed) + + // Add global-style output target FIRST while AST positions are still valid + let insertionOffset = 0; + if (injectValue) { + const result = addGlobalStyleOutputTarget(text, sourceFile, injectValue); + insertionOffset = result.length - text.length; + text = result; + } + + // Remove the addGlobalStyleToComponents property + let start = matchNode.getStart(); + let end = matchNode.getEnd(); + + // Adjust positions if we inserted content before this node (extras typically comes after outputTargets) + const outputTargetsEnd = findOutputTargetsEnd(sourceFile); + if ( + insertionOffset !== 0 && + outputTargetsEnd !== null && + matchNode.getStart() > outputTargetsEnd + ) { + start += insertionOffset; + end += insertionOffset; + } + + // Handle trailing comma + const afterProp = text.slice(end).match(/^\s*,/); + if (afterProp) { + end = end + afterProp[0].length; + } else { + // Check for leading comma + const beforeProp = text.slice(0, start).match(/,\s*$/); + if (beforeProp) { + text = text.slice(0, start - beforeProp[0].length) + text.slice(end); + end = -1; + } + } + + if (end !== -1) { + text = text.slice(0, start) + text.slice(end); + } + + // Check if extras object is now empty and remove it if so + text = cleanupEmptyExtras(text); + + return text; + }, +}; + +/** + * Find the end position of the outputTargets array in the source file. + * @param sourceFile The source file to search + * @returns The end position of the outputTargets array, or null if not found + */ +function findOutputTargetsEnd(sourceFile: ts.SourceFile): number | null { + let endPos: number | null = null; + const visit = (node: ts.Node): void => { + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'outputTargets' + ) { + endPos = node.getEnd(); + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); + return endPos; +} + +/** + * Clean up empty extras object after removing addGlobalStyleToComponents. + * @param text The source text to clean up + * @returns The cleaned up text with empty extras removed + */ +function cleanupEmptyExtras(text: string): string { + // Match extras: { } or extras: {\n } with only whitespace inside + const emptyExtrasRegex = /,?\s*extras\s*:\s*\{\s*\},?/g; + return text.replace(emptyExtrasRegex, ''); +} + +/** + * Add a global-style output target with the specified inject value. + * @param text The source text to modify + * @param sourceFile The source file for position info + * @param injectValue The inject value to set on the new global-style output target + * @returns The modified text with the new global-style output target added + */ +function addGlobalStyleOutputTarget( + text: string, + sourceFile: ts.SourceFile, + injectValue: 'all' | 'client', +): string { + // Check if global-style output target already exists + const hasGlobalStyleTarget = text.includes("type: 'global-style'"); + if (hasGlobalStyleTarget) { + // TODO: Could add inject to existing target, but for now just skip + return text; + } + + // Find outputTargets array + let outputTargetsArray: ts.ArrayLiteralExpression | null = null; + const findOutputTargets = (node: ts.Node): void => { + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'outputTargets' && + ts.isArrayLiteralExpression(node.initializer) + ) { + outputTargetsArray = node.initializer; + } + ts.forEachChild(node, findOutputTargets); + }; + findOutputTargets(sourceFile); + + if (!outputTargetsArray) { + // No outputTargets array found, can't add + return text; + } + + const array: ts.ArrayLiteralExpression = outputTargetsArray; + + // Detect indentation + let indent = ' '; + if (array.elements.length > 0) { + const firstElement = array.elements[0]; + const elemStart = firstElement.getStart(); + const lineStart = text.lastIndexOf('\n', elemStart) + 1; + const leadingWhitespace = text.slice(lineStart, elemStart); + if (/^\s+$/.test(leadingWhitespace)) { + indent = leadingWhitespace; + } + } + + // Find insertion point + const lastElement = array.elements[array.elements.length - 1]; + if (!lastElement) { + // Empty array, insert at start + const arrayStart = array.getStart() + 1; // After '[' + const newTarget = `\n${indent}{\n${indent} type: 'global-style',\n${indent} inject: '${injectValue}',\n${indent}}\n${indent.slice(2)}`; + return text.slice(0, arrayStart) + newTarget + text.slice(arrayStart); + } + + let insertPos = lastElement.getEnd(); + + // Check for trailing comma + const afterLastElement = text.slice(insertPos).match(/^(\s*,)?/); + const hasTrailingComma = afterLastElement && afterLastElement[1]; + if (hasTrailingComma) { + insertPos += afterLastElement[0].length; + } + + const newTarget = `{\n${indent} type: 'global-style',\n${indent} inject: '${injectValue}',\n${indent}}`; + // Only add comma prefix if there wasn't already a trailing comma + const insertion = (hasTrailingComma ? '' : ',') + '\n' + indent + newTarget; + + return text.slice(0, insertPos) + insertion + text.slice(insertPos); +} diff --git a/packages/cli/src/migrations/rules/output-target-renames.ts b/packages/cli/src/migrations/rules/output-target-renames.ts new file mode 100644 index 00000000000..5a96790410a --- /dev/null +++ b/packages/cli/src/migrations/rules/output-target-renames.ts @@ -0,0 +1,394 @@ +import ts from 'typescript'; + +import type { MigrationMatch, MigrationRule } from '../index'; + +/** + * Migration rule for output target renames in Stencil v5. + * + * This migration: + * - Renames `dist` → `loader-bundle` + * - Renames `dist-custom-elements` → `standalone` + * - Renames `dist-hydrate-script` → `ssr` + * - Renames `dist-collection` → `stencil-rebundle` + * - Renames `dist-types` → `types` + * - Extracts `collectionDir` from loader-bundle into separate `stencil-rebundle` output + * - Extracts `typesDir` from loader-bundle into separate `types` output + * - Renames `esmLoaderPath` → `loaderPath` (applies to all module formats, not just ESM) + * - Removes `isPrimaryPackageOutputTarget` (no longer needed, package.json validation auto-detects) + * - Removes `generateTypeDeclarations` (types are now auto-generated via the `types` output target) + */ +export const outputTargetRenamesRule: MigrationRule = { + id: 'output-target-renames', + name: 'Output Target Renames', + description: 'Rename output targets to new v5 names', + fromVersion: '4.x', + toVersion: '5.x', + + detect(sourceFile: ts.SourceFile): MigrationMatch[] { + const matches: MigrationMatch[] = []; + + const oldToNewNames: Record = { + dist: 'loader-bundle', + 'dist-custom-elements': 'standalone', + 'dist-hydrate-script': 'ssr', + 'dist-collection': 'stencil-rebundle', + 'dist-types': 'types', + }; + + const visit = (node: ts.Node) => { + // Look for type: 'old-name' in output target objects + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'type' && + ts.isStringLiteral(node.initializer) + ) { + const typeValue = node.initializer.text; + if (typeValue in oldToNewNames) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + matches.push({ + node, + message: `Output target type '${typeValue}' → '${oldToNewNames[typeValue]}'`, + line: line + 1, + column: character + 1, + }); + } + } + + // Look for collectionDir or typesDir properties in output targets + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + (node.name.text === 'collectionDir' || node.name.text === 'typesDir') + ) { + // Check if this is inside an output target object (has a 'type' property sibling) + const parent = node.parent; + if (ts.isObjectLiteralExpression(parent)) { + const hasTypeProperty = parent.properties.some( + (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'type', + ); + if (hasTypeProperty) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + const propName = node.name.text; + const newType = propName === 'collectionDir' ? 'stencil-rebundle' : 'types'; + matches.push({ + node, + message: `Property '${propName}' will be extracted to separate '${newType}' output target`, + line: line + 1, + column: character + 1, + }); + } + } + } + + // Look for esmLoaderPath to rename to loaderPath + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'esmLoaderPath' + ) { + // Check if this is inside an output target object (has a 'type' property sibling) + const parent = node.parent; + if (ts.isObjectLiteralExpression(parent)) { + const hasTypeProperty = parent.properties.some( + (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'type', + ); + if (hasTypeProperty) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + matches.push({ + node, + message: `Property 'esmLoaderPath' renamed to 'loaderPath' (applies to all module formats)`, + line: line + 1, + column: character + 1, + }); + } + } + } + + // Look for removed properties: isPrimaryPackageOutputTarget and generateTypeDeclarations + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + (node.name.text === 'isPrimaryPackageOutputTarget' || + node.name.text === 'generateTypeDeclarations') + ) { + // Check if this is inside an output target object (has a 'type' property sibling) + const parent = node.parent; + if (ts.isObjectLiteralExpression(parent)) { + const hasTypeProperty = parent.properties.some( + (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'type', + ); + if (hasTypeProperty) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + const propName = node.name.text; + const reason = + propName === 'isPrimaryPackageOutputTarget' + ? 'Package.json validation now auto-detects based on configured outputs' + : "Types are now auto-generated via the 'types' output target"; + matches.push({ + node, + message: `Property '${propName}' is removed in v5. ${reason}`, + line: line + 1, + column: character + 1, + }); + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return matches; + }, + + transform(sourceFile: ts.SourceFile, matches: MigrationMatch[]): string { + if (matches.length === 0) { + return sourceFile.getFullText(); + } + + let text = sourceFile.getFullText(); + + const oldToNewNames: Record = { + dist: 'loader-bundle', + 'dist-custom-elements': 'standalone', + 'dist-hydrate-script': 'ssr', + 'dist-collection': 'stencil-rebundle', + 'dist-types': 'types', + }; + + // Track output targets that have collectionDir or typesDir to extract + const outputTargetsToExtract: Array<{ + targetNode: ts.ObjectLiteralExpression; + collectionDir?: string; + typesDir?: string; + }> = []; + + // Find output targets with collectionDir or typesDir + const findOutputTargetsToExtract = (node: ts.Node) => { + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'outputTargets' && + ts.isArrayLiteralExpression(node.initializer) + ) { + for (const element of node.initializer.elements) { + if (ts.isObjectLiteralExpression(element)) { + let collectionDir: string | undefined; + let typesDir: string | undefined; + + for (const prop of element.properties) { + if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { + if (prop.name.text === 'collectionDir' && ts.isStringLiteral(prop.initializer)) { + collectionDir = prop.initializer.text; + } else if (prop.name.text === 'typesDir' && ts.isStringLiteral(prop.initializer)) { + typesDir = prop.initializer.text; + } + } + } + + if (collectionDir || typesDir) { + outputTargetsToExtract.push({ targetNode: element, collectionDir, typesDir }); + } + } + } + } + ts.forEachChild(node, findOutputTargetsToExtract); + }; + + findOutputTargetsToExtract(sourceFile); + + // Collect all modifications sorted by position (descending) + const modifications: Array<{ + type: 'replace' | 'remove'; + start: number; + end: number; + replacement?: string; + }> = []; + + // Process type renames and collectionDir/typesDir removals + for (const match of matches) { + const prop = match.node as ts.PropertyAssignment; + const propName = (prop.name as ts.Identifier).text; + + if (propName === 'type' && ts.isStringLiteral(prop.initializer)) { + const oldType = prop.initializer.text; + const newType = oldToNewNames[oldType]; + if (newType) { + // Replace just the string value + const start = prop.initializer.getStart() + 1; // +1 to skip opening quote + const end = prop.initializer.getEnd() - 1; // -1 to skip closing quote + modifications.push({ + type: 'replace', + start, + end, + replacement: newType, + }); + } + } else if (propName === 'esmLoaderPath') { + // Rename property from esmLoaderPath to loaderPath + const nameNode = prop.name as ts.Identifier; + modifications.push({ + type: 'replace', + start: nameNode.getStart(), + end: nameNode.getEnd(), + replacement: 'loaderPath', + }); + + // Adjust the path value: prepend '../' since v5's default dir is one level deeper + // v4 default: dist/, v5 default: dist/loader-bundle/ + if (ts.isStringLiteral(prop.initializer)) { + const oldPath = prop.initializer.text; + const newPath = '../' + oldPath; + modifications.push({ + type: 'replace', + start: prop.initializer.getStart() + 1, // +1 to skip opening quote + end: prop.initializer.getEnd() - 1, // -1 to skip closing quote + replacement: newPath, + }); + } + } else if ( + propName === 'collectionDir' || + propName === 'typesDir' || + propName === 'isPrimaryPackageOutputTarget' || + propName === 'generateTypeDeclarations' + ) { + // Remove the property entirely (including comma) + const start = prop.getStart(); + let end = prop.getEnd(); + + // Handle trailing comma or leading comma + const beforeProp = text.slice(0, start).match(/,\s*$/); + const afterProp = text.slice(end).match(/^\s*,/); + + if (afterProp) { + end = end + afterProp[0].length; + } else if (beforeProp) { + // No trailing comma, remove leading comma instead + const commaStart = start - beforeProp[0].length; + modifications.push({ type: 'remove', start: commaStart, end }); + continue; + } + + modifications.push({ type: 'remove', start, end }); + } + } + + // Sort by position descending to preserve positions + modifications.sort((a, b) => b.start - a.start); + + // Apply modifications + for (const mod of modifications) { + if (mod.type === 'replace') { + text = text.slice(0, mod.start) + mod.replacement + text.slice(mod.end); + } else if (mod.type === 'remove') { + text = text.slice(0, mod.start) + text.slice(mod.end); + } + } + + // Add new output targets for extracted collectionDir and typesDir + if (outputTargetsToExtract.length > 0) { + text = addExtractedOutputTargets(text, sourceFile, outputTargetsToExtract); + } + + // Clean up any empty lines or double commas + text = text.replace(/,\s*\n\s*\n/g, ',\n'); + text = text.replace(/{\s*\n\s*\n/g, '{\n'); + text = text.replace(/,\s*,/g, ','); + + return text; + }, +}; + +/** + * Add new output targets for extracted collectionDir and typesDir. + * @param text The original source text + * @param sourceFile The TypeScript source file object + * @param toExtract An array of objects containing the output target nodes and their collectionDir/typesDir values to extract + * @returns The modified source text with new output targets added + */ +function addExtractedOutputTargets( + text: string, + sourceFile: ts.SourceFile, + toExtract: Array<{ + targetNode: ts.ObjectLiteralExpression; + collectionDir?: string; + typesDir?: string; + }>, +): string { + // Find the outputTargets array to append to + let outputTargetsArray: ts.ArrayLiteralExpression | null = null; + const findOutputTargets = (node: ts.Node): void => { + if ( + ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name) && + node.name.text === 'outputTargets' && + ts.isArrayLiteralExpression(node.initializer) + ) { + outputTargetsArray = node.initializer; + } + ts.forEachChild(node, findOutputTargets); + }; + findOutputTargets(sourceFile); + + if (!outputTargetsArray || toExtract.length === 0) { + return text; + } + + // Type assertion to help TypeScript understand outputTargetsArray is non-null + const array: ts.ArrayLiteralExpression = outputTargetsArray; + + // Detect indentation from existing output targets + let indent = ' '; + if (array.elements.length > 0) { + const firstElement = array.elements[0]; + const elemStart = firstElement.getStart(); + const lineStart = text.lastIndexOf('\n', elemStart) + 1; + const leadingWhitespace = text.slice(lineStart, elemStart); + if (/^\s+$/.test(leadingWhitespace)) { + indent = leadingWhitespace; + } + } + + // Find insertion point (before the closing bracket of the array) + const lastElement = array.elements[array.elements.length - 1]; + if (!lastElement) { + return text; + } + + let insertPos = lastElement.getEnd(); + + // Check if there's a trailing comma + const afterLastElement = text.slice(insertPos).match(/^(\s*,)?/); + const hasTrailingComma = afterLastElement && afterLastElement[1]; + if (hasTrailingComma) { + insertPos += afterLastElement[0].length; + } + + // Build the new output targets to insert + const newTargets: string[] = []; + + for (const extracted of toExtract) { + if (extracted.collectionDir) { + newTargets.push( + `{\n${indent} type: 'stencil-rebundle',\n${indent} dir: '${extracted.collectionDir}',\n${indent}}`, + ); + } + if (extracted.typesDir) { + newTargets.push( + `{\n${indent} type: 'types',\n${indent} dir: '${extracted.typesDir}',\n${indent}}`, + ); + } + } + + if (newTargets.length === 0) { + return text; + } + + // Insert the new targets + const insertion = ',\n' + newTargets.map((t) => `${indent}${t}`).join(',\n'); + + text = text.slice(0, insertPos) + insertion + text.slice(insertPos); + + return text; +} diff --git a/src/cli/parse-flags.ts b/packages/cli/src/parse-flags.ts similarity index 94% rename from src/cli/parse-flags.ts rename to packages/cli/src/parse-flags.ts index 4f3da3cc5c0..2435c1d2644 100644 --- a/src/cli/parse-flags.ts +++ b/packages/cli/src/parse-flags.ts @@ -1,6 +1,6 @@ -import { readOnlyArrayHasStringMember, toCamelCase } from '@utils'; +import { LOG_LEVELS, type LogLevel } from '@stencil/core/compiler'; +import { readOnlyArrayHasStringMember, toCamelCase } from '@stencil/core/compiler/utils'; -import { LOG_LEVELS, LogLevel, TaskCommand } from '../declarations'; import { BOOLEAN_CLI_FLAGS, BOOLEAN_STRING_CLI_FLAGS, @@ -14,6 +14,7 @@ import { STRING_CLI_FLAGS, STRING_NUMBER_CLI_FLAGS, } from './config-flags'; +import type { TaskCommand } from './types'; /** * Parse command line arguments into a structured `ConfigFlags` object @@ -216,7 +217,12 @@ const normalizeFlagName = (flagName: string): string => { * `--no-`, etc) removed * @param value the raw value to be set onto the config flags object */ -const setCLIArg = (flags: ConfigFlags, rawArg: string, normalizedArg: string, value: CLIValueResult) => { +const setCLIArg = ( + flags: ConfigFlags, + rawArg: string, + normalizedArg: string, + value: CLIValueResult, +) => { normalizedArg = desugarAlias(normalizedArg); // We're setting a boolean! @@ -249,13 +255,19 @@ const setCLIArg = (flags: ConfigFlags, rawArg: string, normalizedArg: string, va // We're setting a string, but it's one where the user can pass multiple values, // like `--reporters="default" --reporters="jest-junit"` - else if (readOnlyArrayHasStringMember(STRING_ARRAY_CLI_FLAGS, normalizedArg)) { + // Note: STRING_ARRAY_CLI_FLAGS is currently empty, but we keep the infrastructure + // for future CLI flags that accept multiple string values. + else if ( + STRING_ARRAY_CLI_FLAGS.length > 0 && + readOnlyArrayHasStringMember(STRING_ARRAY_CLI_FLAGS, normalizedArg) + ) { if (typeof value === 'string') { - if (!Array.isArray(flags[normalizedArg])) { - flags[normalizedArg] = []; + const flagsRecord = flags as unknown as Record; + if (!Array.isArray(flagsRecord[normalizedArg])) { + flagsRecord[normalizedArg] = []; } - const targetArray = flags[normalizedArg]; + const targetArray = flagsRecord[normalizedArg]; // this is irritating, but TS doesn't know that the `!Array.isArray` // check above guarantees we have an array to work with here, and it // doesn't want to narrow the type of `flags[normalizedArg]`, so we need @@ -335,7 +347,10 @@ const setCLIArg = (flags: ConfigFlags, rawArg: string, normalizedArg: string, va flags.knownArgs.push(rawArg); flags.knownArgs.push(value); } else { - throwCLIParsingError(rawArg, `expected to receive a valid log level but received "${String(value)}"`); + throwCLIParsingError( + rawArg, + `expected to receive a valid log level but received "${String(value)}"`, + ); } } else { throwCLIParsingError(rawArg, 'expected to receive a valid log level but received nothing'); @@ -368,7 +383,7 @@ const setCLIArg = (flags: ConfigFlags, rawArg: string, normalizedArg: string, va * be parsed as a string literal, rather than using `Number` to convert it * to a number. */ -const CLI_ARG_STRING_REGEX = /[^\d\.Ee\+\-]+/g; +const CLI_ARG_STRING_REGEX = /[^\d.Ee+-]+/g; export const Empty = Symbol('Empty'); @@ -514,4 +529,5 @@ const desugarAlias = (maybeAlias: string): string => { * @param rawAlias a CLI flag alias as found on the command line (like `"-c"`) * @returns an equivalent full command (like `"--config"`) */ -const desugarRawAlias = (rawAlias: string): string => '--' + desugarAlias(normalizeFlagName(rawAlias)); +const desugarRawAlias = (rawAlias: string): string => + '--' + desugarAlias(normalizeFlagName(rawAlias)); diff --git a/packages/cli/src/run.ts b/packages/cli/src/run.ts new file mode 100644 index 00000000000..2ee2a495121 --- /dev/null +++ b/packages/cli/src/run.ts @@ -0,0 +1,195 @@ +import { ValidatedConfig } from '@stencil/core/compiler'; +import { hasError, isFunction, result, shouldIgnoreError } from '@stencil/core/compiler/utils'; +import type * as d from '@stencil/core/compiler'; + +import { ConfigFlags, createConfigFlags } from './config-flags'; +import { findConfig } from './find-config'; +import { CoreCompiler, loadCoreCompiler } from './load-compiler'; +import { loadedCompilerLog, startupLog, startupLogVersion } from './logs'; +import { mergeFlags } from './merge-flags'; +import { parseFlags } from './parse-flags'; +import { taskBuild } from './task-build'; +import { taskDocs } from './task-docs'; +import { taskGenerate } from './task-generate'; +import { taskHelp } from './task-help'; +import { taskInfo } from './task-info'; +import { taskMigrate } from './task-migrate'; +import { taskPrerender } from './task-prerender'; +import { taskServe } from './task-serve'; +import { taskTelemetry } from './task-telemetry'; +import { telemetryAction } from './telemetry/telemetry'; +import type { TaskCommand } from './types'; + +/** + * Main entry point for the Stencil CLI + * + * Take care of parsing CLI arguments, initializing various components needed + * by the rest of the program, and kicking off the correct task (build, test, + * etc). + * + * @param init initial CLI options + * @returns an empty promise + */ +export const run = async (init: d.CliInitOptions) => { + const { args, logger, sys } = init; + + try { + const flags = parseFlags(args); + const task = flags.task; + + if (flags.debug || flags.verbose) { + logger.setLevel('debug'); + } + + if (flags.ci) { + logger.enableColors(false); + } + + if (isFunction(sys.applyGlobalPatch)) { + sys.applyGlobalPatch(sys.getCurrentDirectory()); + } + + if ((task && task === 'version') || flags.version) { + // we need to load the compiler here to get the version, but we don't + // want to load it in the case that we're going to just log the help + // message and then exit below (if there's no `task` defined) so we load + // it just within our `if` scope here. + const coreCompiler = await loadCoreCompiler(sys); + console.log(coreCompiler.version); + return; + } + + if (!task || task === 'help' || flags.help) { + await taskHelp(createConfigFlags({ task: 'help', args }), logger, sys); + + return; + } + + startupLog(logger, task); + + const findConfigResults = await findConfig({ sys, configPath: flags.config }); + if (findConfigResults.isErr) { + logger.printDiagnostics(findConfigResults.value); + return sys.exit(1); + } + + const coreCompiler = await loadCoreCompiler(sys); + + startupLogVersion(logger, task, coreCompiler); + + loadedCompilerLog(sys, logger, flags, coreCompiler); + + if (task === 'info') { + taskInfo(coreCompiler, sys, logger); + return; + } + + const foundConfig = result.unwrap(findConfigResults); + // Merge CLI flags into a base config object before passing to Core. + // Core doesn't need to know about flags - it just receives config values. + const configWithFlags = mergeFlags({}, flags); + const validated = await coreCompiler.loadConfig({ + config: configWithFlags, + configPath: foundConfig.configPath, + logger, + sys, + }); + + if (validated.diagnostics.length > 0) { + logger.printDiagnostics(validated.diagnostics); + if (hasError(validated.diagnostics)) { + return sys.exit(1); + } + } + + if (isFunction(sys.applyGlobalPatch)) { + sys.applyGlobalPatch(validated.config.rootDir); + } + + await telemetryAction(sys, validated.config, coreCompiler, flags, async () => { + await runTask(coreCompiler, validated.config, task, sys, flags); + }); + } catch (e) { + if (!shouldIgnoreError(e)) { + const details = `${logger.getLevel() === 'debug' && e instanceof Error ? e.stack : ''}`; + logger.error(`uncaught cli error: ${e}${details}`); + return sys.exit(1); + } + } +}; + +/** + * Run a specified task + * + * @param coreCompiler an instance of a minimal, bootstrap compiler for running the specified task + * @param config a configuration for the Stencil project to apply to the task run + * @param task the task to run + * @param sys the {@link d.CompilerSystem} for interacting with the operating system + * @param flags the parsed CLI flags (owned by CLI, not passed to Core) + * @public + * @returns a void promise + */ +export const runTask = async ( + coreCompiler: CoreCompiler, + config: d.Config, + task: TaskCommand, + sys: d.CompilerSystem, + flags?: ConfigFlags, +): Promise => { + // Ensure we have flags (either passed in or create defaults) + const resolvedFlags = flags ?? createConfigFlags({ task }); + + // Merge CLI flags into config before validation + const configWithFlags = mergeFlags(config, resolvedFlags); + + if (!configWithFlags.sys) { + configWithFlags.sys = sys; + } + const strictConfig: ValidatedConfig = coreCompiler.validateConfig(configWithFlags, {}).config; + + switch (task) { + case 'build': + await taskBuild(coreCompiler, strictConfig, resolvedFlags); + break; + + case 'docs': + await taskDocs(coreCompiler, strictConfig); + break; + + case 'generate': + case 'g': + await taskGenerate(strictConfig, resolvedFlags); + break; + + case 'help': + await taskHelp(resolvedFlags, strictConfig.logger, sys); + break; + + case 'migrate': + await taskMigrate(coreCompiler, strictConfig, resolvedFlags); + break; + + case 'prerender': + await taskPrerender(coreCompiler, strictConfig, resolvedFlags); + break; + + case 'serve': + await taskServe(strictConfig, resolvedFlags); + break; + + case 'telemetry': + await taskTelemetry(resolvedFlags, sys, strictConfig.logger); + break; + + case 'version': + console.log(coreCompiler.version); + break; + + default: + strictConfig.logger.error( + `${strictConfig.logger.emoji('❌ ')}Invalid stencil command, please see the options below:`, + ); + await taskHelp(resolvedFlags, strictConfig.logger, sys); + return configWithFlags.sys.exit(1); + } +}; diff --git a/packages/cli/src/task-build.ts b/packages/cli/src/task-build.ts new file mode 100644 index 00000000000..ed5cce1d1ee --- /dev/null +++ b/packages/cli/src/task-build.ts @@ -0,0 +1,203 @@ +import { relative } from 'path'; +import type * as d from '@stencil/core/compiler'; + +import { printCheckVersionResults, startCheckVersion } from './check-version'; +import { startupCompilerLog } from './logs'; +import { detectMigrations, taskMigrate, type MigrationDetectionResult } from './task-migrate'; +import { runPrerenderTask } from './task-prerender'; +import { taskWatch } from './task-watch'; +import { telemetryBuildFinishedAction } from './telemetry/telemetry'; +import type { ConfigFlags } from './config-flags'; +import type { CoreCompiler } from './load-compiler'; + +export const taskBuild = async ( + coreCompiler: CoreCompiler, + config: d.ValidatedConfig, + flags: ConfigFlags, +) => { + if (flags.watch) { + // watch build + await taskWatch(coreCompiler, config, flags); + return; + } + + // one-time build + let exitCode = 0; + + try { + startupCompilerLog(coreCompiler, config); + + // Check for migrations BEFORE building - deprecated config options should be migrated first + const preBuildMigrationResult = await detectMigrations(coreCompiler, config); + if (preBuildMigrationResult.hasMigrations) { + const action = await promptForMigration(config, preBuildMigrationResult, 'pre-build'); + + if (action === 'run') { + // Run migrations first + await taskMigrate(coreCompiler, config, { ...flags, dryRun: false }); + config.logger.info('\nMigrations applied. Starting build...\n'); + } else if (action === 'dry-run') { + // Show what would be migrated and exit + await taskMigrate(coreCompiler, config, { ...flags, dryRun: true }); + return config.sys.exit(1); + } else { + // User chose to exit + return config.sys.exit(1); + } + } + + const versionChecker = startCheckVersion(config, coreCompiler.version, flags); + + const compiler = await coreCompiler.createCompiler(config); + const results = await compiler.build(); + + await telemetryBuildFinishedAction(config.sys, config, coreCompiler, results, flags); + + await compiler.destroy(); + + if (results.hasError) { + // Check if there are migrations that might help fix the errors + const migrationResult = await detectMigrations(coreCompiler, config); + + if (migrationResult.hasMigrations) { + // Show what migrations are available and prompt user + const action = await promptForMigration(config, migrationResult, 'post-error'); + + if (action === 'run') { + // Run migrations and re-run build + await taskMigrate(coreCompiler, config, { ...flags, dryRun: false }); + config.logger.info('\nRe-running build after migrations...\n'); + + // Re-run the build + const newCompiler = await coreCompiler.createCompiler(config); + const newResults = await newCompiler.build(); + await newCompiler.destroy(); + + if (!newResults.hasError) { + // Build succeeded after migration + exitCode = 0; + if (flags.prerender) { + const prerenderDiagnostics = await runPrerenderTask( + coreCompiler, + config, + newResults.ssrAppFilePath, + newResults.componentGraph, + undefined, + ); + config.logger.printDiagnostics(prerenderDiagnostics); + if (prerenderDiagnostics.some((d) => d.level === 'error')) { + exitCode = 1; + } + } + } else { + exitCode = 1; + } + } else if (action === 'dry-run') { + // Show what would be migrated + await taskMigrate(coreCompiler, config, { ...flags, dryRun: true }); + exitCode = 1; + } else { + // User chose to exit + exitCode = 1; + } + } else { + exitCode = 1; + } + } else if (flags.prerender) { + const prerenderDiagnostics = await runPrerenderTask( + coreCompiler, + config, + results.ssrAppFilePath, + results.componentGraph, + undefined, + ); + config.logger.printDiagnostics(prerenderDiagnostics); + + if (prerenderDiagnostics.some((d) => d.level === 'error')) { + exitCode = 1; + } + } + + await printCheckVersionResults(versionChecker); + } catch (e) { + exitCode = 1; + config.logger.error(e); + } + + if (exitCode > 0) { + return config.sys.exit(exitCode); + } +}; + +type MigrationAction = 'run' | 'dry-run' | 'exit'; + +/** + * Prompt the user about available migrations. + * Shows what migrations are available and lets them choose to run them. + * @param config the Stencil config + * @param migrationResult the result of migration detection with available migrations + * @param context whether this is a pre-build check or post-error check + * @returns the user's chosen action for handling migrations + */ +async function promptForMigration( + config: d.ValidatedConfig, + migrationResult: MigrationDetectionResult, + context: 'pre-build' | 'post-error', +): Promise { + const logger = config.logger; + + // Show migration availability message + logger.info(''); + logger.info(logger.bold(logger.yellow('Migrations Required'))); + logger.info('─'.repeat(40)); + logger.info( + `Found ${migrationResult.totalMatches} item(s) in ${migrationResult.filesAffected} file(s) that need to be migrated for Stencil v5.`, + ); + + // Show summary of what can be migrated + for (const migration of migrationResult.migrations) { + const relPath = relative(config.rootDir, migration.filePath); + logger.info(` ${logger.cyan(relPath)}: ${migration.matches.length} item(s)`); + } + + logger.info(''); + if (context === 'pre-build') { + logger.info('Your config contains deprecated options that must be migrated before building.'); + } else { + logger.info('These migrations may help resolve the build errors above.'); + } + + // Import prompts dynamically (default export is the prompt function) + const prompts = await import('prompts'); + const prompt = prompts.default; + + const response = await prompt({ + name: 'action', + type: 'select', + message: 'What would you like to do?', + choices: [ + { + title: 'Run migration', + value: 'run', + description: 'Apply migrations and re-run build', + }, + { + title: 'Dry run', + value: 'dry-run', + description: 'Preview changes without modifying files', + }, + { + title: 'Exit', + value: 'exit', + description: 'Exit without making changes', + }, + ], + }); + + // Handle Ctrl+C or escape + if (response.action === undefined) { + return 'exit'; + } + + return response.action as MigrationAction; +} diff --git a/src/cli/task-docs.ts b/packages/cli/src/task-docs.ts similarity index 78% rename from src/cli/task-docs.ts rename to packages/cli/src/task-docs.ts index ecd64177efa..b6bc149f6b8 100644 --- a/src/cli/task-docs.ts +++ b/packages/cli/src/task-docs.ts @@ -1,8 +1,8 @@ -import { isOutputTargetDocs } from '@utils'; +import { isOutputTargetDocs } from '@stencil/core/compiler/utils'; +import type { ValidatedConfig } from '@stencil/core/compiler'; -import type { ValidatedConfig } from '../declarations'; -import type { CoreCompiler } from './load-compiler'; import { startupCompilerLog } from './logs'; +import type { CoreCompiler } from './load-compiler'; export const taskDocs = async (coreCompiler: CoreCompiler, config: ValidatedConfig) => { config.devServer = {}; diff --git a/src/cli/task-generate.ts b/packages/cli/src/task-generate.ts similarity index 90% rename from src/cli/task-generate.ts rename to packages/cli/src/task-generate.ts index 0f0189a4d55..879d9641383 100644 --- a/src/cli/task-generate.ts +++ b/packages/cli/src/task-generate.ts @@ -1,7 +1,8 @@ -import { normalizePath, validateComponentTag } from '@utils'; import { join, parse, relative } from 'path'; +import { normalizePath, validateComponentTag } from '@stencil/core/compiler/utils'; +import type { ValidatedConfig } from '@stencil/core/compiler'; -import type { ValidatedConfig } from '../declarations'; +import type { ConfigFlags } from './config-flags'; /** * Task to generate component boilerplate and write it to disk. This task can @@ -10,11 +11,14 @@ import type { ValidatedConfig } from '../declarations'; * already exist, etc. * * @param config the user-supplied config, which we need here to access `.sys`. + * @param flags the CLI flags (owned by CLI, not part of core config) * @returns a void promise */ -export const taskGenerate = async (config: ValidatedConfig): Promise => { +export const taskGenerate = async (config: ValidatedConfig, flags: ConfigFlags): Promise => { if (!config.configPath) { - config.logger.error('Please run this command in your root directory (i. e. the one containing stencil.config.ts).'); + config.logger.error( + 'Please run this command in your root directory (i. e. the one containing stencil.config.ts).', + ); return config.sys.exit(1); } @@ -28,8 +32,9 @@ export const taskGenerate = async (config: ValidatedConfig): Promise => { const { prompt } = await import('prompts'); const input = - config.flags.unknownArgs.find((arg) => !arg.startsWith('-')) || - ((await prompt({ name: 'tagName', type: 'text', message: 'Component tag name (dash-case):' })).tagName as string); + flags.unknownArgs.find((arg: string) => !arg.startsWith('-')) || + ((await prompt({ name: 'tagName', type: 'text', message: 'Component tag name (dash-case):' })) + .tagName as string); if (undefined === input) { // in some shells (e.g. Windows PowerShell), hitting Ctrl+C results in a TypeError printed to the console. @@ -45,9 +50,9 @@ export const taskGenerate = async (config: ValidatedConfig): Promise => { } let cssExtension: GeneratableStylingExtension = 'css'; - if (!!config.plugins.find((plugin) => plugin.name === 'sass')) { + if (config.plugins?.find((plugin) => plugin.name === 'sass')) { cssExtension = await chooseSassExtension(); - } else if (!!config.plugins.find((plugin) => plugin.name === 'less')) { + } else if (config.plugins?.find((plugin) => plugin.name === 'less')) { cssExtension = 'less'; } const filesToGenerateExt = await chooseFilesToGenerate(cssExtension); @@ -107,7 +112,9 @@ export const taskGenerate = async (config: ValidatedConfig): Promise => { * @returns a read-only array of `GeneratableExtension`, the extensions that the user has decided * to generate */ -const chooseFilesToGenerate = async (cssExtension: string): Promise> => { +const chooseFilesToGenerate = async ( + cssExtension: string, +): Promise> => { const { prompt } = await import('prompts'); return ( await prompt({ @@ -151,7 +158,11 @@ const chooseSassExtension = async () => { * @returns the full filepath to the component (with a possible `test` directory * added) */ -const getFilepathForFile = (filePath: string, componentName: string, extension: GeneratableExtension): string => +const getFilepathForFile = ( + filePath: string, + componentName: string, + extension: GeneratableExtension, +): string => isTest(extension) ? normalizePath(join(filePath, 'test', `${componentName}.${extension}`)) : normalizePath(join(filePath, `${componentName}.${extension}`)); @@ -174,7 +185,12 @@ const getBoilerplateAndWriteFile = async ( file: BoilerplateFile, styleExtension: GeneratableStylingExtension, ): Promise => { - const boilerplate = getBoilerplateByExtension(componentName, file.extension, withCss, styleExtension); + const boilerplate = getBoilerplateByExtension( + componentName, + file.extension, + withCss, + styleExtension, + ); await config.sys.writeFile(normalizePath(file.path), boilerplate); return file.path; }; @@ -191,7 +207,10 @@ const getBoilerplateAndWriteFile = async ( * @param files the files we want to check * @param config the Config object, used here to get access to `sys.readFile` */ -const checkForOverwrite = async (files: readonly BoilerplateFile[], config: ValidatedConfig): Promise => { +const checkForOverwrite = async ( + files: readonly BoilerplateFile[], + config: ValidatedConfig, +): Promise => { const alreadyPresent: string[] = []; await Promise.all( diff --git a/src/cli/task-help.ts b/packages/cli/src/task-help.ts similarity index 93% rename from src/cli/task-help.ts rename to packages/cli/src/task-help.ts index 67a4b994db6..cc3279fe7d3 100644 --- a/src/cli/task-help.ts +++ b/packages/cli/src/task-help.ts @@ -1,4 +1,5 @@ -import type * as d from '../declarations'; +import type * as d from '@stencil/core/compiler'; + import { ConfigFlags } from './config-flags'; import { taskTelemetry } from './task-telemetry'; @@ -9,7 +10,11 @@ import { taskTelemetry } from './task-telemetry'; * @param logger a logging implementation to log the results out to the user * @param sys the abstraction for interfacing with the operating system */ -export const taskHelp = async (flags: ConfigFlags, logger: d.Logger, sys: d.CompilerSystem): Promise => { +export const taskHelp = async ( + flags: ConfigFlags, + logger: d.Logger, + sys: d.CompilerSystem, +): Promise => { const prompt = logger.dim(sys.details?.platform === 'windows' ? '>' : '$'); console.log(` diff --git a/src/cli/task-info.ts b/packages/cli/src/task-info.ts similarity index 77% rename from src/cli/task-info.ts rename to packages/cli/src/task-info.ts index eb97b584ff3..231b63e8d15 100644 --- a/src/cli/task-info.ts +++ b/packages/cli/src/task-info.ts @@ -1,4 +1,5 @@ -import type { CompilerSystem, Logger } from '../declarations'; +import type { CompilerSystem, Logger } from '@stencil/core/compiler'; + import type { CoreCompiler } from './load-compiler'; /** @@ -23,11 +24,11 @@ export const taskInfo = (coreCompiler: CoreCompiler, sys: CompilerSystem, logger } console.log(`${logger.cyan(' Compiler:')} ${sys.getCompilerExecutingPath()}`); console.log(`${logger.cyan(' Build:')} ${coreCompiler.buildId}`); - console.log(`${logger.cyan(' Stencil:')} ${coreCompiler.version}${logger.emoji(' ' + coreCompiler.vermoji)}`); + console.log( + `${logger.cyan(' Stencil:')} ${coreCompiler.version}${logger.emoji(' ' + coreCompiler.vermoji)}`, + ); console.log(`${logger.cyan(' TypeScript:')} ${versions.typescript}`); - console.log(`${logger.cyan(' Rollup:')} ${versions.rollup}`); - console.log(`${logger.cyan(' Parse5:')} ${versions.parse5}`); - console.log(`${logger.cyan(' jQuery:')} ${versions.jquery}`); + console.log(`${logger.cyan(' Rolldown:')} ${versions.rolldown}`); console.log(`${logger.cyan(' Terser:')} ${versions.terser}`); console.log(``); }; diff --git a/packages/cli/src/task-migrate.ts b/packages/cli/src/task-migrate.ts new file mode 100644 index 00000000000..16788f0eb0b --- /dev/null +++ b/packages/cli/src/task-migrate.ts @@ -0,0 +1,312 @@ +import { isAbsolute, join, relative } from 'path'; +import ts from 'typescript'; +import type * as d from '@stencil/core/compiler'; + +import { getRulesForVersionUpgrade, type MigrationMatch, type MigrationRule } from './migrations'; +import type { ConfigFlags } from './config-flags'; +import type { CoreCompiler } from './load-compiler'; + +interface MigrationResult { + filePath: string; + rule: MigrationRule; + matches: MigrationMatch[]; + transformed: boolean; +} + +/** + * Represents a detected migration that can be applied. + */ +export interface DetectedMigration { + filePath: string; + rule: MigrationRule; + matches: MigrationMatch[]; +} + +/** + * Result of migration detection. + */ +export interface MigrationDetectionResult { + /** Whether any migrations were detected */ + hasMigrations: boolean; + /** Total number of items that need migration */ + totalMatches: number; + /** Number of files affected */ + filesAffected: number; + /** The detected migrations */ + migrations: DetectedMigration[]; + /** The migration rules that were checked */ + rules: MigrationRule[]; +} + +/** + * Run the migration task to update Stencil components from v4 to v5 API. + * + * @param coreCompiler the Stencil compiler instance + * @param config the validated Stencil config + * @param flags CLI flags (includes dryRun option) + */ +export const taskMigrate = async ( + coreCompiler: CoreCompiler, + config: d.ValidatedConfig, + flags: ConfigFlags, +): Promise => { + const logger = config.logger; + const sys = config.sys; + const dryRun = flags.dryRun ?? false; + + // Get migration rules for the specified version upgrade + // Default: from previous major version to current installed version + const currentMajor = coreCompiler.version.split('.')[0]; + const fromVersion = String(Number(currentMajor) - 1); + const toVersion = currentMajor; + const rules = getRulesForVersionUpgrade(fromVersion, toVersion); + + if (rules.length === 0) { + logger.info(`No migration rules found for ${fromVersion}.x → ${toVersion}.x upgrade.`); + return; + } + + logger.info(`${logger.emoji('🔄 ')}Stencil Migration Tool (v${fromVersion} → v${toVersion})`); + logger.info(`Scanning for components that need migration...`); + + if (dryRun) { + logger.info(logger.cyan('Dry run mode - no files will be modified')); + } + + // Get TypeScript files from tsconfig (same approach as the compiler) + const tsFiles = await getTypeScriptFiles(config, sys, logger); + + if (tsFiles.length === 0) { + logger.info(`No TypeScript files found. Check your tsconfig.json configuration.`); + return; + } + + logger.info(`Found ${tsFiles.length} TypeScript files to scan`); + + const results: MigrationResult[] = []; + + // Process each file + for (const filePath of tsFiles) { + let content = await sys.readFile(filePath); + if (!content) { + continue; + } + + // Run each migration rule - re-parse after each transformation to get fresh positions + for (const rule of rules) { + const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true); + const matches = rule.detect(sourceFile); + + if (matches.length > 0) { + const relPath = relative(config.rootDir, filePath); + logger.info(`\n${logger.cyan(relPath)}`); + logger.info(` ${logger.yellow(`[${rule.id}]`)} ${rule.name}`); + + for (const match of matches) { + logger.info(` Line ${match.line}: ${match.message}`); + } + + if (!dryRun) { + // Apply the transformation + const transformed = rule.transform(sourceFile, matches); + await sys.writeFile(filePath, transformed); + // Update content for next rule to use fresh positions + content = transformed; + results.push({ filePath, rule, matches, transformed: true }); + logger.info(` ${logger.green('✓')} Migrated`); + } else { + results.push({ filePath, rule, matches, transformed: false }); + } + } + } + } + + // Print summary + logger.info('\n' + logger.bold('Migration Summary')); + logger.info('─'.repeat(40)); + + const totalMatches = results.reduce((sum, r) => sum + r.matches.length, 0); + const filesAffected = new Set(results.map((r) => r.filePath)).size; + + if (totalMatches === 0) { + logger.info(logger.green('No migrations needed - your code is up to date!')); + } else { + logger.info(`Found ${totalMatches} item(s) to migrate in ${filesAffected} file(s)`); + + if (dryRun) { + logger.info(logger.yellow('\nRun without --dry-run to apply the migrations')); + } else { + logger.info(logger.green(`\n✓ Successfully migrated ${totalMatches} item(s)`)); + } + } + + // Group results by rule for detailed summary + const byRule = new Map(); + for (const result of results) { + const existing = byRule.get(result.rule.id) || []; + existing.push(result); + byRule.set(result.rule.id, existing); + } + + if (byRule.size > 0) { + logger.info('\nBy migration rule:'); + for (const [ruleId, ruleResults] of byRule) { + const rule = rules.find((r) => r.id === ruleId); + const count = ruleResults.reduce((sum, r) => sum + r.matches.length, 0); + logger.info(` ${rule?.name || ruleId}: ${count} item(s)`); + } + } +}; + +/** + * Detect available migrations without applying them. + * Used by the build task to check if migrations might help fix build errors. + * + * @param coreCompiler the Stencil compiler instance + * @param config the validated Stencil config + * @returns detection result with migration information + */ +export const detectMigrations = async ( + coreCompiler: CoreCompiler, + config: d.ValidatedConfig, +): Promise => { + const sys = config.sys; + const logger = config.logger; + + // Get migration rules for the specified version upgrade + const currentMajor = coreCompiler.version.split('.')[0]; + const fromVersion = String(Number(currentMajor) - 1); + const toVersion = currentMajor; + const rules = getRulesForVersionUpgrade(fromVersion, toVersion); + + if (rules.length === 0) { + return { + hasMigrations: false, + totalMatches: 0, + filesAffected: 0, + migrations: [], + rules: [], + }; + } + + // Get TypeScript files from tsconfig + const tsFiles = await getTypeScriptFiles(config, sys, logger); + + if (tsFiles.length === 0) { + return { + hasMigrations: false, + totalMatches: 0, + filesAffected: 0, + migrations: [], + rules, + }; + } + + const migrations: DetectedMigration[] = []; + + // Detect migrations in each file + for (const filePath of tsFiles) { + const content = await sys.readFile(filePath); + if (!content) { + continue; + } + + for (const rule of rules) { + const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true); + const matches = rule.detect(sourceFile); + + if (matches.length > 0) { + migrations.push({ filePath, rule, matches }); + } + } + } + + const totalMatches = migrations.reduce((sum, m) => sum + m.matches.length, 0); + const filesAffected = new Set(migrations.map((m) => m.filePath)).size; + + return { + hasMigrations: migrations.length > 0, + totalMatches, + filesAffected, + migrations, + rules, + }; +}; + +/** + * Get TypeScript files using the project's tsconfig.json. + * Uses the same approach as the Stencil compiler. + * + * @param config the validated Stencil config + * @param sys the compiler system for file operations + * @param logger the logger for output + * @returns array of absolute paths to TypeScript files + */ +async function getTypeScriptFiles( + config: d.ValidatedConfig, + sys: d.CompilerSystem, + logger: d.Logger, +): Promise { + // Determine tsconfig path - check stencil config first, fall back to default + let tsconfigPath: string; + if (config.tsconfig) { + tsconfigPath = isAbsolute(config.tsconfig) + ? config.tsconfig + : join(config.rootDir, config.tsconfig); + } else { + tsconfigPath = join(config.rootDir, 'tsconfig.json'); + } + + logger.debug(`Using tsconfig: ${tsconfigPath}`); + + // Check if tsconfig exists + const tsconfigContent = await sys.readFile(tsconfigPath); + if (!tsconfigContent) { + logger.error(`tsconfig not found: ${tsconfigPath}`); + return []; + } + + // Parse the tsconfig using TypeScript's native parser + // Use ts.sys directly for readDirectory since it handles glob patterns correctly + const host: ts.ParseConfigFileHost = { + ...ts.sys, + readFile: (p) => { + if (p === tsconfigPath) { + return tsconfigContent; + } + return ts.sys.readFile(p); + }, + onUnRecoverableConfigFileDiagnostic: (diagnostic) => { + logger.error(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')); + }, + }; + + const results = ts.getParsedCommandLineOfConfigFile(tsconfigPath, {}, host); + + if (!results) { + logger.error(`Failed to parse tsconfig: ${tsconfigPath}`); + return []; + } + + if (results.errors && results.errors.length > 0) { + for (const err of results.errors) { + logger.warn(ts.flattenDiagnosticMessageText(err.messageText, '\n')); + } + } + + // Filter to only .ts and .tsx files (excluding .d.ts) + const files = results.fileNames.filter( + (f) => (f.endsWith('.ts') || f.endsWith('.tsx')) && !f.endsWith('.d.ts'), + ); + + // Also include the stencil config file - it's typically not in tsconfig includes + // but it can contain deprecated config options that need migration + const configFile = config.configPath; + if (configFile && (configFile.endsWith('.ts') || configFile.endsWith('.mts'))) { + if (!files.includes(configFile)) { + files.push(configFile); + } + } + + return files; +} diff --git a/packages/cli/src/task-prerender.ts b/packages/cli/src/task-prerender.ts new file mode 100644 index 00000000000..0facb36dc58 --- /dev/null +++ b/packages/cli/src/task-prerender.ts @@ -0,0 +1,65 @@ +import { catchError } from '@stencil/core/compiler/utils'; +import type { + BuildResultsComponentGraph, + Diagnostic, + ValidatedConfig, +} from '@stencil/core/compiler'; + +import { startupCompilerLog } from './logs'; +import type { ConfigFlags } from './config-flags'; +import type { CoreCompiler } from './load-compiler'; + +export const taskPrerender = async ( + coreCompiler: CoreCompiler, + config: ValidatedConfig, + flags: ConfigFlags, +) => { + startupCompilerLog(coreCompiler, config); + + const ssrAppFilePath = flags.unknownArgs[0]; + + if (typeof ssrAppFilePath !== 'string') { + config.logger.error(`Missing hydrate app script path`); + return config.sys.exit(1); + } + + const srcIndexHtmlPath = config.srcIndexHtml; + + const diagnostics = await runPrerenderTask( + coreCompiler, + config, + ssrAppFilePath, + undefined, + srcIndexHtmlPath, + ); + config.logger.printDiagnostics(diagnostics); + + if (diagnostics.some((d) => d.level === 'error')) { + return config.sys.exit(1); + } +}; + +export const runPrerenderTask = async ( + coreCompiler: CoreCompiler, + config: ValidatedConfig, + ssrAppFilePath?: string, + componentGraph?: BuildResultsComponentGraph, + srcIndexHtmlPath?: string, +) => { + const diagnostics: Diagnostic[] = []; + + try { + const prerenderer = await coreCompiler.createPrerenderer(config); + const results = await prerenderer.start({ + ssrAppFilePath, + componentGraph, + srcIndexHtmlPath, + }); + + diagnostics.push(...results.diagnostics); + } catch (e: any) { + catchError(diagnostics, e); + } + + return diagnostics; +}; diff --git a/packages/cli/src/task-serve.ts b/packages/cli/src/task-serve.ts new file mode 100644 index 00000000000..38f339d97b8 --- /dev/null +++ b/packages/cli/src/task-serve.ts @@ -0,0 +1,37 @@ +import { isString } from '@stencil/core/compiler/utils'; +import { start } from '@stencil/dev-server'; +import type { ValidatedConfig } from '@stencil/core/compiler'; + +import type { ConfigFlags } from './config-flags'; + +export const taskServe = async (config: ValidatedConfig, flags: ConfigFlags) => { + config.suppressLogs = true; + + if (typeof flags.open === 'boolean') { + config.devServer.openBrowser = flags.open; + } + config.devServer.reloadStrategy = null; + config.devServer.initialLoadUrl = '/'; + config.devServer.websocket = false; + config.maxConcurrentWorkers = 1; + config.devServer.root = isString(flags.root) ? flags.root : config.sys.getCurrentDirectory(); + + if (!config.sys.onProcessInterrupt) { + throw new Error(`Environment doesn't provide required function: onProcessInterrupt`); + } + + const devServer = await start(config.devServer, config.logger); + + console.log(`${config.logger.cyan(' Root:')} ${devServer.root}`); + console.log(`${config.logger.cyan(' Address:')} ${devServer.address}`); + console.log(`${config.logger.cyan(' Port:')} ${devServer.port}`); + console.log(`${config.logger.cyan(' Server:')} ${devServer.browserUrl}`); + console.log(``); + + config.sys.onProcessInterrupt(() => { + if (devServer) { + config.logger.debug(`dev server close: ${devServer.browserUrl}`); + devServer.close(); + } + }); +}; diff --git a/src/cli/task-telemetry.ts b/packages/cli/src/task-telemetry.ts similarity index 75% rename from src/cli/task-telemetry.ts rename to packages/cli/src/task-telemetry.ts index 107ab9a3cf7..38e778424f9 100644 --- a/src/cli/task-telemetry.ts +++ b/packages/cli/src/task-telemetry.ts @@ -1,4 +1,5 @@ -import type * as d from '../declarations'; +import type * as d from '@stencil/core/compiler'; + import { ConfigFlags } from './config-flags'; import { checkTelemetry, disableTelemetry, enableTelemetry } from './telemetry/telemetry'; @@ -9,7 +10,11 @@ import { checkTelemetry, disableTelemetry, enableTelemetry } from './telemetry/t * @param sys the abstraction for interfacing with the operating system * @param logger a logging implementation to log the results out to the user */ -export const taskTelemetry = async (flags: ConfigFlags, sys: d.CompilerSystem, logger: d.Logger): Promise => { +export const taskTelemetry = async ( + flags: ConfigFlags, + sys: d.CompilerSystem, + logger: d.Logger, +): Promise => { const prompt = logger.dim(sys.details?.platform === 'windows' ? '>' : '$'); const isEnabling = flags.args.includes('on'); const isDisabling = flags.args.includes('off'); @@ -23,17 +28,21 @@ export const taskTelemetry = async (flags: ConfigFlags, sys: d.CompilerSystem, l if (isEnabling) { const result = await enableTelemetry(sys); - result - ? console.log(`\n ${logger.bold('Telemetry is now ') + ENABLED_MESSAGE}`) - : console.log(`Something went wrong when enabling Telemetry.`); + if (result) { + console.log(`\n ${logger.bold('Telemetry is now ') + ENABLED_MESSAGE}`); + } else { + console.log(`Something went wrong when enabling Telemetry.`); + } return; } if (isDisabling) { const result = await disableTelemetry(sys); - result - ? console.log(`\n ${logger.bold('Telemetry is now ') + DISABLED_MESSAGE}`) - : console.log(`Something went wrong when disabling Telemetry.`); + if (result) { + console.log(`\n ${logger.bold('Telemetry is now ') + DISABLED_MESSAGE}`); + } else { + console.log(`Something went wrong when disabling Telemetry.`); + } return; } diff --git a/packages/cli/src/task-watch.ts b/packages/cli/src/task-watch.ts new file mode 100644 index 00000000000..00555936c99 --- /dev/null +++ b/packages/cli/src/task-watch.ts @@ -0,0 +1,71 @@ +import type { DevServer, ValidatedConfig } from '@stencil/core/compiler'; + +import { printCheckVersionResults, startCheckVersion } from './check-version'; +import { startupCompilerLog } from './logs'; +import type { ConfigFlags } from './config-flags'; +import type { CoreCompiler } from './load-compiler'; + +export const taskWatch = async ( + coreCompiler: CoreCompiler, + config: ValidatedConfig, + flags: ConfigFlags, +) => { + let devServer: DevServer | null = null; + let exitCode = 0; + + try { + startupCompilerLog(coreCompiler, config); + + const versionChecker = startCheckVersion(config, coreCompiler.version, flags); + + const compiler = await coreCompiler.createCompiler(config); + const watcher = await compiler.createWatcher(); + + if (!config.sys.onProcessInterrupt) { + throw new Error(`Environment doesn't provide required function: onProcessInterrupt`); + } + + if (flags.serve) { + const { start } = await import('@stencil/dev-server'); + devServer = await start(config.devServer, config.logger, watcher); + } + + config.sys.onProcessInterrupt(() => { + config.logger.debug(`close watch`); + if (compiler) { + compiler.destroy(); + } + }); + + const rmVersionCheckerLog = watcher.on('buildFinish', async () => { + // log the version check one time + rmVersionCheckerLog(); + printCheckVersionResults(versionChecker); + }); + + if (devServer) { + const rmDevServerLog = watcher.on('buildFinish', () => { + // log the dev server url one time + rmDevServerLog(); + const url = devServer?.browserUrl ?? 'UNKNOWN URL'; + config.logger.info(`${config.logger.cyan(url)}\n`); + }); + } + + const closeResults = await watcher.start(); + if (closeResults.exitCode > 0) { + exitCode = closeResults.exitCode; + } + } catch (e) { + exitCode = 1; + config.logger.error(e); + } + + if (devServer) { + await devServer.close(); + } + + if (exitCode > 0) { + return config.sys.exit(exitCode); + } +}; diff --git a/packages/cli/src/telemetry/_test_/helpers.spec.ts b/packages/cli/src/telemetry/_test_/helpers.spec.ts new file mode 100644 index 00000000000..c1add0eac9f --- /dev/null +++ b/packages/cli/src/telemetry/_test_/helpers.spec.ts @@ -0,0 +1,110 @@ +import { createSystem } from '@stencil/core/compiler'; +import { describe, it, expect } from 'vitest'; + +import { ConfigFlags, createConfigFlags } from '../../config-flags'; +import { hasDebug, hasVerbose, isInteractive, tryFn, uuidv4 } from '../helpers'; + +describe('hasDebug', () => { + it('returns true when the "debug" flag is true', () => { + const flags = createConfigFlags({ + debug: true, + }); + + expect(hasDebug(flags)).toBe(true); + }); + + it('returns false when the "debug" flag is false', () => { + const flags = createConfigFlags({ + debug: false, + }); + + expect(hasDebug(flags)).toBe(false); + }); + + it('returns false when a flag is not passed', () => { + const flags = createConfigFlags({}); + + expect(hasDebug(flags)).toBe(false); + }); +}); + +describe('hasVerbose', () => { + it.each>([ + { debug: true, verbose: false }, + { debug: false, verbose: true }, + { debug: false, verbose: false }, + ])('returns false when debug=$debug and verbose=$verbose', (flagOverrides) => { + const flags = createConfigFlags(flagOverrides); + + expect(hasVerbose(flags)).toBe(false); + }); + + it('returns true when debug=true and verbose=true', () => { + const flags = createConfigFlags({ + debug: true, + verbose: true, + }); + + expect(hasVerbose(flags)).toBe(true); + }); +}); + +describe('uuidv4', () => { + it('outputs a UUID', () => { + const pattern = new RegExp( + /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + ); + const uuid = uuidv4(); + expect(!!uuid.match(pattern)).toBe(true); + }); +}); + +describe('isInteractive', () => { + const sys = createSystem(); + + it('returns false by default', () => { + const result = isInteractive(sys, createConfigFlags({ ci: false }), { ci: false, tty: false }); + expect(result).toBe(false); + }); + + it('returns false when tty is false', () => { + const result = isInteractive(sys, createConfigFlags({ ci: true }), { ci: true, tty: false }); + expect(result).toBe(false); + }); + + it('returns false when ci is true', () => { + const result = isInteractive(sys, createConfigFlags({ ci: true }), { ci: true, tty: true }); + expect(result).toBe(false); + }); + + it('returns true when tty is true and ci is false', () => { + const result = isInteractive(sys, createConfigFlags({ ci: false }), { ci: false, tty: true }); + expect(result).toBe(true); + }); +}); + +describe('tryFn', () => { + it('handles failures correctly', async () => { + const result = await tryFn(async () => { + throw new Error('Uh oh!'); + }); + + expect(result).toBe(null); + }); + + it('handles success correctly', async () => { + const result = await tryFn(async () => { + return true; + }); + + expect(result).toBe(true); + }); + + it('handles returning false correctly', async () => { + const result = await tryFn(async () => { + return false; + }); + + expect(result).toBe(false); + }); +}); diff --git a/src/cli/telemetry/test/telemetry.spec.ts b/packages/cli/src/telemetry/_test_/telemetry.spec.ts similarity index 81% rename from src/cli/telemetry/test/telemetry.spec.ts rename to packages/cli/src/telemetry/_test_/telemetry.spec.ts index bc51528ea26..67fdbd95f4c 100644 --- a/src/cli/telemetry/test/telemetry.spec.ts +++ b/packages/cli/src/telemetry/_test_/telemetry.spec.ts @@ -1,10 +1,11 @@ import * as coreCompiler from '@stencil/core/compiler'; +import { createSystem } from '@stencil/core/compiler'; +import { LOADER_BUNDLE, STANDALONE, SSR, WWW } from '@stencil/core/compiler/utils'; import { mockValidatedConfig } from '@stencil/core/testing'; -import { DIST, DIST_CUSTOM_ELEMENTS, DIST_HYDRATE_SCRIPT, WWW } from '@utils'; +import { vi, describe, it, beforeEach, expect } from 'vitest'; +import type * as d from '@stencil/core/compiler'; -import { createConfigFlags } from '../../../cli/config-flags'; -import { createSystem } from '../../../compiler/sys/stencil-sys'; -import type * as d from '../../../declarations'; +import { createConfigFlags } from '../../config-flags'; import * as shouldTrack from '../shouldTrack'; import * as telemetry from '../telemetry'; import { anonymizeConfigForTelemetry } from '../telemetry'; @@ -12,18 +13,18 @@ import { anonymizeConfigForTelemetry } from '../telemetry'; describe('telemetryBuildFinishedAction', () => { let config: d.ValidatedConfig; let sys: d.CompilerSystem; + const flags = createConfigFlags({ task: 'build' }); beforeEach(() => { sys = createSystem(); config = mockValidatedConfig({ - flags: createConfigFlags({ task: 'build' }), outputTargets: [], sys, }); }); it('issues a network request when complete', async () => { - const spyShouldTrack = jest.spyOn(shouldTrack, 'shouldTrack'); + const spyShouldTrack = vi.spyOn(shouldTrack, 'shouldTrack'); spyShouldTrack.mockReturnValue( new Promise((resolve) => { resolve(true); @@ -35,7 +36,7 @@ describe('telemetryBuildFinishedAction', () => { duration: 100, } as d.CompilerBuildResults; - await telemetry.telemetryBuildFinishedAction(sys, config, coreCompiler, results); + await telemetry.telemetryBuildFinishedAction(sys, config, coreCompiler, results, flags); expect(spyShouldTrack).toHaveBeenCalled(); spyShouldTrack.mockRestore(); @@ -45,39 +46,39 @@ describe('telemetryBuildFinishedAction', () => { describe('telemetryAction', () => { let config: d.ValidatedConfig; let sys: d.CompilerSystem; + const flags = createConfigFlags({ task: 'build' }); beforeEach(() => { sys = createSystem(); config = mockValidatedConfig({ - flags: createConfigFlags({ task: 'build' }), outputTargets: [], sys, }); }); it('issues a network request when no async function is passed', async () => { - const spyShouldTrack = jest.spyOn(shouldTrack, 'shouldTrack'); + const spyShouldTrack = vi.spyOn(shouldTrack, 'shouldTrack'); spyShouldTrack.mockReturnValue( new Promise((resolve) => { resolve(true); }), ); - await telemetry.telemetryAction(sys, config, coreCompiler, () => {}); + await telemetry.telemetryAction(sys, config, coreCompiler, flags, () => {}); expect(spyShouldTrack).toHaveBeenCalled(); spyShouldTrack.mockRestore(); }); it('issues a network request when passed async function is complete', async () => { - const spyShouldTrack = jest.spyOn(shouldTrack, 'shouldTrack'); + const spyShouldTrack = vi.spyOn(shouldTrack, 'shouldTrack'); spyShouldTrack.mockReturnValue( new Promise((resolve) => { resolve(true); }), ); - await telemetry.telemetryAction(sys, config, coreCompiler, async () => { + await telemetry.telemetryAction(sys, config, coreCompiler, flags, async () => { new Promise((resolve) => { setTimeout(() => { resolve(true); @@ -139,7 +140,9 @@ describe('hasAppTarget()', () => { }); it("returns 'true' when `outputTargets` contains `www` with serviceWorker and baseUrl", () => { - config.outputTargets = [{ type: WWW, baseUrl: 'https://example.com', serviceWorker: { swDest: './tmp' } }]; + config.outputTargets = [ + { type: WWW, baseUrl: 'https://example.com', serviceWorker: { swDest: './tmp' } }, + ]; expect(telemetry.hasAppTarget(config)).toBe(true); }); }); @@ -147,6 +150,7 @@ describe('hasAppTarget()', () => { describe('prepareData', () => { let config: d.ValidatedConfig; let sys: d.CompilerSystem; + const flags = createConfigFlags({}); beforeEach(() => { config = mockValidatedConfig(); @@ -157,7 +161,7 @@ describe('prepareData', () => { }); it('prepares an object to send to ionic', async () => { - const data = await telemetry.prepareData(coreCompiler, config, sys, 1000); + const data = await telemetry.prepareData(coreCompiler, config, sys, flags, 1000); expect(data).toEqual({ arguments: [], build: coreCompiler.buildId, @@ -171,7 +175,7 @@ describe('prepareData', () => { os_version: '', packages: [], packages_no_versions: [], - rollup: coreCompiler.versions.rollup, + rolldown: coreCompiler.versions.rolldown, stencil: coreCompiler.versions.stencil, system: 'in-memory __VERSION:STENCIL__', system_major: 'in-memory __VERSION:STENCIL__', @@ -188,7 +192,7 @@ describe('prepareData', () => { outputTargets: [{ type: 'www', baseUrl: 'https://example.com' }], }); - const data = await telemetry.prepareData(coreCompiler, config, sys, 1000); + const data = await telemetry.prepareData(coreCompiler, config, sys, flags, 1000); expect(data.has_app_pwa_config).toBe(true); }); @@ -198,7 +202,7 @@ describe('prepareData', () => { outputTargets: [{ type: 'www', serviceWorker: { swDest: './tmp' } }], }); - const data = await telemetry.prepareData(coreCompiler, config, sys, 1000); + const data = await telemetry.prepareData(coreCompiler, config, sys, flags, 1000); expect(data.has_app_pwa_config).toBe(true); }); @@ -211,7 +215,14 @@ describe('prepareData', () => { outputTargets: [{ type: 'www' }], }); - const data = await telemetry.prepareData(coreCompiler, config, sys, 1000, COMPONENT_COUNT); + const data = await telemetry.prepareData( + coreCompiler, + config, + sys, + flags, + 1000, + COMPONENT_COUNT, + ); expect(data.component_count).toEqual(COMPONENT_COUNT); }); @@ -248,16 +259,18 @@ describe('anonymizeConfigForTelemetry', () => { }); it.each([ - 'commonjs', 'devServer', 'env', 'logger', - 'rollupConfig', + 'rolldownConfig', 'sys', - 'testing', 'tsCompilerOptions', ])("should remove objects under prop '%s'", (prop: keyof d.ValidatedConfig) => { - const anonymizedConfig = anonymizeConfigForTelemetry({ ...config, [prop]: {}, outputTargets: [] }); + const anonymizedConfig = anonymizeConfigForTelemetry({ + ...config, + [prop]: {}, + outputTargets: [], + }); expect(anonymizedConfig.hasOwnProperty(prop)).toBe(false); expect(anonymizedConfig.outputTargets).toEqual([]); }); @@ -267,19 +280,17 @@ describe('anonymizeConfigForTelemetry', () => { ...config, outputTargets: [ { type: WWW, baseUrl: 'https://example.com' }, - { type: DIST_HYDRATE_SCRIPT, external: ['beep', 'boop'], dir: 'shoud/go/away' }, - { type: DIST_CUSTOM_ELEMENTS }, - { type: DIST_CUSTOM_ELEMENTS, generateTypeDeclarations: true }, - { type: DIST, typesDir: 'my-types' }, + { type: SSR, external: ['beep', 'boop'], dir: 'shoud/go/away' }, + { type: STANDALONE }, + { type: LOADER_BUNDLE }, ], }); expect(anonymizedConfig.outputTargets).toEqual([ { type: WWW, baseUrl: 'omitted' }, - { type: DIST_HYDRATE_SCRIPT, external: ['beep', 'boop'], dir: 'omitted' }, - { type: DIST_CUSTOM_ELEMENTS }, - { type: DIST_CUSTOM_ELEMENTS, generateTypeDeclarations: true }, - { type: DIST, typesDir: 'omitted' }, + { type: SSR, external: ['beep', 'boop'], dir: 'omitted' }, + { type: STANDALONE }, + { type: LOADER_BUNDLE }, ]); }); }); diff --git a/packages/cli/src/telemetry/helpers.ts b/packages/cli/src/telemetry/helpers.ts new file mode 100644 index 00000000000..ec6a413553a --- /dev/null +++ b/packages/cli/src/telemetry/helpers.ts @@ -0,0 +1,96 @@ +import type * as d from '@stencil/core/compiler'; + +import { ConfigFlags } from '../config-flags'; + +interface TerminalInfo { + /** + * Whether this is in CI or not. + */ + readonly ci: boolean; + /** + * Whether the terminal is an interactive TTY or not. + */ + readonly tty: boolean; +} + +export interface TelemetryConfig { + 'telemetry.stencil'?: boolean; + 'tokens.telemetry'?: string; +} + +export const tryFn = async ( + fn: (...args: any[]) => Promise, + ...args: any[] +): Promise => { + try { + return await fn(...args); + } catch { + // ignore + } + + return null; +}; + +export const isInteractive = ( + sys: d.CompilerSystem, + flags: ConfigFlags, + object?: TerminalInfo, +): boolean => { + const terminalInfo = + object || + Object.freeze({ + tty: sys.isTTY(), + ci: + ['CI', 'BUILD_ID', 'BUILD_NUMBER', 'BITBUCKET_COMMIT', 'CODEBUILD_BUILD_ARN'].filter( + (v) => !!sys.getEnvironmentVar?.(v), + ).length > 0 || !!flags.ci, + }); + + return terminalInfo.tty && !terminalInfo.ci; +}; + +export const UUID_REGEX = new RegExp( + /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, +); + +// Plucked from https://github.com/ionic-team/capacitor/blob/b893a57aaaf3a16e13db9c33037a12f1a5ac92e0/cli/src/util/uuid.ts +export function uuidv4(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c == 'x' ? r : (r & 0x3) | 0x8; + + return v.toString(16); + }); +} + +/** + * Reads and parses a JSON file from the given `path` + * @param sys The system where the command is invoked + * @param path the path on the file system to read and parse + * @returns the parsed JSON + */ +export async function readJson( + sys: d.CompilerSystem, + path: string, +): Promise { + const file = await sys.readFile(path); + return file ? (JSON.parse(file) as T) : null; +} + +/** + * Does the command have the debug flag? + * @param flags The configuration flags passed into the Stencil command + * @returns true if --debug has been passed, otherwise false + */ +export function hasDebug(flags: ConfigFlags): boolean { + return !!flags.debug; +} + +/** + * Does the command have the verbose and debug flags? + * @param flags The configuration flags passed into the Stencil command + * @returns true if both --debug and --verbose have been passed, otherwise false + */ +export function hasVerbose(flags: ConfigFlags): boolean { + return !!flags.verbose && hasDebug(flags); +} diff --git a/packages/cli/src/telemetry/shouldTrack.ts b/packages/cli/src/telemetry/shouldTrack.ts new file mode 100644 index 00000000000..461ce605c21 --- /dev/null +++ b/packages/cli/src/telemetry/shouldTrack.ts @@ -0,0 +1,16 @@ +import * as d from '@stencil/core/compiler'; + +import { isInteractive } from './helpers'; +import { checkTelemetry } from './telemetry'; +import type { ConfigFlags } from '../config-flags'; + +/** + * Used to determine if tracking should occur. + * @param sys The system where the command is invoked + * @param flags The CLI flags (owned by CLI, not part of core config) + * @param ci whether or not the process is running in a Continuous Integration (CI) environment + * @returns true if telemetry should be sent, false otherwise + */ +export async function shouldTrack(sys: d.CompilerSystem, flags: ConfigFlags, ci?: boolean) { + return !ci && isInteractive(sys, flags) && (await checkTelemetry(sys)); +} diff --git a/src/cli/telemetry/telemetry.ts b/packages/cli/src/telemetry/telemetry.ts similarity index 75% rename from src/cli/telemetry/telemetry.ts rename to packages/cli/src/telemetry/telemetry.ts index d50f9b082b1..79bfad90ba3 100644 --- a/src/cli/telemetry/telemetry.ts +++ b/packages/cli/src/telemetry/telemetry.ts @@ -1,10 +1,47 @@ -import { isOutputTargetHydrate, WWW } from '@utils'; +import { isOutputTargetSsr, isOutputTargetWww } from '@stencil/core/compiler/utils'; +import type * as d from '@stencil/core/compiler'; -import type * as d from '../../declarations'; import { readConfig, updateConfig, writeConfig } from '../ionic-config'; import { CoreCompiler } from '../load-compiler'; import { hasDebug, hasVerbose, readJson, tryFn, uuidv4 } from './helpers'; import { shouldTrack } from './shouldTrack'; +import type { ConfigFlags } from '../config-flags'; +import type { TaskCommand } from '../types'; + +type TelemetryCallback = (...args: any[]) => void | Promise; + +interface Metric { + name: string; + timestamp: string; + source: 'stencil_cli'; + value: TrackableData; + session_id: string; +} + +/** + * The model for the data that's tracked. + */ +interface TrackableData { + arguments: string[]; + build: string; + component_count?: number; + config: d.Config; + cpu_model: string | undefined; + duration_ms: number | undefined; + has_app_pwa_config: boolean; + os_name: string | undefined; + os_version: string | undefined; + packages: string[]; + packages_no_versions?: string[]; + rolldown: string; + stencil: string; + system: string; + system_major?: string; + targets: string[]; + task: TaskCommand | null; + typescript: string; + yarn: boolean; +} /** * Used to within taskBuild to provide the component_count property. @@ -13,26 +50,39 @@ import { shouldTrack } from './shouldTrack'; * @param config The config passed into the Stencil command * @param coreCompiler The compiler used to do builds * @param result The results of a compiler build. + * @param flags The CLI flags (owned by CLI, not part of core config) */ export async function telemetryBuildFinishedAction( sys: d.CompilerSystem, config: d.ValidatedConfig, coreCompiler: CoreCompiler, result: d.CompilerBuildResults, + flags: ConfigFlags, ) { - const tracking = await shouldTrack(config, sys, !!config.flags.ci); + const tracking = await shouldTrack(sys, flags, !!flags.ci); if (!tracking) { return; } - const component_count = result.componentGraph ? Object.keys(result.componentGraph).length : undefined; + const component_count = result.componentGraph + ? Object.keys(result.componentGraph).length + : undefined; - const data = await prepareData(coreCompiler, config, sys, result.duration, component_count); + const data = await prepareData( + coreCompiler, + config, + sys, + flags, + result.duration, + component_count, + ); - await sendMetric(sys, config, 'stencil_cli_command', data); + await sendMetric(sys, flags, 'stencil_cli_command', data); - config.logger.debug(`${config.logger.blue('Telemetry')}: ${config.logger.gray(JSON.stringify(data))}`); + config.logger.debug( + `${config.logger.blue('Telemetry')}: ${config.logger.gray(JSON.stringify(data))}`, + ); } /** @@ -41,6 +91,7 @@ export async function telemetryBuildFinishedAction( * @param sys The system where the command is invoked * @param config The config passed into the Stencil command * @param coreCompiler The compiler used to do builds + * @param flags The CLI flags (owned by CLI, not part of core config) * @param action A Promise-based function to call in order to get the duration of any given command. * @returns void */ @@ -48,11 +99,12 @@ export async function telemetryAction( sys: d.CompilerSystem, config: d.ValidatedConfig, coreCompiler: CoreCompiler, - action?: d.TelemetryCallback, + flags: ConfigFlags, + action?: TelemetryCallback, ) { - const tracking = await shouldTrack(config, sys, !!config.flags.ci); + const tracking = await shouldTrack(sys, flags, !!flags.ci); - let duration = undefined; + let duration: number | undefined = undefined; let error: any; if (action) { @@ -69,14 +121,16 @@ export async function telemetryAction( } // We'll get componentCount details inside the taskBuild, so let's not send two messages. - if (!tracking || (config.flags.task == 'build' && !config.flags.args.includes('--watch'))) { + if (!tracking || (flags.task == 'build' && !flags.args.includes('--watch'))) { return; } - const data = await prepareData(coreCompiler, config, sys, duration); + const data = await prepareData(coreCompiler, config, sys, flags, duration); - await sendMetric(sys, config, 'stencil_cli_command', data); - config.logger.debug(`${config.logger.blue('Telemetry')}: ${config.logger.gray(JSON.stringify(data))}`); + await sendMetric(sys, flags, 'stencil_cli_command', data); + config.logger.debug( + `${config.logger.blue('Telemetry')}: ${config.logger.gray(JSON.stringify(data))}`, + ); if (error) { throw error; @@ -94,11 +148,13 @@ export async function telemetryAction( */ export function hasAppTarget(config: d.ValidatedConfig): boolean { return config.outputTargets.some( - (target) => target.type === WWW && (!!target.serviceWorker || (!!target.baseUrl && target.baseUrl !== '/')), + (target) => + isOutputTargetWww(target) && + (!!target.serviceWorker || (!!target.baseUrl && target.baseUrl !== '/')), ); } -export function isUsingYarn(sys: d.CompilerSystem) { +function isUsingYarn(sys: d.CompilerSystem) { return sys.getEnvironmentVar?.('npm_execpath')?.includes('yarn') || false; } @@ -110,7 +166,7 @@ export function isUsingYarn(sys: d.CompilerSystem) { * @param config the configuration used by the Stencil project * @returns a unique list of output target types found in the Stencil configuration */ -export function getActiveTargets(config: d.ValidatedConfig): string[] { +function getActiveTargets(config: d.ValidatedConfig): string[] { const result = config.outputTargets.map((t) => t.type); return Array.from(new Set(result)); } @@ -121,6 +177,7 @@ export function getActiveTargets(config: d.ValidatedConfig): string[] { * @param coreCompiler the core compiler * @param config the current Stencil config * @param sys the compiler system instance in use + * @param flags the CLI flags (owned by CLI, not part of core config) * @param duration_ms the duration of the action being tracked * @param component_count the number of components being built (optional) * @returns a Promise wrapping data for the telemetry endpoint @@ -129,11 +186,15 @@ export const prepareData = async ( coreCompiler: CoreCompiler, config: d.ValidatedConfig, sys: d.CompilerSystem, + flags: ConfigFlags, duration_ms: number | undefined, component_count: number | undefined = undefined, -): Promise => { - const { typescript, rollup } = coreCompiler.versions || { typescript: 'unknown', rollup: 'unknown' }; - const { packages, packagesNoVersions } = await getInstalledPackages(sys, config); +): Promise => { + const { typescript, rolldown } = coreCompiler.versions || { + typescript: 'unknown', + rolldown: 'unknown', + }; + const { packages, packagesNoVersions } = await getInstalledPackages(sys, flags); const targets = getActiveTargets(config); const yarn = isUsingYarn(sys); const stencil = coreCompiler.version || 'unknown'; @@ -146,7 +207,7 @@ export const prepareData = async ( const anonymizedConfig = anonymizeConfigForTelemetry(config); return { - arguments: config.flags.args, + arguments: flags.args, build, component_count, config: anonymizedConfig, @@ -157,12 +218,12 @@ export const prepareData = async ( os_version, packages, packages_no_versions: packagesNoVersions, - rollup, + rolldown, stencil, system, system_major: getMajorVersion(system), targets, - task: config.flags.task, + task: flags.task, typescript, yarn, }; @@ -199,13 +260,11 @@ const CONFIG_PROPS_TO_ANONYMIZE: ReadonlyArray = [ // // TODO(STENCIL-469): Investigate improving anonymization for tsCompilerOptions and devServer const CONFIG_PROPS_TO_DELETE: ReadonlyArray = [ - 'commonjs', 'devServer', 'env', 'logger', - 'rollupConfig', + 'rolldownConfig', 'sys', - 'testing', 'tsCompilerOptions', ]; @@ -249,7 +308,7 @@ export const anonymizeConfigForTelemetry = (config: d.ValidatedConfig): d.Config // members, giving us `["omitted", "omitted", ...]`. // // Instead, we check for its presence and manually copy over. - if (isOutputTargetHydrate(target) && target.external) { + if (isOutputTargetSsr(target) && target.external) { anonymizedOT['external'] = target.external.concat(); } return anonymizedOT; @@ -269,12 +328,12 @@ export const anonymizeConfigForTelemetry = (config: d.ValidatedConfig): d.Config * of each package under the @stencil, @ionic, and @capacitor scopes. * * @param sys the system instance where telemetry is invoked - * @param config the Stencil configuration associated with the current task that triggered telemetry + * @param flags the CLI flags (owned by CLI, not part of core config) * @returns an object listing all dev and production dependencies under the aforementioned scopes */ async function getInstalledPackages( sys: d.CompilerSystem, - config: d.ValidatedConfig, + flags: ConfigFlags, ): Promise<{ packages: string[]; packagesNoVersions: string[] }> { let packages: string[] = []; let packagesNoVersions: string[] = []; @@ -307,8 +366,10 @@ async function getInstalledPackages( ); try { - packages = yarn ? await yarnPackages(sys, ionicPackages) : await npmPackages(sys, ionicPackages); - } catch (e) { + packages = yarn + ? await yarnPackages(sys, ionicPackages) + : await npmPackages(sys, ionicPackages); + } catch { packages = ionicPackages.map(([k, v]) => `${k}@${v.replace('^', '')}`); } @@ -316,7 +377,9 @@ async function getInstalledPackages( return { packages, packagesNoVersions }; } catch (err) { - hasDebug(config.flags) && console.error(err); + if (hasDebug(flags)) { + console.error(err); + } return { packages, packagesNoVersions }; } } @@ -327,12 +390,22 @@ async function getInstalledPackages( * @param ionicPackages a list of the found packages matching `@stencil`, `@capacitor`, or `@ionic` from the package.json file. * @returns an array of strings of all the packages and their versions. */ -async function npmPackages(sys: d.CompilerSystem, ionicPackages: [string, string][]): Promise { +async function npmPackages( + sys: d.CompilerSystem, + ionicPackages: [string, string][], +): Promise { const appRootDir = sys.getCurrentDirectory(); - const packageLockJson: any = await tryFn(readJson, sys, sys.resolvePath(appRootDir + '/package-lock.json')); + const packageLockJson: any = await tryFn( + readJson, + sys, + sys.resolvePath(appRootDir + '/package-lock.json'), + ); return ionicPackages.map(([k, v]) => { - let version = packageLockJson?.dependencies[k]?.version ?? packageLockJson?.devDependencies[k]?.version ?? v; + let version = + packageLockJson?.dependencies[k]?.version ?? + packageLockJson?.devDependencies[k]?.version ?? + v; version = version.includes('file:') ? sanitizeDeclaredVersion(v) : version; return `${k}@${version}`; }); @@ -344,7 +417,10 @@ async function npmPackages(sys: d.CompilerSystem, ionicPackages: [string, string * @param ionicPackages a list of the found packages matching `@stencil`, `@capacitor`, or `@ionic` from the package.json file. * @returns an array of strings of all the packages and their versions. */ -async function yarnPackages(sys: d.CompilerSystem, ionicPackages: [string, string][]): Promise { +async function yarnPackages( + sys: d.CompilerSystem, + ionicPackages: [string, string][], +): Promise { const appRootDir = sys.getCurrentDirectory(); const yarnLock = sys.readFileSync(sys.resolvePath(appRootDir + '/yarn.lock')); const yarnLockYml = sys.parseYarnLockFile?.(yarnLock); @@ -352,7 +428,10 @@ async function yarnPackages(sys: d.CompilerSystem, ionicPackages: [string, strin return ionicPackages.map(([k, v]) => { const identifiedVersion = `${k}@${v}`; let version = yarnLockYml?.object[identifiedVersion]?.version; - version = version && version.includes('undefined') ? sanitizeDeclaredVersion(identifiedVersion) : version; + version = + version && version.includes('undefined') + ? sanitizeDeclaredVersion(identifiedVersion) + : version; return `${k}@${version}`; }); } @@ -371,20 +450,20 @@ function sanitizeDeclaredVersion(version: string): string { * If telemetry is enabled, send a metric to an external data store * * @param sys the system instance where telemetry is invoked - * @param config the Stencil configuration associated with the current task that triggered telemetry + * @param flags the CLI flags (owned by CLI, not part of core config) * @param name the name of a trackable metric. Note this name is not necessarily a scalar value to track, like * "Stencil Version". For example, "stencil_cli_command" is a name that is used to track all CLI command information. * @param value the data to send to the external data store under the provided name argument */ -export async function sendMetric( +async function sendMetric( sys: d.CompilerSystem, - config: d.ValidatedConfig, + flags: ConfigFlags, name: string, - value: d.TrackableData, + value: TrackableData, ): Promise { const session_id = await getTelemetryToken(sys); - const message: d.Metric = { + const message: Metric = { name, timestamp: new Date().toISOString(), source: 'stencil_cli', @@ -392,7 +471,7 @@ export async function sendMetric( session_id, }; - await sendTelemetry(sys, config, message); + await sendTelemetry(sys, flags, message); } /** @@ -413,10 +492,14 @@ async function getTelemetryToken(sys: d.CompilerSystem) { /** * Issues a request to the telemetry server. * @param sys The system where the command is invoked - * @param config The config passed into the Stencil command + * @param flags The CLI flags (owned by CLI, not part of core config) * @param data Data to be tracked */ -async function sendTelemetry(sys: d.CompilerSystem, config: d.ValidatedConfig, data: d.Metric): Promise { +async function sendTelemetry( + sys: d.CompilerSystem, + flags: ConfigFlags, + data: Metric, +): Promise { try { const now = new Date().toISOString(); @@ -438,15 +521,26 @@ async function sendTelemetry(sys: d.CompilerSystem, config: d.ValidatedConfig, d body: JSON.stringify(body), }); - hasVerbose(config.flags) && - console.debug('\nSent %O metric to events service (status: %O)', data.name, response.status, '\n'); + if (hasVerbose(flags)) { + console.debug( + '\nSent %O metric to events service (status: %O)', + data.name, + response.status, + '\n', + ); + } - if (response.status !== 204) { - hasVerbose(config.flags) && - console.debug('\nBad response from events service. Request body: %O', response.body.toString(), '\n'); + if (response.status !== 204 && hasVerbose(flags)) { + console.debug( + '\nBad response from events service. Request body: %O', + response.body.toString(), + '\n', + ); } } catch (e) { - hasVerbose(config.flags) && console.debug('Telemetry request failed:', e); + if (hasVerbose(flags)) { + console.debug('Telemetry request failed:', e); + } } } diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts new file mode 100644 index 00000000000..62b22a5b0ec --- /dev/null +++ b/packages/cli/src/types.ts @@ -0,0 +1,16 @@ +/** + * Supported CLI task commands + */ +export type TaskCommand = + | 'build' + | 'docs' + | 'generate' + | 'g' + | 'help' + | 'info' + | 'migrate' + | 'prerender' + | 'serve' + | 'telemetry' + | 'test' + | 'version'; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 00000000000..d875ecc3f03 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "alwaysStrict": true, + "declaration": true, + "emitDeclarationOnly": true, + "declarationDir": "dist", + "rootDir": "src", + "outDir": "dist", + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "lib": ["dom", "es2021"], + "module": "esnext", + "moduleResolution": "bundler", + "noImplicitAny": false, + "noImplicitOverride": false, + "noImplicitReturns": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": false, + "target": "es2022", + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "dist"] +} diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts new file mode 100644 index 00000000000..c0738f23e4a --- /dev/null +++ b/packages/cli/tsdown.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: ['src/index.ts'], + outDir: 'dist', + format: ['esm'], + platform: 'node', + target: 'node22', + dts: true, + clean: true, + deps: { + neverBundle: [/^node:/, 'typescript'], + }, +}); diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts new file mode 100644 index 00000000000..8220b9b2553 --- /dev/null +++ b/packages/cli/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // globals: true, + include: ['src/**/*.spec.ts'], + }, +}); diff --git a/packages/core/bin/stencil.mjs b/packages/core/bin/stencil.mjs new file mode 100755 index 00000000000..a84878b7d57 --- /dev/null +++ b/packages/core/bin/stencil.mjs @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import '@stencil/cli/cli'; diff --git a/packages/core/build/version-utils.ts b/packages/core/build/version-utils.ts new file mode 100644 index 00000000000..40a121611d4 --- /dev/null +++ b/packages/core/build/version-utils.ts @@ -0,0 +1,429 @@ +import { execSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * Build-time utilities for generating version info baked into the Stencil build. + * Used by tsdown.config.ts to create define replacements. + */ + +// Emoji pool for vermoji +const VERMOJIS = [ + '💯', + '☀️', + '☕️', + '♨️', + '✈️', + '✨', + '❄️', + '❤️', + '☎️', + '⚡️', + '⚽️', + '⚾️', + '⛄️', + '⛑', + '⛰', + '⛱', + '⛲️', + '⛳️', + '⛴', + '⛵️', + '⛷', + '⛸', + '⛹', + '⛺️', + '⭐️', + '🌀', + '🌁', + '🌃', + '🌄', + '🌅', + '🌇', + '🌈', + '🌍', + '🌎', + '🌏', + '🌐', + '🌙', + '🌜', + '🌝', + '🌞', + '🌟', + '🌪', + '🌭', + '🌮', + '🌯', + '🌱', + '🌲', + '🌳', + '🌴', + '🌵', + '🌶', + '🌷', + '🌸', + '🌹', + '🌺', + '🌻', + '🌼', + '🍀', + '🍁', + '🍅', + '🍇', + '🍈', + '🍉', + '🍊', + '🍋', + '🍌', + '🍍', + '🍎', + '🍏', + '🍐', + '🍒', + '🍓', + '🍔', + '🍕', + '🍖', + '🍗', + '🍜', + '🍝', + '🍞', + '🍟', + '🍡', + '🍣', + '🍤', + '🍦', + '🍧', + '🍨', + '🍩', + '🍪', + '🍫', + '🍬', + '🍭', + '🍮', + '🍯', + '🍰', + '🍲', + '🍵', + '🍷', + '🍸', + '🍹', + '🍺', + '🍻', + '🥃', + '🍾', + '🍿', + '🎀', + '🎁', + '🎂', + '🎆', + '🎇', + '🎈', + '🎉', + '🎊', + '🎖', + '🎙', + '🎠', + '🎡', + '🎢', + '🎤', + '🎨', + '🎩', + '🎪', + '🎬', + '🎭', + '🎯', + '🎰', + '🎱', + '🎲', + '🎳', + '🎷', + '🎸', + '🎹', + '🎺', + '🎻', + '🎾', + '🎿', + '🏀', + '🏁', + '🏂', + '🏃', + '🏄', + '🏅', + '🏆', + '🏇', + '🏈', + '🏉', + '🏊', + '🏋', + '🏌', + '🏍', + '🏎', + '🏏', + '🏐', + '🏑', + '🏒', + '🏓', + '🏔', + '🏕', + '🏖', + '🏙', + '🏜', + '🏝', + '🏰', + '🏵', + '🏸', + '🏹', + '🐁', + '🐂', + '🐄', + '🐅', + '🐆', + '🐇', + '🐈', + '🐉', + '🐊', + '🐋', + '🐌', + '🐍', + '🐎', + '🐏', + '🐐', + '🐒', + '🐓', + '🐔', + '🐕', + '🐖', + '🐗', + '🐘', + '🐙', + '🐚', + '🐛', + '🐝', + '🐞', + '🐟', + '🐠', + '🐡', + '🐣', + '🐤', + '🐥', + '🐦', + '🐧', + '🐨', + '🐩', + '🐫', + '🐬', + '🐭', + '🐮', + '🐯', + '🐰', + '🐱', + '🐳', + '🐴', + '🐵', + '🐶', + '🐷', + '🐸', + '🐹', + '🐺', + '🐻', + '🐼', + '🐽', + '🐿', + '👑', + '👒', + '👻', + '👽', + '👾', + '💍', + '💙', + '💚', + '💛', + '💡', + '💥', + '💪', + '💫', + '💾', + '💿', + '📌', + '📍', + '📟', + '🛰', + '📢', + '📣', + '📬', + '📷', + '📺', + '📻', + '🔈', + '🔋', + '🔔', + '🔥', + '🔬', + '🔭', + '🔮', + '🕊', + '🕹', + '🖍', + '🗻', + '😀', + '😃', + '😄', + '😈', + '😊', + '😋', + '😎', + '😛', + '😜', + '😸', + '🤓', + '🤖', + '🚀', + '🚁', + '🚂', + '🚃', + '🚅', + '🚋', + '🚌', + '🚍', + '🚎', + '🚐', + '🚑', + '🚒', + '🚓', + '🚔', + '🚕', + '🚖', + '🚗', + '🚘', + '🚙', + '🚚', + '🚛', + '🚜', + '🚞', + '🚟', + '🚠', + '🚡', + '🚢', + '🚣', + '🚤', + '🚦', + '🚨', + '🚩', + '🛠', + '🛥', + '🛩', + '🛳', + '🤘', + '🦀', + '🦁', + '🦂', + '🦃', + '🦄', + '🧀', +]; + +/** + * Generate a build identifier (epoch time in seconds) + * @returns the build identifier string + */ +export function getBuildId(): string { + return Date.now().toString(10).slice(0, -3); +} + +/** + * Get first 7 characters of current git SHA + * @returns the shortened git SHA + */ +export function getGitSha(): string { + try { + return execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim().slice(0, 7); + } catch { + return 'unknown'; + } +} + +/** + * Generate a dev version string: [BASE_VERSION]-dev.[BUILD_ID].[GIT_SHA] + * @param baseVersion - the base version to use + * @param buildId - the build identifier + * @returns the dev version string + */ +export function getDevVersion(baseVersion: string, buildId: string): string { + return `${baseVersion}-dev.${buildId}.${getGitSha()}`; +} + +/** + * Get a deterministic vermoji based on a hash string (e.g., buildId). + * Each unique buildId produces a consistent emoji. + * @param hash - the hash string to use for selecting the vermoji + * @returns the selected vermoji emoji + */ +export function getVermojiFromHash(hash: string): string { + let hashCode = 0; + for (let i = 0; i < hash.length; i++) { + const char = hash.charCodeAt(i); + hashCode = (hashCode << 5) - hashCode + char; + hashCode = hashCode & hashCode; // Convert to 32-bit integer + } + const index = Math.abs(hashCode) % VERMOJIS.length; + return VERMOJIS[index]; +} + +/** + * Get a random vermoji that hasn't been used in the changelog (for prod releases) + * @param changelogPath - path to the changelog file + * @returns the selected vermoji emoji + */ +export function getVermojiForRelease(changelogPath: string): string { + try { + const changelog = readFileSync(changelogPath, 'utf8'); + const available = VERMOJIS.filter((emoji) => !changelog.includes(emoji)); + if (available.length === 0) { + console.warn("We're out of Vermoji! Time to add more!"); + return '❓'; + } + return available[Math.floor(Math.random() * available.length)]; + } catch { + return '❓'; + } +} + +export interface BuildVersionInfo { + version: string; + buildId: string; + vermoji: string; +} + +/** + * Get all build-time version info for tsdown define replacements + * @param packageJsonPath - path to the package.json file + * @param isProd - whether this is a production build + * @returns the build version info object + */ +export function getBuildVersionInfo(packageJsonPath: string, isProd = false): BuildVersionInfo { + const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + const buildId = getBuildId(); + const baseVersion = pkg.version ?? '0.0.0'; + + const version = isProd ? baseVersion : getDevVersion(baseVersion, buildId); + + const vermoji = isProd + ? getVermojiForRelease(join(packageJsonPath, '../../CHANGELOG.md')) + : getVermojiFromHash(buildId); + + return { version, buildId, vermoji }; +} + +/** + * Create the define object for tsdown string replacements + * @param info - the build version info + * @returns a record of define replacements + */ +export function createDefines(info: BuildVersionInfo): Record { + return { + __STENCIL_VERSION__: JSON.stringify(info.version), + __STENCIL_BUILD_ID__: JSON.stringify(info.buildId), + __STENCIL_VERMOJI__: JSON.stringify(info.vermoji), + }; +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 00000000000..467388b4bf1 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,130 @@ +{ + "name": "@stencil/core", + "version": "5.0.0-alpha.5", + "description": "A Compiler for Web Components and Progressive Web Apps", + "keywords": [ + "components", + "custom elements", + "ionic", + "progressive web app", + "pwa", + "stencil", + "web components", + "webapp" + ], + "homepage": "https://stenciljs.com/", + "license": "MIT", + "author": "StencilJs Contributors", + "repository": { + "type": "git", + "url": "git+https://github.com/stenciljs/core.git" + }, + "bin": { + "stencil": "./bin/stencil.mjs" + }, + "files": [ + "bin/", + "dist/" + ], + "type": "module", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + }, + "./compiler": { + "types": "./dist/compiler/index.d.mts", + "import": "./dist/compiler/index.mjs" + }, + "./compiler/utils": { + "types": "./dist/compiler/utils/index.d.mts", + "import": "./dist/compiler/utils/index.mjs" + }, + "./jsx-runtime": { + "types": "./dist/jsx-runtime.d.mts", + "import": "./dist/jsx-runtime.mjs" + }, + "./jsx-dev-runtime": { + "types": "./dist/jsx-runtime.d.mts", + "import": "./dist/jsx-runtime.mjs" + }, + "./mock-doc": { + "types": "./dist/mock-doc.d.mts", + "import": "./dist/mock-doc.mjs", + "default": "./dist/mock-doc.mjs" + }, + "./runtime": { + "types": "./dist/runtime/index.d.ts", + "import": "./dist/runtime/index.js" + }, + "./runtime/app-data": { + "types": "./dist/runtime/app-data/index.d.ts", + "import": "./dist/runtime/app-data/index.js" + }, + "./runtime/app-globals": { + "types": "./dist/runtime/app-globals/index.d.ts", + "import": "./dist/runtime/app-globals/index.js" + }, + "./runtime/client": { + "types": "./dist/runtime/client/index.d.ts", + "import": "./dist/runtime/client/index.js" + }, + "./runtime/server": { + "types": "./dist/runtime/server/index.d.mts", + "import": "./dist/runtime/server/index.mjs" + }, + "./runtime/server/runner": { + "types": "./dist/runtime/server/runner.d.mts", + "import": "./dist/runtime/server/runner.mjs" + }, + "./sys/node": { + "types": "./dist/sys/node/index.d.mts", + "import": "./dist/sys/node/index.mjs" + }, + "./testing": { + "types": "./dist/testing/index.d.mts", + "import": "./dist/testing/index.mjs" + } + }, + "scripts": { + "build": "tsdown", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@parcel/watcher": "^2.5.1", + "@rollup/pluginutils": "^5.3.0", + "@stencil/cli": "workspace:*", + "@stencil/dev-server": "workspace:*", + "@stencil/mock-doc": "workspace:*", + "browserslist": "^4.24.0", + "chalk": "^5.6.2", + "css-what": "^7.0.0", + "lightningcss": "^1.32.0", + "magic-string": "^0.30.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.1", + "resolve": "^1.22.0", + "rolldown": "^1.0.0-rc.15", + "semver": "^7.7.4", + "terser": "5.37.0", + "tinyglobby": "^0.2.15", + "typescript": "catalog:" + }, + "devDependencies": { + "@ionic/prettier-config": "^4.0.0", + "@stencil/vitest": "catalog:", + "prettier": "^3.5.0", + "tsdown": "catalog:", + "vitest": "catalog:", + "vitest-environment-stencil": "catalog:" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/core/src/app-data/index.ts b/packages/core/src/app-data/index.ts new file mode 100644 index 00000000000..7bde5ebc477 --- /dev/null +++ b/packages/core/src/app-data/index.ts @@ -0,0 +1,105 @@ +import type { BuildConditionals } from '@stencil/core'; + +/** + * A collection of default build flags for a Stencil project. + * + * This collection can be found throughout the Stencil codebase, often imported from the `virtual:app-data` module like so: + * ```ts + * import { BUILD } from 'virtual:app-data'; + * ``` + * and is used to determine if a portion of the output of a Stencil _project_'s compilation step can be eliminated. + * + * e.g. When `BUILD.allRenderFn` evaluates to `false`, the compiler will eliminate conditional statements like: + * ```ts + * if (BUILD.allRenderFn) { + * // some code that will be eliminated if BUILD.allRenderFn is false + * } + * ``` + * + * `virtual:app-data`, the module that `BUILD` is imported from, is an alias for the `@stencil/core/runtime/app-data`, and is + * partially referenced by {@link STENCIL_APP_DATA_ID}. The `src/compiler/bundle/app-data-plugin.ts` references + * `STENCIL_APP_DATA_ID` uses it to replace these defaults with {@link BuildConditionals} that are derived from a + * Stencil project's contents (i.e. metadata from the components). This replacement happens at a Stencil project's + * compile time. Such code can be found at `src/compiler/app-core/app-data.ts`. + */ +export const BUILD: BuildConditionals = { + allRenderFn: false, + element: true, + event: true, + hasRenderFn: true, + hostListener: true, + hostListenerTargetWindow: true, + hostListenerTargetDocument: true, + hostListenerTargetBody: true, + hostListenerTarget: true, + member: true, + method: true, + mode: true, + observeAttribute: true, + prop: true, + propMutable: true, + reflect: true, + scoped: true, + shadowDom: true, + shadowModeClosed: false, + slot: true, + cssAnnotations: true, + state: true, + style: true, + formAssociated: false, + svg: true, + updatable: true, + vdomAttribute: true, + vdomXlink: true, + vdomClass: true, + vdomFunctional: true, + vdomKey: true, + vdomListener: true, + vdomRef: true, + vdomPropOrAttr: true, + vdomRender: true, + vdomStyle: true, + vdomText: true, + propChangeCallback: true, + taskQueue: true, + hotModuleReplacement: false, + isDebug: false, + isDev: false, + isTesting: false, + hydrateServerSide: false, + hydrateClientSide: false, + lifecycleDOMEvents: false, + lazyLoad: false, + profile: false, + slotRelocation: true, + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + appendChildSlotFix: false, + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + cloneNodeFix: false, + hydratedAttribute: false, + hydratedClass: true, + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + scopedSlotTextContentFix: false, + // TODO(STENCIL-854): Remove code related to legacy shadowDomShim field + shadowDomShim: false, + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + slotChildNodesFix: false, + invisiblePrehydration: true, + propBoolean: true, + propNumber: true, + propString: true, + constructableCSS: true, + devTools: false, + shadowDelegatesFocus: true, + shadowSlotAssignmentManual: false, + initializeNextTick: false, + asyncLoading: true, + asyncQueue: false, + attachStyles: true, + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + experimentalSlotFixes: false, +}; + +export const Env = {}; + +export const NAMESPACE = /* default */ 'app' as string; diff --git a/packages/core/src/app-data/readme.md b/packages/core/src/app-data/readme.md new file mode 100644 index 00000000000..5f9c2b8552d --- /dev/null +++ b/packages/core/src/app-data/readme.md @@ -0,0 +1,48 @@ +# app-data + +Build-time virtual module that provides compile-time constants to Stencil's runtime. + +## How It Works + +This directory contains **default values** that get replaced at build time by `src/compiler/bundle/app-data-plugin.ts`. When a Stencil project is compiled, the plugin generates project-specific values based on component metadata. + +## Exports + +### `BUILD` + +A `BuildConditionals` object used for dead-code elimination. The compiler analyzes your components and sets flags like `BUILD.shadowDom`, `BUILD.slot`, etc. based on what features are actually used. + +```ts +import { BUILD } from 'virtual:app-data'; + +if (BUILD.shadowDom) { + // This code is eliminated if no components use Shadow DOM +} +``` + +### `Env` + +User-defined environment variables from `stencil.config.ts`: + +```ts +// stencil.config.ts +export const config = { + env: { apiUrl: 'https://api.example.com' }, +}; + +// component +import { Env } from 'virtual:app-data'; +console.log(Env.apiUrl); +``` + +### `NAMESPACE` + +The project's namespace from config (defaults to `'app'`). + +## Why Defaults Exist + +The defaults in `index.ts` serve as: + +1. **TypeScript scaffolding** - enables type checking and IDE support +2. **Fallback values** - used when the plugin doesn't replace them +3. **Module resolution** - gives bundlers a real file to resolve \ No newline at end of file diff --git a/src/app-globals/index.ts b/packages/core/src/app-globals/index.ts similarity index 100% rename from src/app-globals/index.ts rename to packages/core/src/app-globals/index.ts diff --git a/packages/core/src/app-globals/readme.md b/packages/core/src/app-globals/readme.md new file mode 100644 index 00000000000..5621e5f8d4d --- /dev/null +++ b/packages/core/src/app-globals/readme.md @@ -0,0 +1,55 @@ +# app-globals + +Build-time virtual module that provides global scripts and styles to the runtime. + +## How It Works + +This directory contains **stub exports** that get replaced at build time by `src/compiler/bundle/app-data-plugin.ts`. The plugin generates the actual content based on your `stencil.config.ts` settings. + +## Exports + +### `globalScripts` + +A function that executes your project's global initialization script: + +```ts +// stencil.config.ts +export const config = { + globalScript: 'src/global.ts', +}; + +// src/global.ts +export default function () { + console.log('App initialized!'); +} +``` + +At build time, this becomes an import and invocation of your `globalScript` file. If multiple collections are used, all their global scripts are combined. + +### `globalStyles` + +A string containing compiled global CSS: + +```ts +// stencil.config.ts +export const config = { + globalStyle: 'src/global.css', +}; +``` + +At build time, the CSS is compiled and inlined as a string constant. + +## Runtime Usage + +The `globalScripts()` function is called during app initialization: + +- In the browser: during lazy-load bootstrap +- During SSR: in `server/platform/hydrate-app.ts` + +## Why Stubs Exist + +The empty stubs in `index.ts` serve as: + +1. **TypeScript scaffolding** - enables type checking and IDE support +2. **Fallback values** - used when no `globalScript`/`globalStyle` is configured +3. **Module resolution** - gives bundlers a real file to resolve before replacement \ No newline at end of file diff --git a/packages/core/src/client/client-build.ts b/packages/core/src/client/client-build.ts new file mode 100644 index 00000000000..fd5b05aca79 --- /dev/null +++ b/packages/core/src/client/client-build.ts @@ -0,0 +1,9 @@ +import { BUILD } from 'virtual:app-data'; +import type * as d from '@stencil/core'; + +export const Build: d.UserBuildConditionals = { + isDev: BUILD.isDev, + isBrowser: true, + isServer: false, + isTesting: BUILD.isTesting, +}; diff --git a/packages/core/src/client/client-decorators.ts b/packages/core/src/client/client-decorators.ts new file mode 100644 index 00000000000..f5edb06e38c --- /dev/null +++ b/packages/core/src/client/client-decorators.ts @@ -0,0 +1,119 @@ +/** + * Runtime stubs for Stencil decorators. + * + * These decorators are compile-time metadata flags that Stencil's compiler parses + * and transforms. At runtime, they do nothing - the actual component behavior is + * wired up by the compiled output, not by these decorators. + * + * These stubs exist so that: + * 1. Component classes can be instantiated directly in tests without going through + * Stencil's full compilation pipeline + * 2. The decorators don't throw "X is not a function" errors when used outside + * of Stencil's build process + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ + +/** + * No-op class decorator stub for @Component() + * @param _opts - component options (ignored at runtime) + * @returns a class decorator that returns the target unchanged + */ +export const Component = + (_opts?: any): ClassDecorator => + (target) => + target; + +/** + * No-op property decorator stub for @Element() + * @returns a property decorator that does nothing + */ +export const Element = (): PropertyDecorator => () => {}; + +/** + * No-op property decorator stub for @Event() + * @param _opts - event options (ignored at runtime) + * @returns a property decorator that does nothing + */ +export const Event = + (_opts?: any): PropertyDecorator => + () => {}; + +/** + * No-op property decorator stub for @AttachInternals() + * @param _opts - attach internals options (ignored at runtime) + * @returns a property decorator that does nothing + */ +export const AttachInternals = + (_opts?: any): PropertyDecorator => + () => {}; + +/** + * No-op method decorator stub for @Listen() + * @param _eventName - event name to listen for (ignored at runtime) + * @param _opts - listen options (ignored at runtime) + * @returns a method decorator that does nothing + */ +export const Listen = + (_eventName: string, _opts?: any): MethodDecorator => + () => {}; + +/** + * No-op method decorator stub for @Method() + * @param _opts - method options (ignored at runtime) + * @returns a method decorator that does nothing + */ +export const Method = + (_opts?: any): MethodDecorator => + () => {}; + +/** + * No-op property decorator stub for @Prop() + * @param _opts - prop options (ignored at runtime) + * @returns a property decorator that does nothing + */ +export const Prop = + (_opts?: any): PropertyDecorator => + () => {}; + +/** + * No-op property decorator stub for @State() + * @returns a property decorator that does nothing + */ +export const State = (): PropertyDecorator => () => {}; + +/** + * No-op method decorator stub for @Watch() + * @param _propName - property name to watch (ignored at runtime) + * @param _opts - watch options (ignored at runtime) + * @returns a method decorator that does nothing + */ +export const Watch = + (_propName: any, _opts?: any): MethodDecorator => + () => {}; + +/** + * No-op method decorator stub for @PropSerialize() + * @param _propName - property name to serialize (ignored at runtime) + * @returns a method decorator that does nothing + */ +export const PropSerialize = + (_propName: any): MethodDecorator => + () => {}; + +/** + * No-op method decorator stub for @AttrDeserialize() + * @param _propName - property name to deserialize (ignored at runtime) + * @returns a method decorator that does nothing + */ +export const AttrDeserialize = + (_propName: any): MethodDecorator => + () => {}; + +/** + * No-op compile-time utility stub for resolveVar() + * At runtime, this just returns the string representation of whatever is passed in. + * @param variable - the variable to resolve + * @returns the string representation of the variable + */ +export const resolveVar = (variable: T): string => String(variable); diff --git a/packages/core/src/client/client-host-ref.ts b/packages/core/src/client/client-host-ref.ts new file mode 100644 index 00000000000..e4575a64604 --- /dev/null +++ b/packages/core/src/client/client-host-ref.ts @@ -0,0 +1,91 @@ +import { BUILD } from 'virtual:app-data'; +import type * as d from '@stencil/core'; + +import { CMP_FLAGS } from '../utils/constants'; +import { reWireGetterSetter } from '../utils/es2022-rewire-class-members'; + +/** + * Given a {@link d.RuntimeRef} retrieve the corresponding {@link d.HostRef} + * + * @param ref the runtime ref of interest + * @returns the Host reference (if found) or undefined + */ +export const getHostRef = (ref: d.RuntimeRef): d.HostRef | undefined => { + if (ref.__s_ghr) { + return ref.__s_ghr(); + } + + return undefined; +}; + +/** + * Register a lazy instance with the {@link hostRefs} object so it's + * corresponding {@link d.HostRef} can be retrieved later. + * + * @param lazyInstance the lazy instance of interest + * @param hostRef that instances `HostRef` object + */ +export const registerInstance = (lazyInstance: any, hostRef: d.HostRef) => { + if (!hostRef) return; + lazyInstance.__s_ghr = () => hostRef; + hostRef.$lazyInstance$ = lazyInstance; + + if (hostRef.$cmpMeta$.$flags$ & CMP_FLAGS.hasModernPropertyDecls && (BUILD.state || BUILD.prop)) { + reWireGetterSetter(lazyInstance, hostRef); + } +}; + +/** + * Register a host element for a Stencil component, setting up various metadata + * and callbacks based on {@link BUILD} flags as well as the component's runtime + * metadata. + * + * @param hostElement the host element to register + * @param cmpMeta runtime metadata for that component + * @returns a reference to the host ref WeakMap + */ +export const registerHost = (hostElement: d.HostElement, cmpMeta: d.ComponentRuntimeMeta) => { + const hostRef: d.HostRef = { + $flags$: 0, + $hostElement$: hostElement, + $cmpMeta$: cmpMeta, + $instanceValues$: new Map(), + $serializerValues$: new Map(), + }; + if (BUILD.isDev) { + hostRef.$renderCount$ = 0; + } + if (BUILD.method && BUILD.lazyLoad) { + hostRef.$onInstancePromise$ = new Promise((r) => (hostRef.$onInstanceResolve$ = r)); + } + if (BUILD.asyncLoading) { + hostRef.$onReadyPromise$ = new Promise((r) => (hostRef.$onReadyResolve$ = r)); + // Expose the ready promise on the element itself under a stable string key + // ('s-rp') so the autoloader can access it without going through the + // minified hostRef internals + hostElement['s-rp'] = hostRef.$onReadyPromise$; + // Preserve any existing s-p/s-rc arrays (e.g. pre-set by the autoloader + // before this element was upgraded) so that promises pushed by children + // that connected before this element's constructor ran are not lost. + if (!hostElement['s-p']) hostElement['s-p'] = []; + if (!hostElement['s-rc']) hostElement['s-rc'] = []; + } + if (BUILD.lazyLoad) { + hostRef.$fetchedCbList$ = []; + } + + const ref = hostRef; + hostElement.__s_ghr = () => ref; + + if ( + !BUILD.lazyLoad && + cmpMeta.$flags$ & CMP_FLAGS.hasModernPropertyDecls && + (BUILD.state || BUILD.prop) + ) { + reWireGetterSetter(hostElement, hostRef); + } + + return ref; +}; + +export const isMemberInElement = (elm: any, memberName: string) => memberName in elm; diff --git a/src/client/client-load-module.ts b/packages/core/src/client/client-load-module.ts similarity index 88% rename from src/client/client-load-module.ts rename to packages/core/src/client/client-load-module.ts index 63651bda846..11049fbaad8 100644 --- a/src/client/client-load-module.ts +++ b/packages/core/src/client/client-load-module.ts @@ -1,9 +1,12 @@ -import { BUILD } from '@app-data'; +import { BUILD } from 'virtual:app-data'; +import type * as d from '@stencil/core'; -import type * as d from '../declarations'; import { consoleDevError, consoleError } from './client-log'; -export const cmpModules = /*@__PURE__*/ new Map(); +export const cmpModules = /*@__PURE__*/ new Map< + string, + { [exportName: string]: d.ComponentConstructor } +>(); /** * We need to separate out this prefix so that Esbuild doesn't try to resolve @@ -18,7 +21,7 @@ export const cmpModules = /*@__PURE__*/ new Map (customError || console.error)(e, el); +export const consoleError: d.ErrorHandler = (e: any, el?: HTMLElement) => + (customError || console.error)(e, el); export const STENCIL_DEV_MODE = BUILD.isTesting ? ['STENCIL:'] // E2E testing diff --git a/packages/core/src/client/client-style.ts b/packages/core/src/client/client-style.ts new file mode 100644 index 00000000000..8a78f035291 --- /dev/null +++ b/packages/core/src/client/client-style.ts @@ -0,0 +1,6 @@ +import type * as d from '@stencil/core'; + +export const styles: d.StyleMap = /*@__PURE__*/ new Map(); +export const modeResolutionChain: d.ResolutionHandler[] = []; +export const setScopedSsr = (_opts: d.SsrFactoryOptions) => {}; +export const needsScopedSSR = () => false; diff --git a/src/client/client-task-queue.ts b/packages/core/src/client/client-task-queue.ts similarity index 92% rename from src/client/client-task-queue.ts rename to packages/core/src/client/client-task-queue.ts index d90641bcbbe..3c43551ac84 100644 --- a/src/client/client-task-queue.ts +++ b/packages/core/src/client/client-task-queue.ts @@ -1,6 +1,6 @@ -import { BUILD } from '@app-data'; +import { BUILD } from 'virtual:app-data'; +import type * as d from '@stencil/core'; -import type * as d from '../declarations'; import { PLATFORM_FLAGS } from '../runtime/runtime-constants'; import { consoleError } from './client-log'; import { plt, promiseResolve } from './client-window'; @@ -79,7 +79,9 @@ const flush = () => { queueDomWrites.length = 0; } - if ((queuePending = queueDomReads.length + queueDomWrites.length + queueDomWritesLow.length > 0)) { + if ( + (queuePending = queueDomReads.length + queueDomWrites.length + queueDomWritesLow.length > 0) + ) { // still more to do yet, but we've run out of time // let's let this thing cool off and try again in the next tick plt.raf(flush); diff --git a/packages/core/src/client/client-window.ts b/packages/core/src/client/client-window.ts new file mode 100644 index 00000000000..31cf2922c02 --- /dev/null +++ b/packages/core/src/client/client-window.ts @@ -0,0 +1,72 @@ +import { BUILD } from 'virtual:app-data'; +import type * as d from '@stencil/core'; + +interface StencilWindow extends Omit { + document?: Document; +} + +export const win = ( + typeof window !== 'undefined' ? window : ({} as StencilWindow) +) as StencilWindow; + +export const H = ((win as any).HTMLElement || (class {} as any)) as HTMLElement; + +export const plt: d.PlatformRuntime = { + $flags$: 0, + $resourcesUrl$: '', + jmp: (h) => h(), + raf: (h) => requestAnimationFrame(h), + ael: (el, eventName, listener, opts) => el.addEventListener(eventName, listener, opts), + rel: (el, eventName, listener, opts) => el.removeEventListener(eventName, listener, opts), + ce: (eventName, opts) => new CustomEvent(eventName, opts), +}; + +export const setPlatformHelpers = (helpers: { + jmp?: (c: any) => any; + raf?: (c: any) => number; + ael?: (el: any, eventName: string, listener: any, options: any) => void; + rel?: (el: any, eventName: string, listener: any, options: any) => void; + ce?: (eventName: string, opts?: any) => any; +}) => { + Object.assign(plt, helpers); +}; + +export const supportsListenerOptions = /*@__PURE__*/ (() => { + let supported = false; + try { + win.document?.addEventListener( + 'e', + null, + Object.defineProperty({}, 'passive', { + get() { + supported = true; + }, + }), + ); + } catch {} + return supported; +})(); + +export const promiseResolve = (v?: any) => Promise.resolve(v); + +export const supportsConstructableStylesheets = BUILD.constructableCSS + ? /*@__PURE__*/ (() => { + try { + if (!win.document.adoptedStyleSheets) { + return false; + } + const sheet = new CSSStyleSheet(); + return typeof sheet.replaceSync === 'function'; + } catch {} + return false; + })() + : false; + +// https://github.com/salesforce/lwc/blob/5af18fdd904bc6cfcf7b76f3c539490ff11515b2/packages/%40lwc/engine-dom/src/renderer.ts#L41-L43 +export const supportsMutableAdoptedStyleSheets = supportsConstructableStylesheets + ? /*@__PURE__*/ (() => + !!win.document && + Object.getOwnPropertyDescriptor(win.document.adoptedStyleSheets, 'length')!.writable)() + : false; + +export { H as HTMLElement }; diff --git a/packages/core/src/client/index.ts b/packages/core/src/client/index.ts new file mode 100644 index 00000000000..48d403ff8f7 --- /dev/null +++ b/packages/core/src/client/index.ts @@ -0,0 +1,10 @@ +export * from './client-build'; +export * from './client-decorators'; +export * from './client-host-ref'; +export * from './client-load-module'; +export * from './client-log'; +export * from './client-style'; +export * from './client-task-queue'; +export * from './client-window'; +export { BUILD, Env, NAMESPACE } from 'virtual:app-data'; +export * from '../runtime'; diff --git a/packages/core/src/client/readme.md b/packages/core/src/client/readme.md new file mode 100644 index 00000000000..c0b09abbb99 --- /dev/null +++ b/packages/core/src/client/readme.md @@ -0,0 +1,43 @@ +# client + +Browser-specific platform implementation for Stencil's runtime. + +## Overview + +This directory provides the browser platform layer that connects the platform-agnostic `runtime/` to browser APIs. It implements the `@platform` interface with browser-specific behavior. + +## Key Files + +| File | Purpose | +| ----------------------- | ------------------------------------------------------ | +| `client-host-ref.ts` | Maps host elements to their internal state (`HostRef`) | +| `client-load-module.ts` | Lazy-loads component modules on demand | +| `client-style.ts` | Attaches component styles to the DOM | +| `client-task-queue.ts` | Schedules DOM updates using `requestAnimationFrame` | +| `client-window.ts` | Provides `window`, `document`, and other globals | +| `client-build.ts` | Provides the `Build` object with runtime feature flags | + +## Architecture + +``` +┌─────────────────────────────────────────────┐ +│ Component Code │ +├─────────────────────────────────────────────┤ +│ runtime/ (platform-agnostic core) │ +├──────────────────┬──────────────────────────┤ +│ client/ │ server/ │ +│ (browser) │ (SSR/hydration) │ +└──────────────────┴──────────────────────────┘ +``` + +The runtime imports from `@platform`, which resolves to either `client/` or `server/` depending on the build target. + +## Exports + +The `index.ts` re-exports: + +- All platform implementations (`client-*.ts` files) +- `BUILD`, `Env`, `NAMESPACE` from `virtual:app-data` +- Everything from `@runtime` + +This makes `@stencil/core/runtime/client` a complete bundle for browser usage. \ No newline at end of file diff --git a/packages/core/src/compiler/app-core/app-data.ts b/packages/core/src/compiler/app-core/app-data.ts new file mode 100644 index 00000000000..42953f10cef --- /dev/null +++ b/packages/core/src/compiler/app-core/app-data.ts @@ -0,0 +1,207 @@ +import type { + BuildConditionals, + BuildFeatures, + ComponentCompilerMeta, + Module, + ModuleMap, + ValidatedConfig, +} from '@stencil/core'; + +import { unique } from '../../utils'; + +/** + * Re-export {@link BUILD} defaults + */ +export * from '../../app-data'; + +/** + * Generate a {@link BuildFeatures} entity, based on the provided component metadata + * @param cmps a collection of component compiler metadata, used to set values on the generated build features object + * @returns the generated build features entity + */ +export const getBuildFeatures = (cmps: ComponentCompilerMeta[]): BuildFeatures => { + const slot = cmps.some((c) => c.htmlTagNames.includes('slot')); + const shadowDom = cmps.some((c) => c.encapsulation === 'shadow'); + const slotRelocation = cmps.some( + (c) => c.encapsulation !== 'shadow' && c.htmlTagNames.includes('slot'), + ); + const f: BuildFeatures = { + allRenderFn: cmps.every((c) => c.hasRenderFn), + formAssociated: cmps.some((c) => c.formAssociated), + deserializer: cmps.some((c) => c.hasDeserializer), + element: cmps.some((c) => c.hasElement), + event: cmps.some((c) => c.hasEvent), + hasRenderFn: cmps.some((c) => c.hasRenderFn), + lifecycle: cmps.some((c) => c.hasLifecycle), + asyncLoading: true, + hostListener: cmps.some((c) => c.hasListener), + hostListenerTargetWindow: cmps.some((c) => c.hasListenerTargetWindow), + hostListenerTargetDocument: cmps.some((c) => c.hasListenerTargetDocument), + hostListenerTargetBody: cmps.some((c) => c.hasListenerTargetBody), + hostListenerTarget: cmps.some((c) => c.hasListenerTarget), + member: cmps.some((c) => c.hasMember), + method: cmps.some((c) => c.hasMethod), + mode: cmps.some((c) => c.hasMode), + observeAttribute: cmps.some((c) => c.hasAttribute || c.hasWatchCallback || c.hasDeserializer), + prop: cmps.some((c) => c.hasProp), + propBoolean: cmps.some((c) => c.hasPropBoolean), + propChangeCallback: cmps.some( + (c) => c.hasWatchCallback || c.hasDeserializer || c.hasSerializer, + ), + propNumber: cmps.some((c) => c.hasPropNumber), + propString: cmps.some((c) => c.hasPropString), + propMutable: cmps.some((c) => c.hasPropMutable), + reflect: cmps.some((c) => c.hasReflect || c.hasSerializer), + scoped: cmps.some((c) => c.encapsulation === 'scoped'), + serializer: cmps.some((c) => c.hasSerializer), + shadowDom, + shadowDelegatesFocus: shadowDom && cmps.some((c) => c.shadowDelegatesFocus), + shadowModeClosed: shadowDom && cmps.some((c) => c.shadowMode === 'closed'), + shadowSlotAssignmentManual: shadowDom && cmps.some((c) => c.slotAssignment === 'manual'), + slot, + slotRelocation, + state: cmps.some((c) => c.hasState), + style: cmps.some((c) => c.hasStyle), + svg: cmps.some((c) => c.htmlTagNames.includes('svg')), + updatable: cmps.some((c) => c.isUpdateable), + vdomAttribute: cmps.some((c) => c.hasVdomAttribute), + vdomXlink: cmps.some((c) => c.hasVdomXlink), + vdomClass: cmps.some((c) => c.hasVdomClass), + vdomFunctional: cmps.some((c) => c.hasVdomFunctional), + vdomKey: cmps.some((c) => c.hasVdomKey), + vdomListener: cmps.some((c) => c.hasVdomListener), + vdomPropOrAttr: cmps.some((c) => c.hasVdomPropOrAttr), + vdomRef: cmps.some((c) => c.hasVdomRef), + vdomRender: cmps.some((c) => c.hasVdomRender), + vdomStyle: cmps.some((c) => c.hasVdomStyle), + vdomText: cmps.some((c) => c.hasVdomText), + taskQueue: true, + // Per-component slot patches + patchAll: cmps.some((c) => c.hasPatchAll), + patchChildren: cmps.some((c) => c.hasPatchChildren), + patchClone: cmps.some((c) => c.hasPatchClone), + patchInsert: cmps.some((c) => c.hasPatchInsert), + }; + f.vdomAttribute = f.vdomAttribute || f.reflect; + f.vdomPropOrAttr = f.vdomPropOrAttr || f.reflect; + + return f; +}; + +export const updateComponentBuildConditionals = ( + moduleMap: ModuleMap, + cmps: ComponentCompilerMeta[], +) => { + cmps.forEach((cmp) => { + const importedModules = getModuleImports(moduleMap, cmp.sourceFilePath, []); + importedModules.forEach((importedModule) => { + // if the component already has a boolean true value it'll keep it + // otherwise we get the boolean value from the imported module + cmp.hasVdomAttribute = cmp.hasVdomAttribute || importedModule.hasVdomAttribute; + cmp.hasVdomPropOrAttr = cmp.hasVdomPropOrAttr || importedModule.hasVdomPropOrAttr; + cmp.hasVdomXlink = cmp.hasVdomXlink || importedModule.hasVdomXlink; + cmp.hasVdomClass = cmp.hasVdomClass || importedModule.hasVdomClass; + cmp.hasVdomFunctional = cmp.hasVdomFunctional || importedModule.hasVdomFunctional; + cmp.hasVdomKey = cmp.hasVdomKey || importedModule.hasVdomKey; + cmp.hasVdomListener = cmp.hasVdomListener || importedModule.hasVdomListener; + cmp.hasVdomRef = cmp.hasVdomRef || importedModule.hasVdomRef; + cmp.hasVdomRender = cmp.hasVdomRender || importedModule.hasVdomRender; + cmp.hasVdomStyle = cmp.hasVdomStyle || importedModule.hasVdomStyle; + cmp.hasVdomText = cmp.hasVdomText || importedModule.hasVdomText; + + cmp.htmlAttrNames.push(...importedModule.htmlAttrNames); + cmp.htmlTagNames.push(...importedModule.htmlTagNames); + cmp.potentialCmpRefs.push(...importedModule.potentialCmpRefs); + }); + + cmp.htmlAttrNames = unique(cmp.htmlAttrNames); + cmp.htmlTagNames = unique(cmp.htmlTagNames); + cmp.potentialCmpRefs = unique(cmp.potentialCmpRefs); + }); +}; + +const getModuleImports = (moduleMap: ModuleMap, filePath: string, importedModules: Module[]) => { + let moduleFile = moduleMap.get(filePath); + if (moduleFile == null) { + moduleFile = moduleMap.get(filePath + '.tsx'); + if (moduleFile == null) { + moduleFile = moduleMap.get(filePath + '.ts'); + if (moduleFile == null) { + moduleFile = moduleMap.get(filePath + '.js'); + } + } + } + + if ( + moduleFile != null && + !importedModules.some((m) => m.sourceFilePath === moduleFile.sourceFilePath) + ) { + importedModules.push(moduleFile); + + moduleFile.localImports.forEach((localImport) => { + getModuleImports(moduleMap, localImport, importedModules); + }); + + // Follow functional component dependencies resolved via typeChecker. + moduleFile.functionalComponentDeps?.forEach((depPath) => { + getModuleImports(moduleMap, depPath, importedModules); + }); + } + return importedModules; +}; + +/** + * Update the provided build conditionals object in-line with a provided Stencil project configuration + * + * **This function mutates the build conditionals argument** + * + * @param config the Stencil configuration to use to update the provided build conditionals + * @param b the build conditionals to update + */ +export const updateBuildConditionals = (config: ValidatedConfig, b: BuildConditionals): void => { + b.isDebug = config.logLevel === 'debug'; + b.isDev = !!config.devMode; + b.isTesting = !!config._isTesting; + b.devTools = b.isDev && !config._isTesting; + b.profile = !!config.profile; + b.hotModuleReplacement = !!( + config.devMode && + config.devServer && + config.devServer.reloadStrategy === 'hmr' && + !config._isTesting + ); + b.updatable = b.updatable || b.hydrateClientSide || b.hotModuleReplacement; + b.member = b.member || b.updatable || b.mode || b.lifecycle; + b.constructableCSS = !b.hotModuleReplacement || !!config._isTesting; + b.asyncLoading = !!(b.asyncLoading || b.lazyLoad || b.taskQueue || b.initializeNextTick); + b.cssAnnotations = true; + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + b.appendChildSlotFix = config.extras.appendChildSlotFix; + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + b.slotChildNodesFix = config.extras.slotChildNodesFix; + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + b.experimentalSlotFixes = config.extras.experimentalSlotFixes; + // TODO(STENCIL-1086): remove this option when it's the default behavior + b.experimentalScopedSlotChanges = config.extras.experimentalScopedSlotChanges; + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + b.cloneNodeFix = config.extras.cloneNodeFix; + b.lifecycleDOMEvents = !!(b.isDebug || config._isTesting || config.extras.lifecycleDOMEvents); + // TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior + b.scopedSlotTextContentFix = !!config.extras.scopedSlotTextContentFix; + // TODO(STENCIL-1305): remove this option + b.attachStyles = true; + b.invisiblePrehydration = + typeof config.invisiblePrehydration === 'undefined' ? true : config.invisiblePrehydration; + // TODO(STENCIL-854): Remove code related to legacy shadowDomShim field + if (b.shadowDomShim) { + b.slotRelocation = b.slot; + } + if (config.hydratedFlag) { + b.hydratedAttribute = config.hydratedFlag.selector === 'attribute'; + b.hydratedClass = config.hydratedFlag.selector === 'class'; + b.hydratedSelectorName = config.hydratedFlag.name; + } else { + b.hydratedAttribute = false; + b.hydratedClass = false; + } +}; diff --git a/src/compiler/app-core/bundle-app-core.ts b/packages/core/src/compiler/app-core/bundle-app-core.ts similarity index 76% rename from src/compiler/app-core/bundle-app-core.ts rename to packages/core/src/compiler/app-core/bundle-app-core.ts index 5939499c6a5..dc20402eaf0 100644 --- a/src/compiler/app-core/bundle-app-core.ts +++ b/packages/core/src/compiler/app-core/bundle-app-core.ts @@ -1,29 +1,30 @@ -import type { OutputAsset, OutputChunk, OutputOptions, RollupBuild } from 'rollup'; +import type * as d from '@stencil/core'; +import type { OutputAsset, OutputChunk, OutputOptions, RolldownBuild } from 'rolldown'; -import type * as d from '../../declarations'; import { STENCIL_CORE_ID } from '../bundle/entry-alias-ids'; /** - * Generate rollup output based on a rollup build and a series of options. + * Generate rolldown output based on a rolldown build and a series of options. * - * @param build a rollup build - * @param options output options for rollup + * @param build a rolldown build + * @param options output options for rolldown * @param config a user-supplied configuration object * @param entryModules a list of entry modules, for checking which chunks * contain components * @returns a Promise wrapping either build results or `null` */ -export const generateRollupOutput = async ( - build: RollupBuild, +export const generateRolldownOutput = async ( + build: RolldownBuild, options: OutputOptions, config: d.ValidatedConfig, entryModules: d.EntryModule[], -): Promise => { +): Promise => { if (build == null) { return null; } - const { output }: { output: [OutputChunk, ...(OutputChunk | OutputAsset)[]] } = await build.generate(options); + const { output }: { output: [OutputChunk, ...(OutputChunk | OutputAsset)[]] } = + await build.generate(options); return output.map((chunk: OutputChunk | OutputAsset) => { if (chunk.type === 'chunk') { const isCore = Object.keys(chunk.modules).some((m) => m.includes(STENCIL_CORE_ID)); diff --git a/src/compiler/build/test/build-stats.spec.ts b/packages/core/src/compiler/build/_test_/build-stats.spec.ts similarity index 78% rename from src/compiler/build/test/build-stats.spec.ts rename to packages/core/src/compiler/build/_test_/build-stats.spec.ts index 8047ac93282..6202ef065c1 100644 --- a/src/compiler/build/test/build-stats.spec.ts +++ b/packages/core/src/compiler/build/_test_/build-stats.spec.ts @@ -1,7 +1,8 @@ -import type * as d from '@stencil/core/declarations'; import { mockBuildCtx, mockCompilerCtx, mockValidatedConfig } from '@stencil/core/testing'; -import { result } from '@utils'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; +import { result } from '../../../utils'; import { generateBuildResults } from '../build-results'; import { generateBuildStats } from '../build-stats'; @@ -25,26 +26,35 @@ describe('generateBuildStats', () => { delete compilerBuildStats.timestamp; } - if (compilerBuildStats.hasOwnProperty('compiler') && compilerBuildStats.compiler.hasOwnProperty('version')) { + if ( + compilerBuildStats.hasOwnProperty('compiler') && + compilerBuildStats.compiler.hasOwnProperty('version') + ) { delete compilerBuildStats.compiler.version; } expect(compilerBuildStats).toStrictEqual({ - app: { bundles: 0, components: 0, entries: 0, fsNamespace: 'testing', namespace: 'Testing', outputs: [] }, + app: { + bundles: 0, + components: 0, + entries: 0, + fsNamespace: 'testing', + namespace: 'Testing', + outputs: [], + }, collections: [], compiler: { name: 'in-memory' }, componentGraph: {}, components: [], entries: [], - formats: { commonjs: [], es5: [], esm: [], esmBrowser: [], system: [] }, + formats: { commonjs: [], esm: [], esmBrowser: [] }, options: { - buildEs5: false, hashFileNames: false, hashedFileNameLength: 8, minifyCss: false, minifyJs: false, }, - rollupResults: { + rolldownResults: { modules: [], }, sourceGraph: {}, diff --git a/packages/core/src/compiler/build/_test_/write-export-maps.spec.ts b/packages/core/src/compiler/build/_test_/write-export-maps.spec.ts new file mode 100644 index 00000000000..cf062a1b13d --- /dev/null +++ b/packages/core/src/compiler/build/_test_/write-export-maps.spec.ts @@ -0,0 +1,177 @@ +import { execSync } from 'child_process'; +import * as d from '@stencil/core'; +import { mockBuildCtx, mockValidatedConfig } from '@stencil/core/testing'; +import { beforeEach, describe, expect, it, vi, afterEach, Mock } from 'vitest'; + +import { stubComponentCompilerMeta } from '../../types/_tests_/ComponentCompilerMeta.stub'; +import { writeExportMaps } from '../write-export-maps'; + +vi.mock('child_process', () => ({ + execSync: vi.fn(), +})); + +describe('writeExportMaps', () => { + let config: d.ValidatedConfig; + let buildCtx: d.BuildCtx; + const execSyncMock = execSync as Mock; + + beforeEach(() => { + config = mockValidatedConfig(); + buildCtx = mockBuildCtx(config); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should not generate any exports if there are no output targets', () => { + writeExportMaps(config, buildCtx); + + expect(execSyncMock).toHaveBeenCalledTimes(0); + }); + + it('should generate the default exports for the lazy build if present', () => { + const loaderBundleTarget: d.OutputTargetLoaderBundle = { + type: 'loader-bundle', + dir: '/dist', + buildDir: '/dist', + copy: [], + empty: true, + cjs: true, + skipInDev: false, + }; + const typesTarget: d.OutputTargetTypes = { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }; + config.outputTargets = [loaderBundleTarget, typesTarget]; + + writeExportMaps(config, buildCtx); + + // 3 for root export + 3 for loader export + expect(execSyncMock).toHaveBeenCalledTimes(6); + expect(execSyncMock).toHaveBeenCalledWith(`npm pkg set "exports[.][import]"="./dist/index.js"`); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[.][require]"="./dist/index.cjs"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[.][types]"="./dist/types/index.d.ts"`, + ); + // Loader export points directly to esm/loader.js + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./loader][import]"="./dist/esm/loader.js"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./loader][require]"="./dist/cjs/loader.cjs"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./loader][types]"="./dist/types/loader.d.ts"`, + ); + }); + + it('should generate the default exports for the custom elements build if present', () => { + const typesTarget: d.OutputTargetTypes = { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }; + config.outputTargets = [ + { + type: 'standalone', + dir: '/dist/components', + }, + typesTarget, + ]; + + writeExportMaps(config, buildCtx); + + expect(execSyncMock).toHaveBeenCalledTimes(2); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[.][import]"="./dist/components/index.js"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[.][types]"="./dist/types/index.d.ts"`, + ); + }); + + it('should generate the custom elements exports if the output target is present', () => { + config.rootDir = '/'; + config.outputTargets.push( + { + type: 'standalone', + dir: '/dist/components', + }, + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ); + + buildCtx.components = [ + stubComponentCompilerMeta({ + tagName: 'my-component', + componentClassName: 'MyComponent', + }), + ]; + + writeExportMaps(config, buildCtx); + + // 2 for root export (import + types) + 2 for component export + expect(execSyncMock).toHaveBeenCalledTimes(4); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][import]"="./dist/components/my-component.js"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][types]"="./dist/components/my-component.d.ts"`, + ); + }); + + it('should generate the custom elements exports for multiple components', () => { + config.rootDir = '/'; + config.outputTargets.push( + { + type: 'standalone', + dir: '/dist/components', + }, + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ); + + buildCtx.components = [ + stubComponentCompilerMeta({ + tagName: 'my-component', + componentClassName: 'MyComponent', + }), + stubComponentCompilerMeta({ + tagName: 'my-other-component', + componentClassName: 'MyOtherComponent', + }), + ]; + + writeExportMaps(config, buildCtx); + + // 2 for root export (import + types) + 4 for component exports (2 each) + expect(execSyncMock).toHaveBeenCalledTimes(6); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][import]"="./dist/components/my-component.js"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./my-component][types]"="./dist/components/my-component.d.ts"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./my-other-component][import]"="./dist/components/my-other-component.js"`, + ); + expect(execSyncMock).toHaveBeenCalledWith( + `npm pkg set "exports[./my-other-component][types]"="./dist/components/my-other-component.d.ts"`, + ); + }); +}); diff --git a/src/compiler/build/build-ctx.ts b/packages/core/src/compiler/build/build-ctx.ts similarity index 93% rename from src/compiler/build/build-ctx.ts rename to packages/core/src/compiler/build/build-ctx.ts index dcd5c28e453..da220d584a0 100644 --- a/src/compiler/build/build-ctx.ts +++ b/packages/core/src/compiler/build/build-ctx.ts @@ -1,7 +1,6 @@ -import { hasError, hasWarning, result } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; -import { validateConfig } from '../config/validate-config'; +import { hasError, hasWarning, result } from '../../utils'; /** * A new BuildCtx object is created for every build @@ -22,8 +21,6 @@ export class BuildContext implements d.BuildCtx { buildStats?: result.Result = undefined; esmBrowserComponentBundle: d.BundleModule[]; esmComponentBundle: d.BundleModule[]; - es5ComponentBundle: d.BundleModule[]; - systemComponentBundle: d.BundleModule[]; commonJsComponentBundle: d.BundleModule[]; diagnostics: d.Diagnostic[] = []; dirsAdded: string[] = []; @@ -42,7 +39,7 @@ export class BuildContext implements d.BuildCtx { hasServiceWorkerChanges = false; hasScriptChanges = true; hasStyleChanges = true; - hydrateAppFilePath: string = null; + ssrAppFilePath: string = null; indexBuildCount = 0; indexDoc: Document = undefined; isRebuild = false; @@ -63,8 +60,8 @@ export class BuildContext implements d.BuildCtx { transpileBuildCount = 0; validateTypesPromise: Promise; - constructor(config: d.Config, compilerCtx: d.CompilerCtx) { - this.config = validateConfig(config, {}).config; + constructor(config: d.ValidatedConfig, compilerCtx: d.CompilerCtx) { + this.config = config; this.compilerCtx = compilerCtx; this.buildId = ++this.compilerCtx.activeBuildId; @@ -229,7 +226,7 @@ const getProgress = (completedTasks: d.BuildTask[]) => { return (progressIndex + 1) / taskKeys.length; }; -export const ProgressTask = { +const ProgressTask = { emptyOutputTargets: {}, transpileApp: {}, generateStyles: {}, diff --git a/src/compiler/build/build-finish.ts b/packages/core/src/compiler/build/build-finish.ts similarity index 94% rename from src/compiler/build/build-finish.ts rename to packages/core/src/compiler/build/build-finish.ts index 58a44123df3..0fdd8d04083 100644 --- a/src/compiler/build/build-finish.ts +++ b/packages/core/src/compiler/build/build-finish.ts @@ -1,6 +1,6 @@ -import { isFunction, isRemoteUrl, relative } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { isFunction, isRemoteUrl, relative } from '../../utils'; import { generateBuildResults } from './build-results'; import { generateBuildStats, writeBuildStats } from './build-stats'; @@ -186,9 +186,17 @@ const cleanupUpdateMsg = (logger: d.Logger, msg: string, fileNames: string[]) => * @param config the Stencil configuration associated with the current build * @param diagnostics the diagnostics to update */ -const cleanDiagnosticsRelativePath = (config: d.Config, diagnostics: ReadonlyArray): void => { +const cleanDiagnosticsRelativePath = ( + config: d.Config, + diagnostics: ReadonlyArray, +): void => { diagnostics.forEach((diagnostic) => { - if (!diagnostic.relFilePath && diagnostic.absFilePath && !isRemoteUrl(diagnostic.absFilePath) && config.rootDir) { + if ( + !diagnostic.relFilePath && + diagnostic.absFilePath && + !isRemoteUrl(diagnostic.absFilePath) && + config.rootDir + ) { diagnostic.relFilePath = relative(config.rootDir, diagnostic.absFilePath); } }); diff --git a/packages/core/src/compiler/build/build-hmr.ts b/packages/core/src/compiler/build/build-hmr.ts new file mode 100644 index 00000000000..4911b7f1ddf --- /dev/null +++ b/packages/core/src/compiler/build/build-hmr.ts @@ -0,0 +1,348 @@ +import { basename } from 'path'; +import picomatch from 'picomatch'; +import type * as d from '@stencil/core'; + +import { isGlob, isOutputTargetWww, normalizePath, sortBy } from '../../utils'; +import { getScopeId } from '../style/scope-css'; + +/** + * Track which components had styles in the previous build. + * Used to detect when styles are removed from a component. + * Maps component tag name to an array of style modes (or ['$'] for no mode). + */ +const previousComponentStyles = new Map(); + +/** + * Generate the Hot Module Replacement (HMR) data for the current build. + * @param config a user-supplied config + * @param compilerCtx the compiler context + * @param buildCtx the build context + * @returns the HMR data + */ +export const generateHmr = (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) => { + if (config.devServer?.reloadStrategy == null) { + return null; + } + + const hmr: d.HotModuleReplacement = { + reloadStrategy: config.devServer.reloadStrategy, + versionId: Date.now().toString().substring(6) + '' + Math.round(Math.random() * 89999 + 10000), + }; + + if (buildCtx.scriptsAdded.length > 0) { + hmr.scriptsAdded = buildCtx.scriptsAdded.slice(); + } + + if (buildCtx.scriptsDeleted.length > 0) { + hmr.scriptsDeleted = buildCtx.scriptsDeleted.slice(); + } + + const excludeHmr = excludeHmrFiles(config, config.devServer.excludeHmr, buildCtx.filesChanged); + if (excludeHmr.length > 0) { + hmr.excludeHmr = excludeHmr.slice(); + } + + if (buildCtx.hasHtmlChanges) { + hmr.indexHtmlUpdated = true; + } + + if (buildCtx.hasServiceWorkerChanges) { + hmr.serviceWorkerUpdated = true; + } + + const outputTargetsWww = config.outputTargets.filter(isOutputTargetWww); + + const componentsUpdated = getComponentsUpdated(compilerCtx, buildCtx); + if (componentsUpdated) { + hmr.componentsUpdated = componentsUpdated; + } + + // Detect components that had their styles removed + const stylesRemoved = getStylesRemoved(buildCtx, componentsUpdated); + + // Combine updated styles with removed styles + const allStyleUpdates = [ + ...buildCtx.stylesUpdated.map((s) => ({ + styleId: getScopeId(s.styleTag, s.styleMode), + styleTag: s.styleTag, + styleText: s.styleText, + })), + ...stylesRemoved, + ]; + + if (allStyleUpdates.length > 0) { + hmr.inlineStylesUpdated = sortBy(allStyleUpdates, (s) => s.styleId); + } + + // Update tracking for next build + updateComponentStyleTracking(buildCtx); + + const externalStylesUpdated = getExternalStylesUpdated(buildCtx, outputTargetsWww); + if (externalStylesUpdated) { + hmr.externalStylesUpdated = externalStylesUpdated; + } + + const externalImagesUpdated = getImagesUpdated(buildCtx, outputTargetsWww); + if (externalImagesUpdated) { + hmr.imagesUpdated = externalImagesUpdated; + } + + return hmr; +}; + +const getComponentsUpdated = (compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) => { + // find all of the components that would be affected from the file changes + if (!buildCtx.filesChanged) { + return null; + } + + const filesToLookForImporters = buildCtx.filesChanged.filter((f) => { + return f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js') || f.endsWith('.jsx'); + }); + + if (filesToLookForImporters.length === 0) { + return null; + } + + const changedScriptFiles: string[] = []; + const checkedFiles = new Set(); + const allModuleFiles = buildCtx.moduleFiles.filter( + (m) => m.localImports && m.localImports.length > 0, + ); + + while (filesToLookForImporters.length > 0) { + const scriptFile = filesToLookForImporters.shift(); + addTsFileImporters( + allModuleFiles, + filesToLookForImporters, + checkedFiles, + changedScriptFiles, + scriptFile, + ); + } + + const tags = changedScriptFiles.reduce((acc, changedTsFile) => { + const moduleFile = compilerCtx.moduleMap.get(changedTsFile); + if (moduleFile != null) { + moduleFile.cmps.forEach((cmp) => { + if (typeof cmp.tagName === 'string') { + if (!acc.includes(cmp.tagName)) { + acc.push(cmp.tagName); + } + } + }); + } + return acc; + }, [] as string[]); + + if (tags.length === 0) { + return null; + } + + return tags.sort(); +}; + +/** + * Update the tracking of which components had styles in the current build. + * @param allModuleFiles all module files in the current build + * @param filesToLookForImporters files to look for importers + * @param checkedFiles set of checked files + * @param changedScriptFiles array of changed script files + * @param scriptFile the current script file + */ +const addTsFileImporters = ( + allModuleFiles: d.Module[], + filesToLookForImporters: string[], + checkedFiles: Set, + changedScriptFiles: string[], + scriptFile: string, +) => { + if (!changedScriptFiles.includes(scriptFile)) { + // add it to our list of files to transpile + changedScriptFiles.push(scriptFile); + } + + if (checkedFiles.has(scriptFile)) { + // already checked this file + return; + } + checkedFiles.add(scriptFile); + + // get all the ts files that import this ts file + const tsFilesThatImportsThisTsFile = allModuleFiles.reduce((arr, moduleFile) => { + moduleFile.localImports.forEach((localImport) => { + let checkFile = localImport; + + if (checkFile === scriptFile) { + arr.push(moduleFile.sourceFilePath); + return; + } + + checkFile = localImport + '.tsx'; + if (checkFile === scriptFile) { + arr.push(moduleFile.sourceFilePath); + return; + } + + checkFile = localImport + '.ts'; + if (checkFile === scriptFile) { + arr.push(moduleFile.sourceFilePath); + return; + } + + checkFile = localImport + '.js'; + if (checkFile === scriptFile) { + arr.push(moduleFile.sourceFilePath); + return; + } + }); + return arr; + }, [] as string[]); + + // add all the files that import this ts file to the list of ts files we need to look through + tsFilesThatImportsThisTsFile.forEach((tsFileThatImportsThisTsFile) => { + // if we add to this array, then the while look will keep working until it's empty + filesToLookForImporters.push(tsFileThatImportsThisTsFile); + }); +}; + +const getExternalStylesUpdated = (buildCtx: d.BuildCtx, outputTargetsWww: d.OutputTargetWww[]) => { + if (!buildCtx.isRebuild || outputTargetsWww.length === 0) { + return null; + } + + const cssFiles = buildCtx.filesWritten.filter((f) => f.endsWith('.css')); + if (cssFiles.length === 0) { + return null; + } + + return cssFiles.map((cssFile) => basename(cssFile)).sort(); +}; + +const getImagesUpdated = (buildCtx: d.BuildCtx, outputTargetsWww: d.OutputTargetWww[]) => { + if (outputTargetsWww.length === 0) { + return null; + } + + const imageFiles = buildCtx.filesChanged.reduce((arr, filePath) => { + if (IMAGE_EXT.some((ext) => filePath.toLowerCase().endsWith(ext))) { + const fileName = basename(filePath); + if (!arr.includes(fileName)) { + arr.push(fileName); + } + } + return arr; + }, []); + + if (imageFiles.length === 0) { + return null; + } + + return imageFiles.sort(); +}; + +/** + * Determine a list of files (if any) which should be excluded from HMR updates. + * + * @param config a user-supplied config + * @param excludeHmr a list of glob patterns that should be used to determine + * whether to exclude a file or not (a file will be excluded if it matches one + * @param filesChanged an array of files which are changed in the HMR update + * currently under consideration + * @returns a sorted list of files to exclude + */ +const excludeHmrFiles = ( + config: d.Config, + excludeHmr: string[], + filesChanged: string[], +): string[] => { + const excludeFiles: string[] = []; + + if (!excludeHmr || excludeHmr.length === 0) { + return excludeFiles; + } + + excludeHmr.forEach((pattern) => { + return filesChanged + .map((fileChanged) => { + let shouldExclude = false; + + if (isGlob(pattern)) { + shouldExclude = picomatch.isMatch(fileChanged, pattern); + } else { + shouldExclude = normalizePath(pattern) === normalizePath(fileChanged); + } + + if (shouldExclude) { + config.logger.debug(`excludeHmr: ${fileChanged}`); + excludeFiles.push(basename(fileChanged)); + } + + return shouldExclude; + }) + .some((r) => r); + }); + + return excludeFiles.sort(); +}; + +const IMAGE_EXT = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg']; + +/** + * Detect components that had styles removed (had styles before, but not anymore). + * + * @param buildCtx - the current build context + * @param componentsUpdated - list of updated component tag names + * @returns array of style updates for removed styles + */ +const getStylesRemoved = ( + buildCtx: d.BuildCtx, + componentsUpdated: string[] | null, +): d.HmrStyleUpdate[] => { + if (!componentsUpdated || componentsUpdated.length === 0) { + return []; + } + + const removedStyles: d.HmrStyleUpdate[] = []; + + for (const tagName of componentsUpdated) { + const previousModes = previousComponentStyles.get(tagName); + if (!previousModes) { + continue; + } + + // Check current component styles + const cmp = buildCtx.components.find((c) => c.tagName === tagName); + const currentModes = cmp?.styles?.map((s) => s.modeName || '$') ?? []; + + // Find modes that were removed + for (const mode of previousModes) { + if (!currentModes.includes(mode)) { + removedStyles.push({ + styleId: getScopeId(tagName, mode === '$' ? undefined : mode), + styleTag: tagName, + styleText: '', + }); + } + } + } + + return removedStyles; +}; + +/** + * Update the tracking map with current component styles for next build. + * + * @param buildCtx - the current build context + */ +const updateComponentStyleTracking = (buildCtx: d.BuildCtx): void => { + // Clear and rebuild to remove stale entries + previousComponentStyles.clear(); + + for (const cmp of buildCtx.components) { + if (cmp.styles && cmp.styles.length > 0) { + const modes = cmp.styles.map((s) => s.modeName || '$'); + previousComponentStyles.set(cmp.tagName, modes); + } + } +}; diff --git a/src/compiler/build/build-results.ts b/packages/core/src/compiler/build/build-results.ts similarity index 75% rename from src/compiler/build/build-results.ts rename to packages/core/src/compiler/build/build-results.ts index cc60d280171..abb13fbd71f 100644 --- a/src/compiler/build/build-results.ts +++ b/packages/core/src/compiler/build/build-results.ts @@ -1,11 +1,17 @@ -import { fromEntries, hasError, isString, normalizeDiagnostics } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { fromEntries, hasError, isString, normalizeDiagnostics } from '../../utils'; import { getBuildTimestamp } from './build-ctx'; import { generateHmr } from './build-hmr'; -export const generateBuildResults = (config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx) => { - const componentGraph = buildCtx.componentGraph ? fromEntries(buildCtx.componentGraph.entries()) : undefined; +export const generateBuildResults = ( + config: d.Config, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, +) => { + const componentGraph = buildCtx.componentGraph + ? fromEntries(buildCtx.componentGraph.entries()) + : undefined; const buildResults: d.CompilerBuildResults = { buildId: buildCtx.buildId, @@ -33,8 +39,8 @@ export const generateBuildResults = (config: d.Config, compilerCtx: d.CompilerCt buildResults.hmr = hmr; } - if (isString(buildCtx.hydrateAppFilePath)) { - buildResults.hydrateAppFilePath = buildCtx.hydrateAppFilePath; + if (isString(buildCtx.ssrAppFilePath)) { + buildResults.ssrAppFilePath = buildCtx.ssrAppFilePath; } compilerCtx.lastBuildResults = Object.assign({}, buildResults as any); diff --git a/src/compiler/build/build-stats.ts b/packages/core/src/compiler/build/build-stats.ts similarity index 83% rename from src/compiler/build/build-stats.ts rename to packages/core/src/compiler/build/build-stats.ts index 20865fd3d94..fb569f68b09 100644 --- a/src/compiler/build/build-stats.ts +++ b/packages/core/src/compiler/build/build-stats.ts @@ -1,6 +1,6 @@ -import { byteSize, isOutputTargetStats, result, sortBy } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { byteSize, isOutputTargetStats, result, sortBy } from '../../utils'; /** * Generates the Build Stats from the buildCtx. Writes any files to the file system. @@ -40,20 +40,17 @@ export function generateBuildStats( minifyCss: !!config.minifyCss, hashFileNames: !!config.hashFileNames, hashedFileNameLength: config.hashedFileNameLength, - buildEs5: !!config.buildEs5, }, formats: { esmBrowser: sanitizeBundlesForStats(buildCtx.esmBrowserComponentBundle), esm: sanitizeBundlesForStats(buildCtx.esmComponentBundle), - es5: sanitizeBundlesForStats(buildCtx.es5ComponentBundle), - system: sanitizeBundlesForStats(buildCtx.systemComponentBundle), commonjs: sanitizeBundlesForStats(buildCtx.commonJsComponentBundle), }, components: getComponentsFileMap(config, buildCtx), entries: buildCtx.entryModules, componentGraph: buildResults.componentGraph ?? {}, sourceGraph: getSourceGraph(config, buildCtx), - rollupResults: buildCtx.rollupResults ?? { modules: [] }, + rolldownResults: buildCtx.rolldownResults ?? { modules: [] }, collections: getCollections(config, buildCtx), }; return result.ok(stats); @@ -87,9 +84,12 @@ export async function writeBuildStats( await Promise.all( statsTargets.map(async (outputTarget) => { if (outputTarget.file) { - const result = await config.sys.writeFile(outputTarget.file, JSON.stringify(compilerBuildStats, null, 2)); + const writeResult = await config.sys.writeFile( + outputTarget.file, + JSON.stringify(compilerBuildStats, null, 2), + ); - if (result.error) { + if (writeResult.error) { config.logger.warn([`Stats failed to write file to ${outputTarget.file}`]); } } @@ -98,7 +98,9 @@ export async function writeBuildStats( }); } -function sanitizeBundlesForStats(bundleArray: ReadonlyArray): ReadonlyArray { +function sanitizeBundlesForStats( + bundleArray: ReadonlyArray, +): ReadonlyArray { if (!bundleArray) { return []; } @@ -109,10 +111,10 @@ function sanitizeBundlesForStats(bundleArray: ReadonlyArray): Re components: bundle.cmps.map((c) => c.tagName), bundleId: bundle.output.bundleId, fileName: bundle.output.fileName, - imports: bundle.rollupResult.imports, - // code: bundle.rollupResult.code, // (use this to debug) + imports: bundle.rolldownResult.imports, + // code: bundle.rolldownResult.code, // (use this to debug) // Currently, this number is inaccurate vs what seems to be on disk. - originalByteSize: byteSize(bundle.rollupResult.code), + originalByteSize: byteSize(bundle.rolldownResult.code), }; }); } @@ -122,7 +124,9 @@ function getSourceGraph(config: d.ValidatedConfig, buildCtx: d.BuildCtx) { sortBy(buildCtx.moduleFiles, (m) => m.sourceFilePath).forEach((moduleFile) => { const key = relativePath(config, moduleFile.sourceFilePath); - sourceGraph[key] = moduleFile.localImports.map((localImport) => relativePath(config, localImport)).sort(); + sourceGraph[key] = moduleFile.localImports + .map((localImport) => relativePath(config, localImport)) + .sort(); }); return sourceGraph; @@ -166,13 +170,18 @@ function getComponentsFileMap(config: d.ValidatedConfig, buildCtx: d.BuildCtx) { }); } -function getCollections(config: d.ValidatedConfig, buildCtx: d.BuildCtx): d.CompilerBuildStatCollection[] { +function getCollections( + config: d.ValidatedConfig, + buildCtx: d.BuildCtx, +): d.CompilerBuildStatCollection[] { return buildCtx.collections .map((c) => { return { name: c.collectionName, source: relativePath(config, c.moduleDir), - tags: c.moduleFiles.map((m) => m.cmps.map((cmp: d.ComponentCompilerMeta) => cmp.tagName)).sort(), + tags: c.moduleFiles + .map((m) => m.cmps.map((cmp: d.ComponentCompilerMeta) => cmp.tagName)) + .sort(), }; }) .sort((a, b) => { diff --git a/packages/core/src/compiler/build/build.ts b/packages/core/src/compiler/build/build.ts new file mode 100644 index 00000000000..5ded204010c --- /dev/null +++ b/packages/core/src/compiler/build/build.ts @@ -0,0 +1,97 @@ +import { createDocument } from '@stencil/mock-doc'; +import ts from 'typescript'; +import type * as d from '@stencil/core'; + +import { catchError, isString, readPackageJson } from '../../utils'; +import { generateOutputTargets } from '../output-targets'; +import { emptyOutputTargets } from '../output-targets/empty-dir'; +import { generateGlobalStyles } from '../style/global-styles'; +import { resetDeprecatedApiWarning } from '../transformers/decorators-to-static/component-decorator'; +import { runTsProgram, validateTypesAfterGeneration } from '../transpile/run-program'; +import { buildAbort, buildFinish } from './build-finish'; +import { writeBuild } from './write-build'; + +export const build = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + tsBuilder: ts.BuilderProgram, +) => { + try { + // reset process.cwd() for 3rd-party plugins + process.chdir(config.rootDir); + + // reset the deprecated API warning flag for this build + resetDeprecatedApiWarning(); + + // empty the directories on the first build + await emptyOutputTargets(config, compilerCtx, buildCtx); + if (buildCtx.hasError) return buildAbort(buildCtx); + + if (config.srcIndexHtml) { + const indexSrcHtml = await compilerCtx.fs.readFile(config.srcIndexHtml); + if (isString(indexSrcHtml)) { + buildCtx.indexDoc = createDocument(indexSrcHtml); + } + } + + await readPackageJson(config, compilerCtx, buildCtx); + if (buildCtx.hasError) return buildAbort(buildCtx); + + // run typescript program + const tsTimeSpan = buildCtx.createTimeSpan('transpile started'); + const emittedDts = await runTsProgram(config, compilerCtx, buildCtx, tsBuilder); + tsTimeSpan.finish('transpile finished'); + if (buildCtx.hasError) return buildAbort(buildCtx); + + // If TS emitted nothing, the "script change" was a phantom duplicate event — clear the flag + // so type validation and bundling are skipped. + if (buildCtx.isRebuild && buildCtx.hasScriptChanges && compilerCtx.changedModules.size === 0) { + buildCtx.hasScriptChanges = false; + } + + // Skip type validation on rebuilds with no script changes — the type graph is unchanged. + const skipTypeValidation = buildCtx.isRebuild && !buildCtx.hasScriptChanges; + + if (!skipTypeValidation) { + const { needsRebuild } = await validateTypesAfterGeneration( + config, + compilerCtx, + buildCtx, + tsBuilder, + emittedDts, + ); + if (buildCtx.hasError) return buildAbort(buildCtx); + + if (needsRebuild) { + // components.d.ts was just created; the current TS program lacks it. + // Return null so watch-build restarts with a fresh program. + return null; + } + // types changed but no restart needed — components.d.ts is watch-ignored + // to prevent cascade rebuilds, so just continue with the current build. + } + + // preprocess and generate styles before any outputTarget starts + buildCtx.stylesPromise = generateGlobalStyles(config, compilerCtx, buildCtx); + if (buildCtx.hasError) return buildAbort(buildCtx); + + // create outputs + await generateOutputTargets(config, compilerCtx, buildCtx); + if (buildCtx.hasError) return buildAbort(buildCtx); + + // write outputs + await buildCtx.stylesPromise; + await writeBuild(config, compilerCtx, buildCtx); + } catch (e: any) { + // ¯\_(ツ)_/¯ + catchError(buildCtx.diagnostics, e); + } + + // TODO + // clear changed files + compilerCtx.changedFiles.clear(); + + // return what we've learned today + return buildFinish(buildCtx); +}; diff --git a/src/compiler/build/compiler-ctx.ts b/packages/core/src/compiler/build/compiler-ctx.ts similarity index 75% rename from src/compiler/build/compiler-ctx.ts rename to packages/core/src/compiler/build/compiler-ctx.ts index 8e35b1c6c1e..858a7ecaa50 100644 --- a/src/compiler/build/compiler-ctx.ts +++ b/packages/core/src/compiler/build/compiler-ctx.ts @@ -1,7 +1,7 @@ -import { join, noop, normalizePath } from '@utils'; import { basename, dirname, extname } from 'path'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { join, noop, normalizePath } from '../../utils'; import { buildEvents } from '../events'; import { InMemoryFileSystem } from '../sys/in-memory-fs'; @@ -35,24 +35,41 @@ export class CompilerContext implements d.CompilerCtx { moduleMap: d.ModuleMap = new Map(); nodeMap = new WeakMap(); resolvedCollections = new Set(); - rollupCache = new Map(); - rollupCacheHydrate: any = null; - rollupCacheLazy: any = null; - rollupCacheNative: any = null; - cachedGlobalStyle: string; + rolldownCache = new Map(); + rolldownCacheSsr: any = null; + rolldownCacheLazy: any = null; + rolldownCacheNative: any = null; + cssTransformCache = new Map(); + /** + * Cross-build cache for {@link ts.transpileModule} results. + * Keyed by `"${bundleId}:${normalizedFilePath}"`. Unlike the per-instance + * cache inside typescriptPlugin (which only survives one rolldown build), + * this persists across watch rebuilds so only files that actually changed + * (i.e. appear in {@link changedModules}) need to be re-transpiled. + */ + transpileCache = new Map(); + /** + * Cross-build cache of the last style text pushed to the HMR client for + * each component scope (keyed by getScopeId result: "tag$mode"). Used by + * extTransformsPlugin to skip pushing unchanged styles to buildCtx.stylesUpdated, + * preventing the browser from re-injecting all 90+ component styles on every + * rebuild even when only one component's TS file changed. + */ + prevStylesMap = new Map(); + globalStyleCache = new Map(); styleModeNames = new Set(); worker: d.CompilerWorkerContext = null; reset() { this.cache.clear(); this.cssModuleImports.clear(); - this.cachedGlobalStyle = null; + this.globalStyleCache.clear(); this.collections.length = 0; this.compilerOptions = null; this.hasSuccessfulBuild = false; - this.rollupCacheHydrate = null; - this.rollupCacheLazy = null; - this.rollupCacheNative = null; + this.rolldownCacheSsr = null; + this.rolldownCacheLazy = null; + this.rolldownCacheNative = null; this.moduleMap.clear(); this.resolvedCollections.clear(); @@ -75,9 +92,9 @@ export class CompilerContext implements d.CompilerCtx { export const getModuleLegacy = (compilerCtx: d.CompilerCtx, sourceFilePath: string): d.Module => { sourceFilePath = normalizePath(sourceFilePath); - const moduleFile = compilerCtx.moduleMap.get(sourceFilePath); - if (moduleFile != null) { - return moduleFile; + const existingModule = compilerCtx.moduleMap.get(sourceFilePath); + if (existingModule != null) { + return existingModule; } else { const sourceFileDir = dirname(sourceFilePath); const sourceFileExt = extname(sourceFilePath); diff --git a/src/compiler/build/full-build.ts b/packages/core/src/compiler/build/full-build.ts similarity index 92% rename from src/compiler/build/full-build.ts rename to packages/core/src/compiler/build/full-build.ts index 5bca8b466cf..43ced9869c2 100644 --- a/src/compiler/build/full-build.ts +++ b/packages/core/src/compiler/build/full-build.ts @@ -1,6 +1,6 @@ import ts from 'typescript'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; import { createTsBuildProgram } from '../transpile/create-build-program'; import { build } from './build'; import { BuildContext } from './build-ctx'; @@ -47,7 +47,9 @@ export const createFullBuild = async ( tsWatchProgram.close(); tsWatchProgram = null; } - config.logger.debug('Rebuilding with fresh TypeScript program after components.d.ts generation'); + config.logger.debug( + 'Rebuilding with fresh TypeScript program after components.d.ts generation', + ); createTsBuildProgram(config, onBuild).then((program) => { tsWatchProgram = program; }); diff --git a/src/compiler/build/validate-files.ts b/packages/core/src/compiler/build/validate-files.ts similarity index 86% rename from src/compiler/build/validate-files.ts rename to packages/core/src/compiler/build/validate-files.ts index fc729155354..0159b8d9dd1 100644 --- a/src/compiler/build/validate-files.ts +++ b/packages/core/src/compiler/build/validate-files.ts @@ -1,6 +1,7 @@ -import type * as d from '../../declarations'; +import type * as d from '@stencil/core'; + +import { validateBuildPackageJson } from '../config/validate-package-json'; import { validateManifestJson } from '../html/validate-manifest-json'; -import { validateBuildPackageJson } from '../types/validate-build-package-json'; /** * Validate the existence and contents of certain files that were generated after writing the results of the build to diff --git a/packages/core/src/compiler/build/watch-build.ts b/packages/core/src/compiler/build/watch-build.ts new file mode 100644 index 00000000000..cf9308f898d --- /dev/null +++ b/packages/core/src/compiler/build/watch-build.ts @@ -0,0 +1,466 @@ +import { dirname } from 'path'; +import type * as d from '@stencil/core'; +import type ts from 'typescript'; + +import { isString, resolve } from '../../utils'; +import { compilerRequest } from '../bundle/dev-module'; +import { + filesChanged, + hasHtmlChanges, + hasScriptChanges, + hasStyleChanges, + isWatchIgnorePath, + scriptsAdded, + scriptsDeleted, +} from '../fs-watch/fs-watch-rebuild'; +import { hasServiceWorkerChanges } from '../service-worker/generate-sw'; +import { IncrementalCompiler } from '../transpile/incremental-compiler'; +import { build } from './build'; +import { BuildContext } from './build-ctx'; + +/** + * This method contains context and functionality for a watch build. This is called via + * the compiler when running a build in watch mode (i.e. `stencil build --watch`). + * + * Uses an IncrementalCompiler for TypeScript compilation with explicit cache invalidation, + * triggered by @parcel/watcher file system events. + * + * @param config The validated config for the Stencil project + * @param compilerCtx The compiler context for the project + * @returns An object containing helper methods for the dev-server's watch program + */ +export const createWatchBuild = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, +): Promise => { + let isRebuild = false; + let isBuilding = false; + let incrementalCompiler: IncrementalCompiler; + let closeResolver: Function; + const watchWaiter = new Promise( + (resolvePromise) => (closeResolver = resolvePromise), + ); + + const dirsAdded = new Set(); + const dirsDeleted = new Set(); + const filesAdded = new Set(); + const filesUpdated = new Set(); + const filesDeleted = new Set(); + + // TS files that need cache invalidation before the next rebuild + const tsFilesToInvalidate = new Set(); + + // Debounce timer — multiple watchers can fire for the same change + let rebuildTimeout: ReturnType | null = null; + + // Suppress FSEvents double-events (the same save can fire twice ~200-500ms apart), + // outside the 10ms debounce window. Drop events for files built within the cooldown period. + const recentlyBuiltFiles = new Set(); + let lastBuildFinishedAt = 0; + const DUPLICATE_EVENT_COOLDOWN_MS = 1500; + + // Files the active build was triggered by; mid-build duplicates for these are dropped. + const currentlyBuildingFiles = new Set(); + + /** Trigger a rebuild, invalidating changed TS files first. */ + const triggerRebuild = async () => { + if (isBuilding) { + rebuildTimeout = setTimeout(triggerRebuild, 50); + return; + } + + isBuilding = true; + try { + // Re-parse tsconfig when files are added or deleted so TypeScript's root + // file list stays in sync. Skipping this causes "file not found" errors + // for deleted files and silently omits new components from the build. + if (filesAdded.size > 0 || filesDeleted.size > 0) { + incrementalCompiler.refreshRootNames(); + } + + if (tsFilesToInvalidate.size > 0) { + incrementalCompiler.invalidateFiles(Array.from(tsFilesToInvalidate)); + tsFilesToInvalidate.clear(); + } + + // Snapshot pending files so mid-build duplicates can be suppressed in onFsChange. + currentlyBuildingFiles.clear(); + filesAdded.forEach((f) => currentlyBuildingFiles.add(f)); + filesUpdated.forEach((f) => currentlyBuildingFiles.add(f)); + filesDeleted.forEach((f) => currentlyBuildingFiles.add(f)); + + const tsBuilder = incrementalCompiler.rebuild(); + await onBuild(tsBuilder); + } finally { + currentlyBuildingFiles.clear(); + isBuilding = false; + } + }; + + /** + * A callback function that is invoked to trigger a rebuild of a Stencil project. This will + * update the build context with the associated file changes (these are used downstream to trigger + * HMR) and then calls the `build()` function to execute the Stencil build. + * + * @param tsBuilder The TypeScript builder program for transpilation. + */ + const onBuild = async (tsBuilder: ts.EmitAndSemanticDiagnosticsBuilderProgram) => { + const buildCtx = new BuildContext(config, compilerCtx); + buildCtx.isRebuild = isRebuild; + buildCtx.requiresFullBuild = !isRebuild; + buildCtx.dirsAdded = Array.from(dirsAdded.keys()).sort(); + buildCtx.dirsDeleted = Array.from(dirsDeleted.keys()).sort(); + buildCtx.filesAdded = Array.from(filesAdded.keys()).sort(); + buildCtx.filesUpdated = Array.from(filesUpdated.keys()).sort(); + buildCtx.filesDeleted = Array.from(filesDeleted.keys()).sort(); + buildCtx.filesChanged = filesChanged(buildCtx); + buildCtx.scriptsAdded = scriptsAdded(buildCtx); + buildCtx.scriptsDeleted = scriptsDeleted(buildCtx); + buildCtx.hasScriptChanges = hasScriptChanges(buildCtx); + buildCtx.hasStyleChanges = hasStyleChanges(buildCtx); + buildCtx.hasHtmlChanges = hasHtmlChanges(config, buildCtx); + buildCtx.hasServiceWorkerChanges = hasServiceWorkerChanges(config, buildCtx); + + if (config.logLevel === 'debug') { + config.logger.debug( + `WATCH_BUILD::watchBuild::onBuild filesAdded: ${formatFilesForDebug(buildCtx.filesAdded)}`, + ); + config.logger.debug( + `WATCH_BUILD::watchBuild::onBuild filesDeleted: ${formatFilesForDebug(buildCtx.filesDeleted)}`, + ); + config.logger.debug( + `WATCH_BUILD::watchBuild::onBuild filesUpdated: ${formatFilesForDebug(buildCtx.filesUpdated)}`, + ); + config.logger.debug( + `WATCH_BUILD::watchBuild::onBuild filesWritten: ${formatFilesForDebug(buildCtx.filesWritten)}`, + ); + } + + // Remove stale module map entries to prevent duplicate-tag build errors + Array.from(compilerCtx.moduleMap.keys()).forEach((key) => { + if (filesUpdated.has(key) || filesDeleted.has(key)) { + // Check if the file exists in the fs + const fileExists = compilerCtx.fs.accessSync(key); + if (!fileExists) { + compilerCtx.moduleMap.delete(key); + } + } + }); + + // Ensure newly added/updated files are watched + new Set([...filesUpdated, ...filesAdded]).forEach((filePath) => { + compilerCtx.addWatchFile(filePath); + }); + + dirsAdded.clear(); + dirsDeleted.clear(); + filesAdded.clear(); + filesUpdated.clear(); + filesDeleted.clear(); + + emitFsChange(compilerCtx, buildCtx); + + buildCtx.start(); + const result = await build(config, compilerCtx, buildCtx, tsBuilder); + + if (result && !result.hasError) { + isRebuild = true; + } + + // Record consumed files so late-arriving OS duplicates are suppressed. + recentlyBuiltFiles.clear(); + buildCtx.filesChanged.forEach((f) => recentlyBuiltFiles.add(f)); + lastBuildFinishedAt = Date.now(); + }; + + /** + * Returns files as a prefixed list, or 'none' if empty. + * No space before the filename — the logger wraps on whitespace. + * @param files the list of files to format for debug output + * @returns the formatted string for debug output + */ + const formatFilesForDebug = (files: ReadonlyArray): string => { + return files.length > 0 ? files.map((filename: string) => `-${filename}`).join('\n') : 'none'; + }; + + /** + * Start watchers for all relevant directories and run the initial build. + * @returns a promise that resolves when the watch program is closed. + */ + const start = async () => { + await Promise.all([ + watchFiles(compilerCtx, config.srcDir), + watchFiles(compilerCtx, config.rootDir, { recursive: false }), + ...(config.watchExternalDirs || []).map((dir) => watchFiles(compilerCtx, dir)), + ]); + + incrementalCompiler = new IncrementalCompiler(config); + const tsBuilder = incrementalCompiler.rebuild(); + await onBuild(tsBuilder); + + return watchWaiter; + }; + + /** + * A map of absolute directory paths and their associated {@link d.CompilerFileWatcher} (which contains + * the ability to teardown the watcher for the specific directory) + */ + const watchingDirs = new Map(); + /** + * A map of absolute file paths and their associated {@link d.CompilerFileWatcher} (which contains + * the ability to teardown the watcher for the specific file) + */ + const watchingFiles = new Map(); + + /** + * Callback method that will execute whenever a file change has occurred. + * This will update the appropriate set with the file path based on the + * type of change, and then will kick off a rebuild of the project. + * + * @param filePath The absolute path to the file in the Stencil project + * @param eventKind The type of file change that occurred (update, add, delete) + */ + const onFsChange: d.CompilerFileWatcherCallback = (filePath, eventKind) => { + if (incrementalCompiler && !isWatchIgnorePath(config, filePath)) { + // Drop duplicate OS events: same file within cooldown window, or mid-build duplicate. + const isDuplicateOfRecentBuild = + recentlyBuiltFiles.has(filePath) && + Date.now() - lastBuildFinishedAt < DUPLICATE_EVENT_COOLDOWN_MS; + const isDuplicateMidBuild = isBuilding && currentlyBuildingFiles.has(filePath); + if (isDuplicateOfRecentBuild || isDuplicateMidBuild) { + config.logger.debug( + `WATCH_BUILD::fs_event_change suppressed duplicate - type=${eventKind}, path=${filePath}`, + ); + return; + } + + updateCompilerCtxCache(config, compilerCtx, filePath, eventKind); + + switch (eventKind) { + case 'dirAdd': + dirsAdded.add(filePath); + break; + case 'dirDelete': + dirsDeleted.add(filePath); + break; + case 'fileAdd': + filesAdded.add(filePath); + break; + case 'fileUpdate': + filesUpdated.add(filePath); + break; + case 'fileDelete': + filesDeleted.add(filePath); + break; + } + + if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) { + tsFilesToInvalidate.add(filePath); + } + + config.logger.debug( + `WATCH_BUILD::fs_event_change - type=${eventKind}, path=${filePath}, time=${new Date().getTime()}`, + ); + + if (rebuildTimeout) { + clearTimeout(rebuildTimeout); + } + rebuildTimeout = setTimeout(() => { + rebuildTimeout = null; + triggerRebuild(); + }, 10); + } + }; + + /** + * Callback method that will execute when a directory modification has occurred. + * This will just call the `onFsChange()` callback method with the same arguments. + * + * @param filePath The absolute path to the file in the Stencil project + * @param eventKind The type of file change that occurred (update, add, delete) + */ + const onDirChange: d.CompilerFileWatcherCallback = (filePath, eventKind) => { + if (eventKind != null) { + onFsChange(filePath, eventKind); + } + }; + + /** + * Utility method to teardown the watch program and close/clear all watched files. + * + * @returns An object with the `exitCode` status of the teardown. + */ + const close = async () => { + watchingDirs.forEach((w) => w.close()); + watchingFiles.forEach((w) => w.close()); + watchingDirs.clear(); + watchingFiles.clear(); + + if (rebuildTimeout) { + clearTimeout(rebuildTimeout); + rebuildTimeout = null; + } + + const watcherCloseResults: d.WatcherCloseResults = { + exitCode: 0, + }; + closeResolver(watcherCloseResults); + return watcherCloseResults; + }; + + const request = async (data: d.CompilerRequest) => compilerRequest(config, compilerCtx, data); + + // Add a definition to the `compilerCtx` for `addWatchFile` + // This method will add the specified file path to the watched files collection and instruct + // the `CompilerSystem` what to do when a file change occurs (the `onFsChange()` callback) + compilerCtx.addWatchFile = (filePath) => { + if ( + isString(filePath) && + !watchingFiles.has(filePath) && + !isWatchIgnorePath(config, filePath) + ) { + watchingFiles.set(filePath, config.sys.watchFile(filePath, onFsChange)); + } + }; + + // Add a definition to the `compilerCtx` for `addWatchDir` + // This method will add the specified file path to the watched directories collection and instruct + // the `CompilerSystem` what to do when a directory change occurs (the `onDirChange()` callback) + compilerCtx.addWatchDir = (dirPath, recursive) => { + if (isString(dirPath) && !watchingDirs.has(dirPath) && !isWatchIgnorePath(config, dirPath)) { + watchingDirs.set(dirPath, config.sys.watchDirectory(dirPath, onDirChange, recursive)); + } + }; + + // When the compiler system destroys, we need to also destroy this watch program + config.sys.addDestroy(close); + + return { + start, + close, + on: compilerCtx.events.on, + request, + }; +}; + +/** + * A list of directories that are excluded from being watched for changes. + */ +const EXCLUDE_DIRS = ['.cache', '.git', '.github', '.stencil', '.vscode', 'node_modules']; + +/** + * A list of file extensions that are excluded from being watched for changes. + */ +const EXCLUDE_EXTENSIONS = [ + '.md', + '.markdown', + '.txt', + '.spec.ts', + '.spec.tsx', + '.e2e.ts', + '.e2e.tsx', + '.gitignore', + '.editorconfig', +]; + +/** + * Marks all root files of a Stencil project to be watched for changes. Whenever + * one of these files is determined as changed (according to TS), a rebuild of the project will execute. + * + * @param compilerCtx The compiler context for the Stencil project + * @param dir The directory to watch for changes + * @param options The options to watch files in the directory + * @param options.recursive Whether to watch files recursively + * @param options.excludeDirNames A list of directories to exclude from being watched + * @param options.excludeExtensions A list of file extensions to exclude from being watched for changes + */ +const watchFiles = async ( + compilerCtx: d.CompilerCtx, + dir: string, + options?: { + recursive?: boolean; + excludeDirNames?: string[]; + excludeExtensions?: string[]; + }, +) => { + const recursive = options?.recursive ?? true; + const excludeDirNames = options?.excludeDirNames ?? EXCLUDE_DIRS; + const excludeExtensions = options?.excludeExtensions ?? EXCLUDE_EXTENSIONS; + + /** + * non-src files that cause a rebuild + * mainly for root level config files, and getting an event when they change + */ + const rootFiles = await compilerCtx.fs.readdir(dir, { + recursive, + excludeDirNames, + excludeExtensions, + }); + + /** + * If the directory is watched recursively, we need to watch the directory itself. + */ + if (recursive) { + compilerCtx.addWatchDir(dir, true); + } + + /** + * Iterate over each file in the collection (filter out directories) and add + * a watcher for each + */ + rootFiles + .filter(({ isFile }) => isFile) + .forEach(({ absPath }) => compilerCtx.addWatchFile(absPath)); +}; + +const emitFsChange = (compilerCtx: d.CompilerCtx, buildCtx: BuildContext) => { + if ( + buildCtx.dirsAdded.length > 0 || + buildCtx.dirsDeleted.length > 0 || + buildCtx.filesUpdated.length > 0 || + buildCtx.filesAdded.length > 0 || + buildCtx.filesDeleted.length > 0 + ) { + compilerCtx.events.emit('fsChange', { + dirsAdded: buildCtx.dirsAdded.slice(), + dirsDeleted: buildCtx.dirsDeleted.slice(), + filesUpdated: buildCtx.filesUpdated.slice(), + filesAdded: buildCtx.filesAdded.slice(), + filesDeleted: buildCtx.filesDeleted.slice(), + }); + } +}; + +const updateCompilerCtxCache = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + path: string, + kind: d.CompilerFileWatcherEvent, +) => { + compilerCtx.fs.clearFileCache(path); + compilerCtx.changedFiles.add(path); + + if (kind === 'fileDelete') { + compilerCtx.moduleMap.delete(path); + } else if (kind === 'dirDelete') { + const fsRootDir = resolve('/'); + compilerCtx.moduleMap.forEach((_, moduleFilePath) => { + let moduleAncestorDir = dirname(moduleFilePath); + + for (let i = 0; i < 50; i++) { + if (moduleAncestorDir === config.rootDir || moduleAncestorDir === fsRootDir) { + break; + } + + if (moduleAncestorDir === path) { + compilerCtx.fs.clearFileCache(moduleFilePath); + compilerCtx.moduleMap.delete(moduleFilePath); + compilerCtx.changedFiles.add(moduleFilePath); + break; + } + + moduleAncestorDir = dirname(moduleAncestorDir); + } + }); + } +}; diff --git a/src/compiler/build/write-build.ts b/packages/core/src/compiler/build/write-build.ts similarity index 95% rename from src/compiler/build/write-build.ts rename to packages/core/src/compiler/build/write-build.ts index 447e4940289..09c2ca0a87a 100644 --- a/src/compiler/build/write-build.ts +++ b/packages/core/src/compiler/build/write-build.ts @@ -1,6 +1,6 @@ -import { catchError } from '@utils'; +import * as d from '@stencil/core'; -import * as d from '../../declarations'; +import { catchError } from '../../utils'; import { outputServiceWorkers } from '../output-targets/output-service-workers'; import { validateBuildFiles } from './validate-files'; import { writeExportMaps } from './write-export-maps'; diff --git a/packages/core/src/compiler/build/write-export-maps.ts b/packages/core/src/compiler/build/write-export-maps.ts new file mode 100644 index 00000000000..a430c044439 --- /dev/null +++ b/packages/core/src/compiler/build/write-export-maps.ts @@ -0,0 +1,215 @@ +import { execSync } from 'child_process'; +import type * as d from '@stencil/core'; + +import { + isOutputTargetLoaderBundle, + isOutputTargetStandalone, + isOutputTargetTypes, + join, + normalizePath, + relative, +} from '../../utils'; + +/** + * Create export map entry point definitions for the `package.json` file using the npm CLI. + * + * In v5, this uses a "smart default" approach: + * - Check if exports["."] already points to a valid output (loader-bundle or standalone) + * - If valid, leave it alone (respect user customization) + * - If missing or invalid, set a sensible default (loader-bundle > standalone priority) + * - Always ensure types field is set correctly + * - Generate per-component exports for standalone output + * + * @param config The validated Stencil config + * @param buildCtx The build context containing the components to generate export maps for + */ +export const writeExportMaps = (config: d.ValidatedConfig, buildCtx: d.BuildCtx): void => { + const loaderBundle = config.outputTargets.find(isOutputTargetLoaderBundle); + const standalone = config.outputTargets.find(isOutputTargetStandalone); + const types = config.outputTargets.find(isOutputTargetTypes); + + // Generate root export - use smart default approach + generateRootExport(config, buildCtx, loaderBundle, standalone, types); + + // Generate loader export if loader-bundle exists + // Points directly to esm/loader.js (no separate loader directory) + if (loaderBundle) { + generateLoaderExport(config, loaderBundle, types); + } + + // Generate per-component exports for standalone + if (standalone) { + generateComponentExports(config, buildCtx, standalone); + } +}; + +/** + * Generate the root export `exports["."]`. + * + * Uses smart default approach: + * - Check if current root export points to a valid loader-bundle or standalone output + * - If valid, leave it alone + * - If missing or invalid, set default (loader-bundle > standalone priority) + * @param config The validated Stencil config + * @param buildCtx The build context containing the components to generate export maps for + * @param loaderBundle The loader-bundle output target, if it exists + * @param standalone The standalone output target, if it exists + * @param types The types output target, if it exists + */ +const generateRootExport = ( + config: d.ValidatedConfig, + buildCtx: d.BuildCtx, + loaderBundle: d.OutputTargetLoaderBundle | undefined, + standalone: d.OutputTargetStandalone | undefined, + types: d.OutputTargetTypes | undefined, +): void => { + // No distributable outputs - nothing to do + if (!loaderBundle && !standalone) { + return; + } + + // Check if the current root export already points to a valid output + const currentExports = buildCtx.packageJson.exports as Record | undefined; + const currentRootExport = currentExports?.['.'] as Record | undefined; + const currentImport = currentRootExport?.import; + + // Determine if current import path is valid (points to loader-bundle or standalone) + const isValidRoot = + currentImport && isValidRootExport(config, currentImport, loaderBundle, standalone); + + // Only set root export if missing or invalid + if (!isValidRoot) { + // Priority: loader-bundle > standalone + const primaryDir = loaderBundle?.dir ?? standalone?.dir; + if (primaryDir) { + const importPath = normalizePath(relative(config.rootDir, join(primaryDir, 'index.js'))); + execSync(`npm pkg set "exports[.][import]"="${importPath}"`); + + // Set CJS require path if loader-bundle has CJS enabled + if (loaderBundle?.cjs) { + const requirePath = normalizePath( + relative(config.rootDir, join(loaderBundle.dir, 'index.cjs')), + ); + execSync(`npm pkg set "exports[.][require]"="${requirePath}"`); + } + } + } + + // Always ensure types is set correctly (from the types output target) + if (types?.dir) { + const typesPath = normalizePath(relative(config.rootDir, join(types.dir, 'index.d.ts'))); + execSync(`npm pkg set "exports[.][types]"="${typesPath}"`); + } +}; + +/** + * Check if the current root export import path is valid + * (points to either loader-bundle or standalone output). + * @param config The validated Stencil config + * @param currentImport The current import path from exports["."] + * @param loaderBundle The loader-bundle output target, if it exists + * @param standalone The standalone output target, if it exists + * @returns True if the current import path points to a valid output, false otherwise + */ +const isValidRootExport = ( + config: d.ValidatedConfig, + currentImport: string, + loaderBundle: d.OutputTargetLoaderBundle | undefined, + standalone: d.OutputTargetStandalone | undefined, +): boolean => { + const normalizedCurrent = normalizePath(currentImport); + + // Check if it points to loader-bundle + if (loaderBundle?.dir) { + const loaderBundlePath = normalizePath(relative(config.rootDir, loaderBundle.dir)); + if (normalizedCurrent.includes(loaderBundlePath)) { + return true; + } + } + + // Check if it points to standalone + if (standalone?.dir) { + const standalonePath = normalizePath(relative(config.rootDir, standalone.dir)); + if (normalizedCurrent.includes(standalonePath)) { + return true; + } + } + + return false; +}; + +/** + * Ensure a path has a relative prefix (./ or ../). + * Handles cases where normalizePath/relative may or may not add the prefix. + * @param path The path to ensure has a relative prefix + * @returns The path with a relative prefix + */ +const ensureRelativePrefix = (path: string): string => { + if (path.startsWith('./') || path.startsWith('../')) { + return path; + } + return './' + path; +}; + +/** + * Generate the loader export `exports["./loader"]`. + * + * Points directly to the esm/loader.js file in the loader-bundle output. + * No separate loader directory is generated - package.json exports handle the mapping. + * + * @param config The validated Stencil config + * @param loaderBundle The loader-bundle output target + * @param types The types output target, if it exists + */ +const generateLoaderExport = ( + config: d.ValidatedConfig, + loaderBundle: d.OutputTargetLoaderBundle, + types: d.OutputTargetTypes | undefined, +): void => { + const esmDir = join(loaderBundle.dir, 'esm'); + const esmLoaderPath = ensureRelativePrefix( + normalizePath(relative(config.rootDir, join(esmDir, 'loader.js'))), + ); + + execSync(`npm pkg set "exports[./loader][import]"="${esmLoaderPath}"`); + + // Set CJS require path if CJS is enabled + if (loaderBundle.cjs) { + const cjsDir = join(loaderBundle.dir, 'cjs'); + const cjsLoaderPath = ensureRelativePrefix( + normalizePath(relative(config.rootDir, join(cjsDir, 'loader.cjs'))), + ); + execSync(`npm pkg set "exports[./loader][require]"="${cjsLoaderPath}"`); + } + + // Types for the loader entry point + if (types?.dir) { + const typesPath = ensureRelativePrefix( + normalizePath(relative(config.rootDir, join(types.dir, 'loader.d.ts'))), + ); + execSync(`npm pkg set "exports[./loader][types]"="${typesPath}"`); + } +}; + +/** + * Generate per-component exports for standalone output. + * Each component gets its own subpath export: `exports["./my-component"]` + * @param config The validated Stencil config + * @param buildCtx The build context containing the components to generate export maps for + * @param standalone The standalone output target + */ +const generateComponentExports = ( + config: d.ValidatedConfig, + buildCtx: d.BuildCtx, + standalone: d.OutputTargetStandalone, +): void => { + let outDir = relative(config.rootDir, standalone.dir!); + if (!outDir.startsWith('.')) { + outDir = './' + outDir; + } + + buildCtx.components.forEach((cmp) => { + execSync(`npm pkg set "exports[./${cmp.tagName}][import]"="${outDir}/${cmp.tagName}.js"`); + execSync(`npm pkg set "exports[./${cmp.tagName}][types]"="${outDir}/${cmp.tagName}.d.ts"`); + }); +}; diff --git a/src/compiler/bundle/test/app-data-plugin.spec.ts b/packages/core/src/compiler/bundle/_test_/app-data-plugin.spec.ts similarity index 86% rename from src/compiler/bundle/test/app-data-plugin.spec.ts rename to packages/core/src/compiler/bundle/_test_/app-data-plugin.spec.ts index 2ebeb7d695f..de3b566f4b4 100644 --- a/src/compiler/bundle/test/app-data-plugin.spec.ts +++ b/packages/core/src/compiler/bundle/_test_/app-data-plugin.spec.ts @@ -1,6 +1,7 @@ -import * as d from '@stencil/core/declarations'; +import * as d from '@stencil/core'; import { mockValidatedConfig } from '@stencil/core/testing'; import MagicString from 'magic-string'; +import { describe, expect, it } from 'vitest'; import { appendBuildConditionals } from '../app-data-plugin'; @@ -14,7 +15,9 @@ describe('app data plugin', () => { it('should include the fsNamespace in the appended BUILD constant', () => { const { config, magicString } = setup(); appendBuildConditionals(config, {}, magicString); - expect(magicString.toString().includes(`export const BUILD = /* ${config.fsNamespace} */`)).toBe(true); + expect( + magicString.toString().includes(`export const BUILD = /* ${config.fsNamespace} */`), + ).toBe(true); }); it.each([true, false])('should include hydratedAttribute when %p', (hydratedAttribute) => { @@ -23,7 +26,9 @@ describe('app data plugin', () => { }; const { config, magicString } = setup(); appendBuildConditionals(config, conditionals, magicString); - expect(magicString.toString().includes(`hydratedAttribute: ${String(hydratedAttribute)}`)).toBe(true); + expect(magicString.toString().includes(`hydratedAttribute: ${String(hydratedAttribute)}`)).toBe( + true, + ); }); it.each([true, false])('should include hydratedClass when %p', (hydratedClass) => { diff --git a/src/compiler/bundle/test/core-resolve-plugin.spec.ts b/packages/core/src/compiler/bundle/_test_/core-resolve-plugin.spec.ts similarity index 78% rename from src/compiler/bundle/test/core-resolve-plugin.spec.ts rename to packages/core/src/compiler/bundle/_test_/core-resolve-plugin.spec.ts index 07f845368aa..c6f4a5dd805 100644 --- a/src/compiler/bundle/test/core-resolve-plugin.spec.ts +++ b/packages/core/src/compiler/bundle/_test_/core-resolve-plugin.spec.ts @@ -1,8 +1,13 @@ import { mockCompilerCtx, mockValidatedConfig } from '@stencil/core/testing'; +import { describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; import { createSystem } from '../../../compiler/sys/stencil-sys'; -import type * as d from '../../../declarations'; -import { coreResolvePlugin, getHydratedFlagHead, getStencilInternalModule } from '../core-resolve-plugin'; +import { + coreResolvePlugin, + getHydratedFlagHead, + getStencilInternalModule, +} from '../core-resolve-plugin'; import { APP_DATA_CONDITIONAL, STENCIL_JSX_RUNTIME_ID } from '../entry-alias-ids'; describe('core resolve plugin', () => { @@ -13,16 +18,16 @@ describe('core resolve plugin', () => { it('http localhost with port url path', () => { const compilerExe = 'http://localhost:3333/@stencil/core/compiler/stencil.js?v=1.2.3'; - const internalModule = 'hydrate/index.js'; + const internalModule = 'server/index.mjs'; const m = getStencilInternalModule(config, compilerExe, internalModule); - expect(m).toBe('/node_modules/@stencil/core/internal/hydrate/index.js'); + expect(m).toBe('/node_modules/@stencil/core/runtime/server/index.mjs'); }); it('node path', () => { const compilerExe = '/Users/me/node_modules/stencil/compiler/stencil.js'; const internalModule = 'client/index.js'; const m = getStencilInternalModule(config, compilerExe, internalModule); - expect(m).toBe('/Users/me/node_modules/stencil/internal/client/index.js'); + expect(m).toBe('/Users/me/node_modules/stencil/runtime/client/index.js'); }); it('should not set initialValue', () => { @@ -73,8 +78,9 @@ describe('core resolve plugin', () => { it('should resolve jsx-runtime to same path as @stencil/core for lazy builds', () => { const compilerCtx = mockCompilerCtx(config); const plugin = coreResolvePlugin(config, compilerCtx, 'client', false, true); - const resolved = (plugin.resolveId as Function)(STENCIL_JSX_RUNTIME_ID); - expect(resolved).toContain('internal/client/index.js'); + const resolveId = plugin.resolveId as { handler: (id: string) => string }; + const resolved = resolveId.handler(STENCIL_JSX_RUNTIME_ID); + expect(resolved).toContain('runtime/client/index.js'); expect(resolved).toContain(APP_DATA_CONDITIONAL); }); }); diff --git a/packages/core/src/compiler/bundle/_test_/ext-transforms-plugin.spec.ts b/packages/core/src/compiler/bundle/_test_/ext-transforms-plugin.spec.ts new file mode 100644 index 00000000000..a30d21c0547 --- /dev/null +++ b/packages/core/src/compiler/bundle/_test_/ext-transforms-plugin.spec.ts @@ -0,0 +1,112 @@ +import { + mockBuildCtx, + mockCompilerCtx, + mockModule, + mockValidatedConfig, +} from '@stencil/core/testing'; +import { describe, expect, it, vi } from 'vitest'; + +import { normalizePath } from '../../../utils'; +import * as importPathLib from '../../transformers/stencil-import-path'; +import { stubComponentCompilerMeta } from '../../types/_tests_/ComponentCompilerMeta.stub'; +import { BundleOptions } from '../bundle-interface'; +import { extTransformsPlugin } from '../ext-transforms-plugin'; + +describe('extTransformsPlugin', () => { + function setup(bundleOptsOverrides: Partial = {}) { + const config = mockValidatedConfig({ + plugins: [], + outputTargets: [ + { + type: 'stencil-rebundle', + dir: 'dist/collectionDir', + }, + ], + srcDir: '/some/stubbed/path', + }); + const compilerCtx = mockCompilerCtx(config); + const buildCtx = mockBuildCtx(config, compilerCtx); + + const compilerComponentMeta = stubComponentCompilerMeta({ + tagName: 'my-component', + componentClassName: 'MyComponent', + }); + + buildCtx.components = [compilerComponentMeta]; + + compilerCtx.moduleMap.set( + compilerComponentMeta.sourceFilePath, + mockModule({ + cmps: [compilerComponentMeta], + }), + ); + + const bundleOpts: BundleOptions = { + id: 'test-bundle', + platform: 'client', + inputs: {}, + ...bundleOptsOverrides, + }; + + const cssText = ':host { text: pink; }'; + + // mock out the read for our CSS + vi.spyOn(compilerCtx.fs, 'readFile').mockResolvedValue(cssText); + + // mock out compilerCtx.worker.transformCssToEsm because 1) we want to + // test what arguments are passed to it and 2) calling it un-mocked causes + // the infamous autoprefixer-spew-issue :( + const transformCssToEsmSpy = vi + .spyOn(compilerCtx.worker, 'transformCssToEsm') + .mockResolvedValue({ + styleText: cssText, + output: cssText, + map: null, + diagnostics: [], + imports: [], + defaultVarName: 'foo', + styleDocs: [], + }); + + const writeFileSpy = vi.spyOn(compilerCtx.fs, 'writeFile'); + return { + plugin: extTransformsPlugin(config, compilerCtx, buildCtx), + config, + compilerCtx, + buildCtx, + bundleOpts, + writeFileSpy, + transformCssToEsmSpy, + cssText, + }; + } + + describe('transform function', () => { + it('should set name', () => { + expect(setup().plugin.name).toBe('extTransformsPlugin'); + }); + + it('should return early if no data can be gleaned from the id', async () => { + const { plugin } = setup(); + // @ts-ignore we're testing something which shouldn't normally happen, + // but might if an argument of the wrong type were passed as `id` + const parseSpy = vi.spyOn(importPathLib, 'parseImportPath').mockReturnValue({ data: null }); + // @ts-ignore the Rolldown plugins expect to be called in a Rolldown context + expect(await plugin.transform('asdf', 'foo.css')).toBe(null); + parseSpy.mockRestore(); + }); + + it('should write CSS files if associated with a tag', async () => { + const { plugin, writeFileSpy } = setup(); + + // @ts-ignore the Rolldown plugins expect to be called in a Rolldown context + await plugin.transform('asdf', '/some/stubbed/path/foo.css?tag=my-component'); + + const [path, css] = writeFileSpy.mock.calls[0]; + + expect(normalizePath(path)).toBe('./dist/collectionDir/foo.css'); + + expect(css).toBe(':host { text: pink; }'); + }); + }); +}); diff --git a/packages/core/src/compiler/bundle/app-data-plugin.ts b/packages/core/src/compiler/bundle/app-data-plugin.ts new file mode 100644 index 00000000000..040e961cd6b --- /dev/null +++ b/packages/core/src/compiler/bundle/app-data-plugin.ts @@ -0,0 +1,308 @@ +import { basename } from 'path'; +import MagicString from 'magic-string'; +import ts from 'typescript'; +import type * as d from '@stencil/core'; +import type { LoadResult, Plugin, ResolveIdResult, TransformResult } from 'rolldown'; + +import { + createJsVarName, + isOutputTargetGlobalStyle, + isString, + loadTypeScriptDiagnostics, + normalizePath, +} from '../../utils'; +import { buildGlobalStyleFromInput } from '../style/global-styles'; +import { removeRebundleImports } from '../transformers/remove-rebundle-imports'; +import { + APP_DATA_CONDITIONAL, + STENCIL_APP_DATA_ID, + STENCIL_APP_GLOBALS_ID, +} from './entry-alias-ids'; +import type { BundlePlatform } from './bundle-interface'; + +/** + * A Rolldown plugin which bundles application data. + * + * @param config the Stencil configuration for a particular project + * @param compilerCtx the current compiler context + * @param buildCtx the current build context + * @param buildConditionals the set build conditionals for the build + * @param platform the platform that is being built + * @returns a Rolldown plugin which carries out the necessary work + */ +export const appDataPlugin = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + buildConditionals: d.BuildConditionals, + platform: BundlePlatform, +): Plugin => { + if (!platform) { + return { + name: 'appDataPlugin', + }; + } + const globalScripts = getGlobalScriptData(config, compilerCtx); + + return { + name: 'appDataPlugin', + + // Use Rolldown's hook filter to only call resolveId for specific Stencil IDs + resolveId: { + filter: { id: /^@stencil\/core\/runtime\/app-(data|globals)$/ }, + handler(id: string, importer: string | undefined): ResolveIdResult { + if (id === STENCIL_APP_DATA_ID || id === STENCIL_APP_GLOBALS_ID) { + if (platform === 'worker') { + this.error('@stencil/core packages cannot be imported from a worker.'); + } + + if (platform === 'ssr' || STENCIL_APP_GLOBALS_ID) { + // ssr will always bundle app-data and runtime + // and the load() fn will build a custom globals import + return id; + } else if (platform === 'client' && importer && importer.endsWith(APP_DATA_CONDITIONAL)) { + // since the importer ends with ?app-data=conditional we know that + // we need to build custom app-data based off of component metadata + // return the same "id" so that the "load()" method knows to + // build custom app-data + return id; + } + // for a client build that does not have ?app-data=conditional at the end then we + // do not want to create custom app-data, but should use the default + } + return null; + }, + }, + + async load(id: string): Promise { + if (id === STENCIL_APP_GLOBALS_ID) { + const s = new MagicString(``); + appendGlobalScripts(globalScripts, s); + await appendGlobalStyles(config, compilerCtx, buildCtx, s, platform); + return s.toString(); + } + if (id === STENCIL_APP_DATA_ID) { + // build custom app-data based off of component metadata + const s = new MagicString(``); + appendNamespace(config, s); + appendBuildConditionals(config, buildConditionals, s); + appendEnv(config, s); + return s.toString(); + } + if (id !== config.globalScript) { + return null; + } + + const module = compilerCtx.moduleMap.get(config.globalScript); + if (!module) { + return null; + } else if (!module.sourceMapFileText) { + return { + code: module.staticSourceFileText, + map: null, + }; + } + + const sourceMap: d.SourceMap = JSON.parse(module.sourceMapFileText); + sourceMap.sources = sourceMap.sources.map((src) => basename(src)); + return { code: module.staticSourceFileText, map: sourceMap }; + }, + + transform(code: string, id: string): TransformResult { + id = normalizePath(id); + if (globalScripts.some((s) => s.path === id)) { + const program = this.parse(code, {}); + const needsDefault = !(program as any).body.some( + (s: any) => s.type === 'ExportDefaultDeclaration', + ); + + if (needsDefault) { + const diagnostic: d.Diagnostic = { + level: 'warn', + type: 'build', + header: 'Missing default export in globalScript', + messageText: `globalScript should export a default function.\nSee: https://stenciljs.com/docs/config#globalscript`, + relFilePath: id, + lines: [], + }; + buildCtx.diagnostics.push(diagnostic); + } + + const defaultExport = needsDefault + ? '\nexport const globalFn = () => {};\nexport default globalFn;' + : ''; + code = code + defaultExport; + + const compilerOptions: ts.CompilerOptions = { ...config.tsCompilerOptions }; + compilerOptions.module = ts.ModuleKind.ESNext; + + const results = ts.transpileModule(code, { + compilerOptions, + fileName: id, + transformers: { + after: [removeRebundleImports(compilerCtx)], + }, + }); + buildCtx.diagnostics.push(...loadTypeScriptDiagnostics(results.diagnostics)); + + if (config.sourceMap) { + // generate the sourcemap for global script + const codeMs = new MagicString(code); + const codeMap = codeMs.generateMap({ + source: id, + // this is the name of the sourcemap, not to be confused with the `file` field in a generated sourcemap + file: id + '.map', + includeContent: true, + hires: true, + }); + + return { + code: results.outputText, + map: { + ...codeMap, + // MagicString changed their types in this PR: https://github.com/Rich-Harris/magic-string/pull/235 + // so that their `sourcesContent` is of type `(string | null)[]`. But, it will only return `[null]` if + // `includeContent` is set to `false`. Since we explicitly set `includeContent: true`, we can override + // the type to satisfy Rolldown's type expectation + sourcesContent: codeMap.sourcesContent as string[], + }, + }; + } + + return { code: results.outputText }; + } + return null; + }, + }; +}; + +export const getGlobalScriptData = (config: d.ValidatedConfig, compilerCtx: d.CompilerCtx) => { + const globalScripts: GlobalScript[] = []; + + if (isString(config.globalScript)) { + const mod = compilerCtx.moduleMap.get(config.globalScript); + const globalScript = compilerCtx.version === 2 ? config.globalScript : mod && mod.jsFilePath; + + if (globalScript) { + globalScripts.push({ + defaultName: createJsVarName(config.namespace + 'GlobalScript'), + path: normalizePath(globalScript), + }); + } + } + + compilerCtx.collections.forEach((collection) => { + if (collection.global != null && isString(collection.global.sourceFilePath)) { + let defaultName = createJsVarName(collection.collectionName + 'GlobalScript'); + if (globalScripts.some((s) => s.defaultName === defaultName)) { + defaultName += globalScripts.length; + } + globalScripts.push({ + defaultName, + path: normalizePath(collection.global.sourceFilePath), + }); + } + }); + + return globalScripts; +}; + +const appendGlobalScripts = (globalScripts: GlobalScript[], s: MagicString) => { + if (globalScripts.length === 1) { + s.prepend(`import * as appGlobalScriptNs from '${globalScripts[0].path}';\n`); + s.prepend(`const appGlobalScript = appGlobalScriptNs.default || (() => {});\n`); + s.append(`export const globalScripts = appGlobalScript;\n`); + } else if (globalScripts.length > 1) { + globalScripts.forEach((globalScript) => { + s.prepend(`import * as ${globalScript.defaultName}Ns from '${globalScript.path}';\n`); + s.prepend( + `const ${globalScript.defaultName} = ${globalScript.defaultName}Ns.default || (() => {});\n`, + ); + }); + + s.append(`export const globalScripts = () => {\n`); + s.append(` return Promise.all([\n`); + globalScripts.forEach((globalScript) => { + s.append(` ${globalScript.defaultName}(),\n`); + }); + s.append(` ]);\n`); + s.append(`};\n`); + } else { + s.append(`export const globalScripts = () => {};\n`); + } +}; + +/** + * Appends the global styles to the MagicString. + * + * Collects CSS from all global-style output targets where `inject` matches the platform: + * - `inject: 'all'` - included in both client and SSR builds + * - `inject: 'client'` - included only in client builds + * - `inject: 'none'` - not included (stylesheet must be loaded externally) + * + * @param config the Stencil configuration + * @param compilerCtx the compiler context + * @param buildCtx the build context + * @param s the MagicString to append the global styles onto + * @param platform the platform that is being built + */ +const appendGlobalStyles = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + s: MagicString, + platform: BundlePlatform, +) => { + const globalStyleTargets = config.outputTargets.filter(isOutputTargetGlobalStyle); + + // Collect CSS from targets that should be injected for this platform + const cssPromises = globalStyleTargets + .filter((target) => { + if (!target.input) return false; + if (target.inject === 'none') return false; + if (target.inject === 'all') return true; + // 'client' only injects for client platform + return target.inject === 'client' && platform === 'client'; + }) + .map((target) => buildGlobalStyleFromInput(config, compilerCtx, buildCtx, target.input!)); + + const cssResults = await Promise.all(cssPromises); + const globalStyles = cssResults.filter(Boolean).join('\n'); + + s.append(`export const globalStyles = ${JSON.stringify(globalStyles)};\n`); +}; + +/** + * Generates the `BUILD` constant that is used at compile-time in a Stencil project + * + * **This function mutates the provided {@link MagicString} argument** + * + * @param config the configuration associated with the Stencil project + * @param buildConditionals the build conditionals to serialize into a JS object + * @param s a `MagicString` to append the generated constant onto + */ +export const appendBuildConditionals = ( + config: d.ValidatedConfig, + buildConditionals: d.BuildConditionals, + s: MagicString, +): void => { + const buildData = Object.keys(buildConditionals) + .sort() + .map((key) => key + ': ' + JSON.stringify((buildConditionals as any)[key])) + .join(', '); + + s.append(`export const BUILD = /* ${config.fsNamespace} */ { ${buildData} };\n`); +}; + +const appendEnv = (config: d.ValidatedConfig, s: MagicString) => { + s.append(`export const Env = /* ${config.fsNamespace} */ ${JSON.stringify(config.env)};\n`); +}; + +const appendNamespace = (config: d.ValidatedConfig, s: MagicString) => { + s.append(`export const NAMESPACE = '${config.fsNamespace}';\n`); +}; + +interface GlobalScript { + defaultName: string; + path: string; +} diff --git a/packages/core/src/compiler/bundle/bundle-interface.ts b/packages/core/src/compiler/bundle/bundle-interface.ts new file mode 100644 index 00000000000..abf18158b41 --- /dev/null +++ b/packages/core/src/compiler/bundle/bundle-interface.ts @@ -0,0 +1,61 @@ +import type { BuildConditionals } from '@stencil/core'; +import type { SourceFile, TransformerFactory } from 'typescript'; + +/** + * Options for bundled output passed on Rolldown + * + * This covers the ID for the bundle, the platform it runs on, input modules, + * and more + */ +export interface BundleOptions { + id: string; + conditionals?: BuildConditionals; + /** + * When `true`, all `@stencil/core/*` packages will be treated as external + * and omitted from the generated bundle. + */ + externalRuntime?: boolean; + platform: BundlePlatform; + /** + * A collection of TypeScript transformation factories to apply during the "before" stage of the TypeScript + * compilation pipeline (before built-in .js transformations) + */ + customBeforeTransformers?: TransformerFactory[]; + /** + * This is equivalent to the Rolldown `input` configuration option. It's + * an object mapping names to entry points which tells Rolldown to bundle + * each thing up as a separate output chunk. + * + * @see {@link https://rolldownjs.org/guide/en/#input} + */ + inputs: { [entryKey: string]: string }; + /** + * A map of strings which are passed to the Stencil-specific loader plugin + * which we use to resolve the imports of Stencil project files when building + * with Rolldown. + * + * @see {@link loader-plugin:loaderPlugin} + */ + loader?: { [id: string]: string }; + /** + * Rolldown's `codeSplitting` output option (replaces Rolldown's `inlineDynamicImports`). + * + * When false, dynamic imports (i.e. `import()` calls) are inlined as part of the same + * chunk being bundled rather than being created as separate chunks. + * + * @see {@link https://rolldown.rs/reference/outputoptions.codesplitting} + */ + codeSplitting?: boolean; + inlineWorkers?: boolean; + /** + * Duplicate of Rolldown's `preserveEntrySignatures` option. + * + * "Controls if Rolldown tries to ensure that entry chunks have the same + * exports as the underlying entry module." + * + * @see {@link https://rolldownjs.org/guide/en/#preserveentrysignatures} + */ + preserveEntrySignatures?: false | 'strict' | 'allow-extension' | 'exports-only'; +} + +export type BundlePlatform = 'client' | 'ssr' | 'worker'; diff --git a/packages/core/src/compiler/bundle/bundle-output.ts b/packages/core/src/compiler/bundle/bundle-output.ts new file mode 100644 index 00000000000..43a7b26f11f --- /dev/null +++ b/packages/core/src/compiler/bundle/bundle-output.ts @@ -0,0 +1,183 @@ +import { rolldown, InputOptions, TreeshakingOptions, Plugin } from 'rolldown'; +import type * as d from '@stencil/core'; + +import { createOnWarnFn, loadRolldownDiagnostics } from '../../utils'; +import { lazyComponentPlugin } from '../output-targets/dist-lazy/lazy-component-plugin'; +import { appDataPlugin } from './app-data-plugin'; +import { coreResolvePlugin } from './core-resolve-plugin'; +import { devNodeModuleResolveId } from './dev-node-module-resolve'; +import { extFormatPlugin } from './ext-format-plugin'; +import { extTransformsPlugin } from './ext-transforms-plugin'; +import { fileLoadPlugin } from './file-load-plugin'; +import { loaderPlugin } from './loader-plugin'; +import { pluginHelper } from './plugin-helper'; +import { serverPlugin } from './server-plugin'; +import { typescriptPlugin } from './typescript-plugin'; +import { userIndexPlugin } from './user-index-plugin'; +import { workerPlugin } from './worker-plugin'; +import type { BundleOptions } from './bundle-interface'; + +export const bundleOutput = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + bundleOpts: BundleOptions, +) => { + try { + const rolldownOptions = getRolldownOptions(config, compilerCtx, buildCtx, bundleOpts); + const rolldownBuild = await rolldown(rolldownOptions); + return rolldownBuild; + } catch (e: any) { + if (!buildCtx.hasError) { + // TODO(STENCIL-353): Implement a type guard that balances using our own copy of Rolldown types (which are + // breakable) and type safety (so that the error variable may be something other than `any`) + loadRolldownDiagnostics(config, compilerCtx, buildCtx, e); + } + } + return undefined; +}; + +/** + * Build the rolldown options that will be used to transpile, minify, and otherwise transform a Stencil project + * @param config the Stencil configuration for the project + * @param compilerCtx the current compiler context + * @param buildCtx a context object containing information about the current build + * @param bundleOpts Rolldown bundling options to apply to the base configuration setup by this function + * @returns the rolldown options to be used + */ +export const getRolldownOptions = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + bundleOpts: BundleOptions, +): InputOptions => { + const beforePlugins = config.rolldownPlugins.before || []; + const afterPlugins = config.rolldownPlugins.after || []; + + // Create a plugin for dev module resolution if enabled + const devModulePlugin: Plugin | null = config.devServer?.experimentalDevModules + ? { + name: 'stencil-dev-module-resolve', + async resolveId(importee: string, importer: string | undefined) { + // Let other plugins handle it first, then intercept the result + const resolved = await this.resolve(importee, importer, { skipSelf: true }); + if (resolved) { + return devNodeModuleResolveId(config, compilerCtx.fs, resolved, importee); + } + return null; + }, + } + : null; + + const rolldownOptions: InputOptions = { + input: bundleOpts.inputs, + platform: bundleOpts.platform === 'ssr' ? 'node' : 'browser', + tsconfig: config.tsconfig, + + plugins: [ + coreResolvePlugin( + config, + compilerCtx, + bundleOpts.platform, + !!bundleOpts.externalRuntime, + bundleOpts.conditionals?.lazyLoad ?? false, + ), + appDataPlugin(config, compilerCtx, buildCtx, bundleOpts.conditionals, bundleOpts.platform), + lazyComponentPlugin(buildCtx), + loaderPlugin(bundleOpts.loader), + userIndexPlugin(config, compilerCtx), + typescriptPlugin(compilerCtx, bundleOpts, config), + extFormatPlugin(config), + extTransformsPlugin(config, compilerCtx, buildCtx), + workerPlugin(config, compilerCtx, buildCtx, bundleOpts.platform, !!bundleOpts.inlineWorkers), + serverPlugin(config, bundleOpts.platform), + ...beforePlugins, + devModulePlugin, + ...afterPlugins, + pluginHelper(config, buildCtx, bundleOpts.platform), + fileLoadPlugin(compilerCtx.fs), + ].filter(Boolean) as Plugin[], + + resolve: { + // Stencil-specific main fields plus standard ones + mainFields: ['jsnext:main', 'es2017', 'es2015', 'module', 'main'] as any, + // Export conditions for package.json exports field + conditionNames: (bundleOpts.platform === 'ssr' + ? ['node', 'import', 'require', 'default'] + : ['browser', 'default', 'import', 'module', 'require']) as string[], + // File extensions to resolve (includes .d.ts for type declaration files) + extensions: [ + '.tsx', + '.ts', + '.mts', + '.cts', + '.js', + '.mjs', + '.cjs', + '.json', + '.d.ts', + '.d.mts', + '.d.cts', + ] as any, + // Apply user's nodeResolve config if provided + ...config.nodeResolve, + }, + + transform: { + define: { + 'process.env.NODE_ENV': config.devMode ? '"development"' : '"production"', + }, + }, + + // Disable warnings about built-in features we're intentionally using + checks: { + preferBuiltinFeature: false, + pluginTimings: config.logLevel === 'debug', + }, + + // Tell Rolldown to treat these files as JS - our plugins transform them to ESM + // CSS: ext-transforms-plugin handles CSS to ESM conversion + // Text/assets: ext-format-plugin handles text/url to ESM conversion + moduleTypes: { + '.css': 'js', + '.scss': 'js', + '.sass': 'js', + '.less': 'js', + '.styl': 'js', + '.stylus': 'js', + '.pcss': 'js', + // Text formats (from ext-format-plugin FORMAT_TEXT_EXTS) + '.txt': 'js', + '.frag': 'js', + '.vert': 'js', + // URL formats (from ext-format-plugin FORMAT_URL_MIME) + '.svg': 'js', + }, + + treeshake: getTreeshakeOption(config, bundleOpts), + preserveEntrySignatures: bundleOpts.preserveEntrySignatures ?? 'strict', + external: config.rolldownConfig.inputOptions.external, + onwarn: createOnWarnFn(buildCtx.diagnostics), + }; + + return rolldownOptions; +}; + +const getTreeshakeOption = ( + config: d.ValidatedConfig, + bundleOpts: BundleOptions, +): TreeshakingOptions | boolean => { + if (bundleOpts.platform === 'ssr') { + return { + moduleSideEffects: false, + propertyReadSideEffects: false, + }; + } + if (config.devMode || config.rolldownConfig.inputOptions.treeshake === false) { + return false; + } + return { + moduleSideEffects: false, + propertyReadSideEffects: false, + }; +}; diff --git a/src/compiler/bundle/constants.ts b/packages/core/src/compiler/bundle/constants.ts similarity index 100% rename from src/compiler/bundle/constants.ts rename to packages/core/src/compiler/bundle/constants.ts diff --git a/packages/core/src/compiler/bundle/core-resolve-plugin.ts b/packages/core/src/compiler/bundle/core-resolve-plugin.ts new file mode 100644 index 00000000000..e449c6e5f31 --- /dev/null +++ b/packages/core/src/compiler/bundle/core-resolve-plugin.ts @@ -0,0 +1,247 @@ +import { dirname } from 'path'; +import type * as d from '@stencil/core'; +import type { Plugin } from 'rolldown'; + +import { HYDRATED_CSS } from '../../runtime/runtime-constants'; +import { isRemoteUrl, join, normalizeFsPath, normalizePath } from '../../utils'; +import { fetchModuleAsync } from '../sys/fetch/fetch-module-async'; +import { getStencilModuleUrl, packageVersions } from '../sys/fetch/fetch-utils'; +import { + APP_DATA_CONDITIONAL, + STENCIL_CORE_ID, + STENCIL_INTERNAL_CLIENT_PLATFORM_ID, + STENCIL_INTERNAL_SSR_PLATFORM_ID, + STENCIL_INTERNAL_ID, + STENCIL_JSX_DEV_RUNTIME_ID, + STENCIL_JSX_RUNTIME_ID, +} from './entry-alias-ids'; +import type { BundlePlatform } from './bundle-interface'; + +export const coreResolvePlugin = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + platform: BundlePlatform, + externalRuntime: boolean, + lazyLoad: boolean, +): Plugin => { + const compilerExe = config.sys.getCompilerExecutingPath(); + const internalClient = getStencilInternalModule(config, compilerExe, 'client/index.js'); + const internalSsr = getStencilInternalModule(config, compilerExe, 'server/index.mjs'); + + // Cache transformed file content - the hydrated flag replacements are deterministic + const transformedCodeCache = new Map(); + + // Pre-compute hydrated flag replacement info once + const hydratedFlag = config.hydratedFlag; + const hydratedFlagHead = hydratedFlag ? getHydratedFlagHead(hydratedFlag) : null; + const hydratedReplacements: Array<[string, string]> | null = + hydratedFlag && hydratedFlagHead !== HYDRATED_CSS + ? buildHydratedReplacements(hydratedFlag, hydratedFlagHead) + : null; + + // Build filter for load hook - only process the internal client/ssr runtime files + // Must also match paths with query strings (e.g., ?app-data=conditional for lazy builds) + const escapeRegex = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const loadFilter = new RegExp( + `^(${escapeRegex(internalClient)}|${escapeRegex(internalSsr)})(\\?.*)?$`, + ); + + return { + name: 'coreResolvePlugin', + + // Use Rolldown's hook filter to only call this plugin for @stencil/core imports + // This avoids JS<->Rust boundary crossing for every import in the bundle + resolveId: { + filter: { id: /^@stencil\/core/ }, + handler(id) { + if (id === STENCIL_CORE_ID || id === STENCIL_INTERNAL_ID) { + if (platform === 'client') { + if (externalRuntime) { + return { + id: STENCIL_INTERNAL_CLIENT_PLATFORM_ID, + external: true, + }; + } + if (lazyLoad) { + // with a lazy / loader-bundle build, add `?app-data=conditional` as an identifier to ensure we don't + // use the default app-data, but build a custom one based on component meta + return internalClient + APP_DATA_CONDITIONAL; + } + // for a non-lazy / standalone build, use the default, complete core. + // This ensures all features are available for any importer library + return internalClient; + } + if (platform === 'ssr') { + return internalSsr; + } + } + if (id === STENCIL_INTERNAL_CLIENT_PLATFORM_ID) { + if (externalRuntime) { + // not bundling the client runtime and the user's component together this + // must be the custom elements build, where @stencil/core/runtime/client + // is an import, rather than bundling + return { + id: STENCIL_INTERNAL_CLIENT_PLATFORM_ID, + external: true, + }; + } + // importing @stencil/core/runtime/client directly, so it shouldn't get + // the custom app-data conditionals + return internalClient; + } + if (id === STENCIL_INTERNAL_SSR_PLATFORM_ID) { + return internalSsr; + } + // Handle jsx-runtime and jsx-dev-runtime imports + // These must resolve to the same internal client path as @stencil/core + // to prevent Rolldown from bundling duplicate runtime code with different + // minified property names, which causes VNode property mismatches during hydration + if (id === STENCIL_JSX_RUNTIME_ID || id === STENCIL_JSX_DEV_RUNTIME_ID) { + if (platform === 'client') { + if (externalRuntime) { + return { + id: STENCIL_INTERNAL_CLIENT_PLATFORM_ID, + external: true, + }; + } + if (lazyLoad) { + // with a lazy / loader-bundle build, add `?app-data=conditional` as an identifier to ensure we don't + // use the default app-data, but build a custom one based on component meta + return internalClient + APP_DATA_CONDITIONAL; + } + // for a non-lazy / standalone build, use the default, complete core. + return internalClient; + } + if (platform === 'ssr') { + return internalSsr; + } + } + return null; + }, + }, + + load: { + filter: { id: loadFilter }, + async handler(filePath) { + if (filePath && !filePath.startsWith('\0')) { + filePath = normalizeFsPath(filePath); + + if (filePath === internalClient || filePath === internalSsr) { + if (platform === 'worker') { + return ` +export const Build = { + isDev: ${config.devMode}, + isBrowser: true, + isServer: false, + isTesting: false, +};`; + } + + // Check cache first - transformed content is deterministic per file + const cached = transformedCodeCache.get(filePath); + if (cached) { + return cached; + } + + let code = await compilerCtx.fs.readFile(filePath); + + if (typeof code !== 'string' && isRemoteUrl(compilerExe)) { + const url = getStencilModuleUrl(compilerExe, filePath); + code = await fetchModuleAsync( + config.sys, + compilerCtx.fs, + packageVersions, + url, + filePath, + ); + } + + if (typeof code === 'string') { + // Apply pre-computed hydrated flag replacements in a single pass + if (hydratedReplacements) { + for (const [search, replace] of hydratedReplacements) { + code = code.replace(search, replace); + } + } else if (!hydratedFlag) { + code = code.replace(HYDRATED_CSS, '{}'); + } + + // Cache the transformed result + transformedCodeCache.set(filePath, code); + } + + return code; + } + } + return null; + }, + }, + }; +}; + +export const getStencilInternalModule = ( + config: d.ValidatedConfig, + compilerExe: string, + internalModule: string, +) => { + if (isRemoteUrl(compilerExe)) { + return normalizePath( + config.sys.getLocalModulePath({ + rootDir: config.rootDir, + moduleId: '@stencil/core', + path: 'runtime/' + internalModule, + }), + ); + } + + const compilerExeDir = dirname(compilerExe); + return normalizePath(join(compilerExeDir, '..', 'runtime', internalModule)); +}; + +export const getHydratedFlagHead = (h: d.HydratedFlag) => { + // {visibility:hidden}.hydrated{visibility:inherit} + + let initial: string; + let hydrated: string; + + if (!String(h.initialValue) || h.initialValue === '' || h.initialValue == null) { + initial = ''; + } else { + initial = `{${h.property}:${h.initialValue}}`; + } + + const selector = h.selector === 'attribute' ? `[${h.name}]` : `.${h.name}`; + + if (!String(h.hydratedValue) || h.hydratedValue === '' || h.hydratedValue == null) { + hydrated = ''; + } else { + hydrated = `${selector}{${h.property}:${h.hydratedValue}}`; + } + + return initial + hydrated; +}; + +/** + * Pre-build all hydrated flag string replacements to avoid repeated computation. + * Returns an array of [search, replace] tuples to apply in sequence. + * @param hydratedFlag the hydrated flag configuration + * @param hydratedFlagHead the pre-computed CSS string for the hydrated flag + * @returns an array of [search, replace] tuples for string replacement + */ +const buildHydratedReplacements = ( + hydratedFlag: d.HydratedFlag, + hydratedFlagHead: string, +): Array<[string, string]> => { + const replacements: Array<[string, string]> = [[HYDRATED_CSS, hydratedFlagHead]]; + + if (hydratedFlag.name !== 'hydrated') { + replacements.push( + [`.classList.add("hydrated")`, `.classList.add("${hydratedFlag.name}")`], + [`.classList.add('hydrated')`, `.classList.add('${hydratedFlag.name}')`], + [`.setAttribute("hydrated",`, `.setAttribute("${hydratedFlag.name}",`], + [`.setAttribute('hydrated',`, `.setAttribute('${hydratedFlag.name}',`], + ); + } + + return replacements; +}; diff --git a/src/compiler/bundle/dev-module.ts b/packages/core/src/compiler/bundle/dev-module.ts similarity index 93% rename from src/compiler/bundle/dev-module.ts rename to packages/core/src/compiler/bundle/dev-module.ts index ce630568724..a855966c232 100644 --- a/src/compiler/bundle/dev-module.ts +++ b/packages/core/src/compiler/bundle/dev-module.ts @@ -1,10 +1,10 @@ -import { generatePreamble, join, relative } from '@utils'; import { basename, dirname } from 'path'; -import { OutputOptions, rollup } from 'rollup'; +import { OutputOptions, rolldown } from 'rolldown'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { generatePreamble, join, relative } from '../../utils'; import { BuildContext } from '../build/build-ctx'; -import { getRollupOptions } from './bundle-output'; +import { getRolldownOptions } from './bundle-output'; import { DEV_MODULE_CACHE_BUSTER, DEV_MODULE_DIR } from './constants'; export const compilerRequest = async ( @@ -89,14 +89,14 @@ const bundleDevModule = async ( const buildCtx = new BuildContext(config, compilerCtx); try { - const inputOpts = getRollupOptions(config, compilerCtx, buildCtx, { + const inputOpts = getRolldownOptions(config, compilerCtx, buildCtx, { id: parsedUrl.nodeModuleId, platform: 'client', inputs: { index: parsedUrl.nodeResolvedPath, }, }); - const rollupBuild = await rollup(inputOpts); + const rolldownBuild = await rolldown(inputOpts); const outputOpts: OutputOptions = { banner: generatePreamble(config), @@ -109,7 +109,7 @@ const bundleDevModule = async ( inputOpts.input = parsedUrl.nodeResolvedPath; } - const r = await rollupBuild.generate(outputOpts); + const r = await rolldownBuild.generate(outputOpts); if (buildCtx.hasError) { results.status = 500; diff --git a/src/compiler/bundle/dev-node-module-resolve.ts b/packages/core/src/compiler/bundle/dev-node-module-resolve.ts similarity index 88% rename from src/compiler/bundle/dev-node-module-resolve.ts rename to packages/core/src/compiler/bundle/dev-node-module-resolve.ts index 58f0c94ff12..f683736fa06 100644 --- a/src/compiler/bundle/dev-node-module-resolve.ts +++ b/packages/core/src/compiler/bundle/dev-node-module-resolve.ts @@ -1,8 +1,8 @@ -import { join, relative } from '@utils'; import { basename, dirname } from 'path'; -import { ResolveIdResult } from 'rollup'; +import { ResolveIdResult } from 'rolldown'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { join, relative } from '../../utils'; import { InMemoryFileSystem } from '../sys/in-memory-fs'; import { DEV_MODULE_DIR } from './constants'; @@ -35,13 +35,18 @@ export const devNodeModuleResolveId = async ( let pkgJsonData: d.PackageJsonData; try { pkgJsonData = JSON.parse(pkgJsonStr); - } catch (e) {} + } catch {} if (!pkgJsonData || !pkgJsonData.version) { return resolvedId; } - resolvedId.id = serializeDevNodeModuleUrl(config, pkgJsonData.name, pkgJsonData.version, resolvedPath); + resolvedId.id = serializeDevNodeModuleUrl( + config, + pkgJsonData.name, + pkgJsonData.version, + resolvedPath, + ); resolvedId.external = true; return resolvedId; diff --git a/packages/core/src/compiler/bundle/entry-alias-ids.ts b/packages/core/src/compiler/bundle/entry-alias-ids.ts new file mode 100644 index 00000000000..f089deb50c0 --- /dev/null +++ b/packages/core/src/compiler/bundle/entry-alias-ids.ts @@ -0,0 +1,13 @@ +export const STENCIL_CORE_ID = '@stencil/core'; +export const STENCIL_INTERNAL_ID = '@stencil/core/runtime'; +export const STENCIL_APP_DATA_ID = '@stencil/core/runtime/app-data'; +export const STENCIL_APP_GLOBALS_ID = '@stencil/core/runtime/app-globals'; +export const STENCIL_SSR_FACTORY_ID = '@stencil/core/runtime/server/ssr-factory'; +export const STENCIL_INTERNAL_CLIENT_PLATFORM_ID = '@stencil/core/runtime/client'; +export const STENCIL_INTERNAL_SSR_PLATFORM_ID = '@stencil/core/runtime/server'; +export const STENCIL_JSX_RUNTIME_ID = '@stencil/core/jsx-runtime'; +export const STENCIL_JSX_DEV_RUNTIME_ID = '@stencil/core/jsx-dev-runtime'; +export const APP_DATA_CONDITIONAL = '?app-data=conditional'; +export const LAZY_BROWSER_ENTRY_ID = '@lazy-browser-entrypoint' + APP_DATA_CONDITIONAL; +export const LAZY_EXTERNAL_ENTRY_ID = '@lazy-external-entrypoint' + APP_DATA_CONDITIONAL; +export const USER_INDEX_ENTRY_ID = '@user-index-entrypoint'; diff --git a/src/compiler/bundle/ext-format-plugin.ts b/packages/core/src/compiler/bundle/ext-format-plugin.ts similarity index 89% rename from src/compiler/bundle/ext-format-plugin.ts rename to packages/core/src/compiler/bundle/ext-format-plugin.ts index 2642c9468fb..222c03495bb 100644 --- a/src/compiler/bundle/ext-format-plugin.ts +++ b/packages/core/src/compiler/bundle/ext-format-plugin.ts @@ -1,8 +1,8 @@ -import { createJsVarName, normalizeFsPathQuery } from '@utils'; import { basename } from 'path'; -import type { Plugin, TransformPluginContext, TransformResult } from 'rollup'; +import type * as d from '@stencil/core'; +import type { Plugin, TransformPluginContext, TransformResult } from 'rolldown'; -import type * as d from '../../declarations'; +import { createJsVarName, normalizeFsPathQuery } from '../../utils'; export const extFormatPlugin = (config: d.ValidatedConfig): Plugin => { return { @@ -67,7 +67,9 @@ const formatUrl = ( const varName = createJsVarName(basename(filePath)); const base64 = config.sys.encodeToBase64(code); if (config.devMode && base64.length > DATAURL_MAX_IMAGE_SIZE) { - pluginCtx.warn(`Importing large files will bloat your bundle size, please use external assets instead.`); + pluginCtx.warn( + `Importing large files will bloat your bundle size, please use external assets instead.`, + ); } return `const ${varName} = 'data:${mime};base64,${base64}';export default ${varName};`; diff --git a/packages/core/src/compiler/bundle/ext-transforms-plugin.ts b/packages/core/src/compiler/bundle/ext-transforms-plugin.ts new file mode 100644 index 00000000000..23ba76856da --- /dev/null +++ b/packages/core/src/compiler/bundle/ext-transforms-plugin.ts @@ -0,0 +1,313 @@ +import type * as d from '@stencil/core'; +import type { Plugin } from 'rolldown'; + +import { + hasError, + isOutputTargetStencilRebundle, + isOutputTargetDocs, + join, + mergeIntoWith, + normalizeFsPath, + relative, +} from '../../utils'; +import { runPluginTransformsEsmImports } from '../plugin/plugin'; +import { getScopeId } from '../style/scope-css'; +import { parseImportPath } from '../transformers/stencil-import-path'; + +/** + * This keeps a map of all the component styles we've seen already so we can create + * a correct state of all styles when we're doing a rebuild. This map helps by + * storing the state of all styles as follows, e.g.: + * + * ``` + * { + * 'cmp-a-$': { + * '/path/to/project/cmp-a.scss': 'button{color:red}', + * '/path/to/project/cmp-a.md.scss': 'button{color:blue}' + * } + * ``` + * + * Whenever one of the files change, we can propagate a correct concatenated + * version of all styles to the browser by setting `buildCtx.stylesUpdated`. + */ +type ComponentStyleMap = Map; +const allCmpStyles = new Map(); + +/** + * A Rolldown plugin which bundles up some transformation of CSS imports as well + * as writing some files to disk for the `STENCIL_META` output target. + * + * @param config a user-supplied configuration + * @param compilerCtx the current compiler context + * @param buildCtx the current build context + * @returns a Rolldown plugin which carries out the necessary work + */ +export const extTransformsPlugin = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, +): Plugin => { + let transformCount = 0; + let cacheHits = 0; + let firstTransformStart: number | null = null; + let lastTransformEnd: number = 0; + + return { + name: 'extTransformsPlugin', + + buildEnd() { + if (config.logLevel === 'debug' && firstTransformStart !== null) { + const totalElapsed = lastTransformEnd - firstTransformStart; + const computed = transformCount - cacheHits; + buildCtx.debug( + `extTransformsPlugin: ${transformCount} stylesheets in ${totalElapsed.toFixed(1)}ms wall-clock` + + (cacheHits > 0 ? ` (${computed} computed, ${cacheHits} from cache)` : ''), + ); + } + }, + + /** + * A custom function targeting the `transform` build hook in Rolldown. See here for details: + * https://rolldownjs.org/guide/en/#transform + * + * Here we are ignoring the first argument (which contains the module's source code) and + * only looking at the `id` argument. We use that `id` to get information about the module + * in question from disk ourselves so that we can then do some transformations on it. + * + * @param _ an unused parameter (normally the code for a given module) + * @param id the id of a module + * @returns metadata for Rolldown or null if no transformation should be done + */ + async transform(_, id) { + if (/\0/.test(id)) { + return null; + } + + /** + * Make sure compiler context has a registered worker. The interface suggests that it + * potentially can be undefined, therefore check for it here. + */ + if (!compilerCtx.worker) { + return null; + } + + // The `id` here was possibly previously updated using + // `serializeImportPath` to annotate the filepath with various metadata + // serialized to query-params. If that was done for this particular `id` + // then the `data` prop will not be null. + const { data } = parseImportPath(id); + + if (data != null) { + const filePath = normalizeFsPath(id); + + // --------------------------------------------------------------------------- + // Memoize the expensive SASS + Lightning CSS computation across output targets. + // customElements, lazy, and hydrate all process the same ~N stylesheets; + // caching here means only the first output target pays the full per-sheet + // cost — subsequent targets hit the cache and complete in microseconds. + // + // NOTE: this cache is keyed by the raw annotated `id` which encodes the + // file path plus component metadata (tag, mode, encapsulation). Entries are + // invalidated in `invalidateRolldownCaches` whenever the source file or a + // SASS dependency changes. + // --------------------------------------------------------------------------- + // Check before the populate block so we can distinguish cache hits from misses. + const wasCached = compilerCtx.cssTransformCache.has(id); + + if (!wasCached) { + const code = await compilerCtx.fs.readFile(filePath); + if (typeof code !== 'string') { + compilerCtx.cssTransformCache.set(id, null); + } else { + const pluginTransforms = await runPluginTransformsEsmImports( + config, + compilerCtx, + buildCtx, + code, + filePath, + ); + const cssTransformResults = await compilerCtx.worker.transformCssToEsm({ + file: pluginTransforms.id, + input: pluginTransforms.code, + tag: data.tag, + tags: buildCtx.components.map((c) => c.tagName), + addTagTransformers: !!buildCtx.config.extras.additionalTagTransformers, + encapsulation: data.encapsulation, + mode: data.mode, + sourceMap: config.sourceMap, + minify: config.minifyCss, + autoprefixer: config.autoprefixCss, + // Extract docs if any docs output targets are configured + docs: config.outputTargets.some(isOutputTargetDocs), + }); + compilerCtx.cssTransformCache.set(id, { + pluginTransformId: pluginTransforms.id, + pluginTransformCode: pluginTransforms.code, + pluginTransformDependencies: pluginTransforms.dependencies, + pluginTransformDiagnostics: pluginTransforms.diagnostics, + cssTransformOutput: cssTransformResults, + }); + } + } + + const cacheEntry = compilerCtx.cssTransformCache.get(id); + if (wasCached) { + cacheHits++; + } + if (cacheEntry == null) { + return null; + } + + // --------------------------------------------------------------------------- + // Replay the cheap per-output-target side effects using the cached data. + // None of these are CPU-bound; they're all O(1) Map or array operations. + // --------------------------------------------------------------------------- + + /** + * add file to watch list if it is outside of the `srcDir` config path + */ + if ( + config.watch && + (id.startsWith('/') || id.startsWith('.')) && + !id.startsWith(config.srcDir) + ) { + compilerCtx.addWatchFile(id.split('?')[0]); + } + + if (firstTransformStart === null) { + firstTransformStart = performance.now(); + } + + let cmpStyles: ComponentStyleMap | undefined = undefined; + let cmp: d.ComponentCompilerMeta | undefined = undefined; + + if (data.tag) { + cmp = buildCtx.components.find((c) => c.tagName === data.tag); + const moduleFile = + cmp && !cmp.isCollectionDependency && compilerCtx.moduleMap.get(cmp.sourceFilePath); + + if (moduleFile) { + const rebundleDirs = config.outputTargets.filter(isOutputTargetStencilRebundle); + const relPath = relative(config.srcDir, cacheEntry.pluginTransformId); + + // Write the transformed CSS file to any stencil-rebundle output target dirs. + // This uses cached data so it only does I/O, no re-computation. + await Promise.all( + rebundleDirs.map(async (outputTarget) => { + const rebundlePath = join(outputTarget.dir!, relPath); + await compilerCtx.fs.writeFile(rebundlePath, cacheEntry.pluginTransformCode); + }), + ); + } + + /** + * initiate map for component styles + */ + const scopeId = getScopeId(data.tag, data.mode); + if (!allCmpStyles.has(scopeId)) { + allCmpStyles.set(scopeId, new Map()); + } + cmpStyles = allCmpStyles.get(scopeId); + } + + transformCount++; + lastTransformEnd = performance.now(); + + /** + * persist component styles for transformed stylesheet + */ + if (cmpStyles) { + cmpStyles.set(filePath, cacheEntry.cssTransformOutput.styleText); + } + + // Set style docs + if (cmp) { + cmp.styleDocs ||= []; + mergeIntoWith( + cmp.styleDocs, + cacheEntry.cssTransformOutput.styleDocs, + (docs) => `${docs.name},${docs.mode}`, + ); + } + + // Track dependencies + for (const dep of cacheEntry.pluginTransformDependencies) { + this.addWatchFile(dep); + compilerCtx.addWatchFile(dep); + } + + buildCtx.diagnostics.push(...cacheEntry.pluginTransformDiagnostics); + buildCtx.diagnostics.push(...cacheEntry.cssTransformOutput.diagnostics); + const didError = + hasError(cacheEntry.cssTransformOutput.diagnostics) || + hasError(cacheEntry.pluginTransformDiagnostics); + if (didError) { + this.error('Plugin CSS transform error'); + } + + /** + * if the style has updated, compose all styles for the component + */ + if (data.tag && data.mode) { + // Find the style entry for the current mode (not always styles[0] which is the default mode). + const currentModeStyle = cmp?.styles?.find((s) => s.modeName === data.mode); + const externalStyles = currentModeStyle?.externalStyles; + + /** + * if component has external styles, use a list to keep the order to which + * styles are applied. + */ + const styleText = cmpStyles + ? externalStyles + ? /** + * attempt to find the original `filePath` key through `originalComponentPath` + * and `absolutePath` as path can differ based on how Stencil is installed + * e.g. through `npm link` or `npm install` + */ + externalStyles + .map( + (es) => + cmpStyles.get(es.originalComponentPath) || cmpStyles.get(es.absolutePath), + ) + .join('\n') + : /** + * if `externalStyles` is not defined, then created the style text in the + * order of which the styles were compiled. + */ + [...cmpStyles.values()].join('\n') + : /** + * if `cmpStyles` is not defined, then use the style text from the transform + * as it is not connected to a component. + */ + cacheEntry.cssTransformOutput.styleText; + + // Only push to stylesUpdated if the CSS actually changed since the + // last build. Without this check, every rebuild re-pushes all 90+ + // component stylesheets even when only a .tsx file changed, causing + // the HMR client to re-inject every style on every save. + const scopeId = getScopeId(data.tag, data.mode); + const prevText = compilerCtx.prevStylesMap.get(scopeId); + const alreadyQueued = buildCtx.stylesUpdated.some( + (s) => s.styleTag === data.tag && s.styleMode === data.mode, + ); + if (!alreadyQueued && styleText !== prevText) { + compilerCtx.prevStylesMap.set(scopeId, styleText); + buildCtx.stylesUpdated.push({ + styleTag: data.tag, + styleMode: data.mode, + styleText, + }); + } + } + + return { + code: cacheEntry.cssTransformOutput.output, + map: cacheEntry.cssTransformOutput.map, + moduleSideEffects: false, + }; + } + + return null; + }, + }; +}; diff --git a/src/compiler/bundle/file-load-plugin.ts b/packages/core/src/compiler/bundle/file-load-plugin.ts similarity index 77% rename from src/compiler/bundle/file-load-plugin.ts rename to packages/core/src/compiler/bundle/file-load-plugin.ts index df79ee44f1c..692c483f17a 100644 --- a/src/compiler/bundle/file-load-plugin.ts +++ b/packages/core/src/compiler/bundle/file-load-plugin.ts @@ -1,6 +1,6 @@ -import { isDtsFile, normalizeFsPath } from '@utils'; -import type { Plugin } from 'rollup'; +import type { Plugin } from 'rolldown'; +import { isDtsFile, normalizeFsPath } from '../../utils'; import { InMemoryFileSystem } from '../sys/in-memory-fs'; export const fileLoadPlugin = (fs: InMemoryFileSystem): Plugin => { diff --git a/packages/core/src/compiler/bundle/loader-plugin.ts b/packages/core/src/compiler/bundle/loader-plugin.ts new file mode 100644 index 00000000000..5d46e0dd6ef --- /dev/null +++ b/packages/core/src/compiler/bundle/loader-plugin.ts @@ -0,0 +1,55 @@ +import type { LoadResult, Plugin, ResolveIdResult } from 'rolldown'; + +// Escape special regex characters in a string +const escapeRegex = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +/** + * Rolldown plugin that aids in resolving the entry points (1 or more files) for a Stencil project. For example, a project + * using the `standalone` output target may have a single 'entry point' for each file containing a component. + * Each of those files will be independently resolved and loaded by this plugin for further processing by Rolldown later + * in the bundling process. + * + * @param entries the Stencil project files to process. It should be noted that the keys in this object may not + * necessarily be an absolute or relative path to a file, but may be a Rolldown Virtual Module (which begin with \0). + * @returns the rolldown plugin that loads and process a Stencil project's entry points + */ +export const loaderPlugin = (entries: { [id: string]: string } = {}): Plugin => { + const entryKeys = Object.keys(entries); + + const entryFilter = + entryKeys.length > 0 ? new RegExp(`^(${entryKeys.map(escapeRegex).join('|')})$`) : /^$/; + + return { + name: 'stencilLoaderPlugin', + /** + * A rolldown build hook for resolving the imports of individual Stencil project files. This hook only resolves + * modules that are contained in the plugin's `entries` argument. [Source](https://rolldownjs.org/guide/en/#resolveid) + * @param id the importee to resolve + * @returns a string that resolves an import to some id, null otherwise + */ + resolveId: { + filter: { id: entryFilter }, + handler(id: string): ResolveIdResult { + if (id in entries) { + return { id }; + } + return null; + }, + }, + /** + * A rolldown build hook for loading individual Stencil project files [Source](https://rolldownjs.org/guide/en/#load) + * @param id the path of the module to load. It should be noted that the keys in this object may not necessarily + * be an absolute or relative path to a file, but may be a Rolldown Virtual Module. + * @returns the module matched, null otherwise + */ + load: { + filter: { id: entryFilter }, + handler(id: string): LoadResult { + if (id in entries) { + return entries[id]; + } + return null; + }, + }, + }; +}; diff --git a/packages/core/src/compiler/bundle/plugin-helper.ts b/packages/core/src/compiler/bundle/plugin-helper.ts new file mode 100644 index 00000000000..149f171dabb --- /dev/null +++ b/packages/core/src/compiler/bundle/plugin-helper.ts @@ -0,0 +1,84 @@ +import type * as d from '@stencil/core'; +import type { Plugin } from 'rolldown'; + +import { buildError, relative } from '../../utils'; +import type { BundlePlatform } from './bundle-interface'; + +const builtIns = new Set([ + 'child_process', + 'cluster', + 'dgram', + 'dns', + 'module', + 'net', + 'readline', + 'repl', + 'tls', + 'assert', + 'console', + 'constants', + 'domain', + 'events', + 'path', + 'punycode', + 'querystring', + '_stream_duplex', + '_stream_passthrough', + '_stream_readable', + '_stream_writable', + '_stream_transform', + 'string_decoder', + 'sys', + 'tty', + 'crypto', + 'fs', + 'Buffer', + 'buffer', + 'global', + 'http', + 'https', + 'os', + 'process', + 'stream', + 'timers', + 'url', + 'util', + 'vm', + 'zlib', +]); + +// Pre-build regex filter from builtIns set for Rolldown hook filtering +const BUILT_INS_FILTER = new RegExp(`^(${[...builtIns].join('|')})$`); + +export const pluginHelper = ( + config: d.ValidatedConfig, + builtCtx: d.BuildCtx, + platform: BundlePlatform, +): Plugin => { + return { + name: 'pluginHelper', + // Use Rolldown's hook filter to only process Node built-in imports + // This plugin only warns about missing polyfills, so filter aggressively + resolveId: { + filter: { id: BUILT_INS_FILTER }, + handler(importee: string, importer: string | undefined): null { + // Strip trailing slash if present + if (importee.endsWith('/')) { + importee = importee.slice(0, -1); + } + + if (builtIns.has(importee)) { + let fromMsg = ''; + if (importer) { + fromMsg = ` from ${relative(config.rootDir, importer)}`; + } + const diagnostic = buildError(builtCtx.diagnostics); + diagnostic.header = `Node Polyfills Required`; + diagnostic.messageText = `For the import "${importee}" to be bundled${fromMsg}, ensure the "rolldown-plugin-node-polyfills" plugin is installed and added to the stencil config plugins (${platform}). Please see the bundling docs for more information. + Further information: https://stenciljs.com/docs/module-bundling`; + } + return null; + }, + }, + }; +}; diff --git a/packages/core/src/compiler/bundle/server-plugin.ts b/packages/core/src/compiler/bundle/server-plugin.ts new file mode 100644 index 00000000000..968a0605713 --- /dev/null +++ b/packages/core/src/compiler/bundle/server-plugin.ts @@ -0,0 +1,86 @@ +import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; +import type { Plugin } from 'rolldown'; + +import { isOutputTargetSsr, isString, normalizeFsPath } from '../../utils'; +import type { BundlePlatform } from './bundle-interface'; + +// Escape special regex characters +const escapeRegex = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +export const serverPlugin = (config: d.ValidatedConfig, platform: BundlePlatform): Plugin => { + const isSsrBundle = platform === 'ssr'; + const serverVarid = `@removed-server-code`; + + const isServerOnlyModule = (id: string) => { + if (isString(id)) { + id = normalizeFsPath(id); + return id.includes('.server/') || id.endsWith('.server'); + } + return false; + }; + + const externals = isSsrBundle + ? config.outputTargets.filter(isOutputTargetSsr).flatMap((o) => o.external) + : []; + + // Build filter based on what this plugin handles: + // - @removed-server-code (virtual module) + // - .server paths (for client builds) + // - externals (for ssr builds) + const filterPatterns = [escapeRegex(serverVarid), '\\.server']; + if (externals.length > 0) { + filterPatterns.push(...externals.map(escapeRegex)); + } + const resolveFilter = new RegExp(`(${filterPatterns.join('|')})`); + + return { + name: 'serverPlugin', + + resolveId: { + filter: { id: resolveFilter }, + handler(id, importer) { + if (id === serverVarid) { + return id; + } + if (isSsrBundle) { + if (externals.includes(id)) { + // don't attempt to bundle node builtins for the ssr bundle + return { + id, + external: true, + }; + } + if (isServerOnlyModule(importer) && !id.startsWith('.') && !isAbsolute(id)) { + // do not bundle if the importer is a server-only module + // and the module it is importing is a node module + return { + id, + external: true, + }; + } + } else { + if (isServerOnlyModule(id)) { + // any path that has .server in it shouldn't actually + // be bundled in the web build, only the ssr build + return serverVarid; + } + } + return null; + }, + }, + + load: { + filter: { id: /^@removed-server-code$/ }, + handler(id) { + if (id === serverVarid) { + return { + code: 'export default {};', + syntheticNamedExports: true, + }; + } + return null; + }, + }, + }; +}; diff --git a/packages/core/src/compiler/bundle/typescript-plugin.ts b/packages/core/src/compiler/bundle/typescript-plugin.ts new file mode 100644 index 00000000000..79eadc1ddb3 --- /dev/null +++ b/packages/core/src/compiler/bundle/typescript-plugin.ts @@ -0,0 +1,106 @@ +import { basename, isAbsolute } from 'path'; +import ts from 'typescript'; +import type * as d from '@stencil/core'; +import type { LoadResult, Plugin, TransformResult } from 'rolldown'; + +import { normalizeFsPath } from '../../utils'; +import { getModule } from '../transpile/transpiled-module'; +import type { BundleOptions } from './bundle-interface'; + +/** + * Rolldown plugin that aids in resolving the TypeScript files and performing the transpilation step. + * @param compilerCtx the current compiler context + * @param bundleOpts Rolldown bundling options to apply during TypeScript compilation + * @param config the Stencil configuration for the project + * @returns the rolldown plugin for handling TypeScript files. + */ +export const typescriptPlugin = ( + compilerCtx: d.CompilerCtx, + bundleOpts: BundleOptions, + config: d.ValidatedConfig, +): Plugin => { + // Cache key prefix per bundle type so different transformer pipelines don't share entries. + const cachePrefix = bundleOpts.id + ':'; + let cacheHits = 0; + let cacheMisses = 0; + + return { + name: `${bundleOpts.id}TypescriptPlugin`, + + buildEnd() { + if (config.logLevel === 'debug' && cacheMisses > 0) { + config.logger.debug( + `${bundleOpts.id}TypescriptPlugin: ${cacheMisses} transforms computed` + + (cacheHits > 0 ? `, ${cacheHits} from cache` : ''), + ); + } + }, + + /** + * A rolldown build hook for loading TypeScript files and their associated source maps (if they exist). + * [Source](https://rolldownjs.org/guide/en/#load) + * @param id the path of the file to load + * @returns the module matched (with its sourcemap if it exists), null otherwise + */ + load(id: string): LoadResult { + if (isAbsolute(id)) { + const fsFilePath = normalizeFsPath(id); + const module = getModule(compilerCtx, fsFilePath); + + if (module) { + if (!module.sourceMapFileText) { + return { code: module.staticSourceFileText, map: null }; + } + + const sourceMap: d.SourceMap = JSON.parse(module.sourceMapFileText); + sourceMap.sources = sourceMap.sources.map((src) => basename(src)); + return { code: module.staticSourceFileText, map: sourceMap }; + } + } + return null; + }, + /** + * Performs TypeScript compilation/transpilation, including applying any transformations against the Abstract Syntax + * Tree (AST) specific to stencil + * @param _code the code to modify, unused + * @param id module's identifier + * @returns the transpiled code, with its associated sourcemap. null otherwise + */ + transform(_code: string, id: string): TransformResult { + if (isAbsolute(id)) { + const fsFilePath = normalizeFsPath(id); + const mod = getModule(compilerCtx, fsFilePath); + if (mod?.cmps) { + // Cross-build cache: survives rolldown teardown; evicted per changedModules in output-targets/index.ts. + const cacheKey = cachePrefix + fsFilePath; + const cached = compilerCtx.transpileCache.get(cacheKey); + if (cached) { + cacheHits++; + const sourceMap: d.SourceMap = cached.sourceMapText + ? JSON.parse(cached.sourceMapText) + : null; + return { code: cached.outputText, map: sourceMap }; + } + + cacheMisses++; + const tsResult = ts.transpileModule(mod.staticSourceFileText, { + compilerOptions: config.tsCompilerOptions, + fileName: mod.sourceFilePath, + transformers: { + before: bundleOpts.customBeforeTransformers ?? [], + }, + }); + compilerCtx.transpileCache.set(cacheKey, { + outputText: tsResult.outputText, + sourceMapText: tsResult.sourceMapText ?? null, + }); + const sourceMap: d.SourceMap = tsResult.sourceMapText + ? JSON.parse(tsResult.sourceMapText) + : null; + return { code: tsResult.outputText, map: sourceMap }; + } + } + return null; + }, + }; +}; diff --git a/packages/core/src/compiler/bundle/user-index-plugin.ts b/packages/core/src/compiler/bundle/user-index-plugin.ts new file mode 100644 index 00000000000..2a692d442fc --- /dev/null +++ b/packages/core/src/compiler/bundle/user-index-plugin.ts @@ -0,0 +1,37 @@ +import type * as d from '@stencil/core'; +import type { Plugin } from 'rolldown'; + +import { join } from '../../utils'; +import { USER_INDEX_ENTRY_ID } from './entry-alias-ids'; + +export const userIndexPlugin = (config: d.ValidatedConfig, compilerCtx: d.CompilerCtx): Plugin => { + return { + name: 'userIndexPlugin', + + // Use Rolldown's hook filter to only process @user-index-entrypoint + resolveId: { + filter: { id: /^@user-index-entrypoint$/ }, + async handler(importee) { + if (importee === USER_INDEX_ENTRY_ID) { + const usersIndexJsPath = join(config.srcDir, 'index.ts'); + const hasUserIndex = await compilerCtx.fs.access(usersIndexJsPath); + if (hasUserIndex) { + return usersIndexJsPath; + } + return importee; + } + return null; + }, + }, + + load: { + filter: { id: /^@user-index-entrypoint$/ }, + handler(id) { + if (id === USER_INDEX_ENTRY_ID) { + return `//! Autogenerated index`; + } + return null; + }, + }, + }; +}; diff --git a/packages/core/src/compiler/bundle/worker-plugin.ts b/packages/core/src/compiler/bundle/worker-plugin.ts new file mode 100644 index 00000000000..72bff9b9298 --- /dev/null +++ b/packages/core/src/compiler/bundle/worker-plugin.ts @@ -0,0 +1,497 @@ +import type * as d from '@stencil/core'; +import type { Plugin, PluginContext, TransformResult } from 'rolldown'; + +import { generatePreamble, hasError, normalizeFsPath } from '../../utils'; +import { optimizeModule } from '../optimize/optimize-module'; +import { bundleOutput } from './bundle-output'; +import { STENCIL_INTERNAL_ID } from './entry-alias-ids'; +import type { BundlePlatform } from './bundle-interface'; + +// Filter for worker-related file patterns +const WORKER_FILTER = /(\?worker(-inline)?|\.worker(\.tsx?|\/index\.tsx?))$/; + +export const workerPlugin = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + platform: BundlePlatform, + inlineWorkers: boolean, +): Plugin => { + if (platform === 'worker' || platform === 'ssr') { + return { + name: 'workerPlugin', + transform: { + filter: { id: /\?worker(-inline)?$/ }, + handler(_, id) { + if (id.endsWith('?worker') || id.endsWith('?worker-inline')) { + return getMockedWorkerMain(); + } + return null; + }, + }, + }; + } + + const workersMap = new Map(); + + return { + name: 'workerPlugin', + + buildStart() { + workersMap.clear(); + }, + + resolveId: { + filter: { id: /^@worker-helper$/ }, + handler(id) { + if (id === WORKER_HELPER_ID) { + return { + id, + moduleSideEffects: false, + }; + } + return null; + }, + }, + + load: { + filter: { id: /^@worker-helper$/ }, + handler(id) { + if (id === WORKER_HELPER_ID) { + return WORKER_HELPERS; + } + return null; + }, + }, + + transform: { + filter: { id: WORKER_FILTER }, + async handler(_, id): Promise { + if (/\0/.test(id)) { + return null; + } + + // Canonical worker path + if (id.endsWith('?worker')) { + const workerEntryPath = normalizeFsPath(id); + const workerName = getWorkerName(workerEntryPath); + const { code, dependencies, workerMsgId } = await getWorker( + config, + compilerCtx, + buildCtx, + this, + workersMap, + workerEntryPath, + ); + const referenceId = this.emitFile({ + type: 'asset', + source: code, + name: workerName + '.js', + }); + dependencies.forEach((dep) => this.addWatchFile(dep)); + return { + code: getWorkerMain(referenceId, workerName, workerMsgId), + moduleSideEffects: false, + }; + } else if (id.endsWith('?worker-inline')) { + const workerEntryPath = normalizeFsPath(id); + const workerName = getWorkerName(workerEntryPath); + const { code, dependencies, workerMsgId } = await getWorker( + config, + compilerCtx, + buildCtx, + this, + workersMap, + workerEntryPath, + ); + const referenceId = this.emitFile({ + type: 'asset', + source: code, + name: workerName + '.js', + }); + dependencies.forEach((dep) => this.addWatchFile(dep)); + return { + code: getInlineWorker(referenceId, workerName, workerMsgId), + moduleSideEffects: false, + }; + } + + // Proxy worker path + const workerEntryPath = getWorkerEntryPath(id); + if (workerEntryPath != null) { + const worker = await getWorker( + config, + compilerCtx, + buildCtx, + this, + workersMap, + workerEntryPath, + ); + if (worker) { + if (inlineWorkers) { + return { + code: getInlineWorkerProxy(workerEntryPath, worker.workerMsgId, worker.exports), + moduleSideEffects: false, + }; + } else { + return { + code: getWorkerProxy(workerEntryPath, worker.exports), + moduleSideEffects: false, + }; + } + } + } + return null; + }, + }, + }; +}; + +const getWorkerEntryPath = (id: string) => { + if (WORKER_SUFFIX.some((p) => id.endsWith(p))) { + return normalizeFsPath(id); + } + return null; +}; + +interface WorkerMeta { + code: string; + workerMsgId: string; + exports: string[]; + dependencies: string[]; +} + +const getWorker = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + ctx: PluginContext, + workersMap: Map, + workerEntryPath: string, +): Promise => { + let worker = workersMap.get(workerEntryPath); + if (!worker) { + worker = await buildWorker(config, compilerCtx, buildCtx, ctx, workerEntryPath); + workersMap.set(workerEntryPath, worker); + } + return worker; +}; + +const getWorkerName = (id: string) => { + const parts = id.split('/').filter((i) => !i.includes('index')); + id = parts[parts.length - 1]; + return id.replace('.tsx', '').replace('.ts', ''); +}; + +const buildWorker = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + ctx: PluginContext, + workerEntryPath: string, +) => { + const workerName = getWorkerName(workerEntryPath); + const workerMsgId = `stencil.${workerName}`; + const build = await bundleOutput(config, compilerCtx, buildCtx, { + platform: 'worker', + id: workerName, + inputs: { + [workerName]: workerEntryPath, + }, + codeSplitting: false, + }); + + if (build) { + // Generate commonjs output so we can intercept exports at runtime + const output = await build.generate({ + format: 'commonjs', + banner: `${generatePreamble(config)}\n(()=>{\n`, + footer: '})();', + intro: getWorkerIntro(workerMsgId, config.devMode), + esModule: false, + externalLiveBindings: false, + }); + const entryPoint = output.output[0]; + if (entryPoint.imports.length > 0) { + ctx.error( + 'Workers should not have any external imports: ' + JSON.stringify(entryPoint.imports), + ); + } + + // Optimize code + let code = entryPoint.code; + const results = await optimizeModule(config, compilerCtx, { + input: code, + sourceTarget: 'es2017', + isCore: false, + minify: config.minifyJs, + inlineHelpers: true, + }); + buildCtx.diagnostics.push(...results.diagnostics); + if (!hasError(results.diagnostics)) { + code = results.output; + } + + return { + code, + exports: entryPoint.exports, + workerMsgId, + dependencies: Object.keys(entryPoint.modules).filter( + (id) => !/\0/.test(id) && id !== workerEntryPath, + ), + }; + } + return null; +}; + +const WORKER_SUFFIX = ['.worker.ts', '.worker.tsx', '.worker/index.ts', '.worker/index.tsx']; + +const WORKER_HELPER_ID = '@worker-helper'; + +const GET_TRANSFERABLES = ` +const isInstanceOf = (value, className) => { + const C = globalThis[className]; + return C != null && value instanceof C; +} +const getTransferables = (value) => { + if (value != null) { + if ( + isInstanceOf(value, "ArrayBuffer") || + isInstanceOf(value, "MessagePort") || + isInstanceOf(value, "ImageBitmap") || + isInstanceOf(value, "OffscreenCanvas") + ) { + return [value]; + } + if (typeof value === "object") { + if (value.constructor === Object) { + value = Object.values(value); + } + if (Array.isArray(value)) { + return value.flatMap(getTransferables); + } + return getTransferables(value.buffer); + } + } + return []; +};`; +const getWorkerIntro = (workerMsgId: string, isDev: boolean) => ` +${GET_TRANSFERABLES} +const exports = {}; +const workerMsgId = '${workerMsgId}'; +const workerMsgCallbackId = workerMsgId + '.cb'; +addEventListener('message', async ({data}) => { + if (data && data[0] === workerMsgId) { + let id = data[1]; + let method = data[2]; + let args = data[3]; + let i = 0; + let argsLen = args.length; + let value; + let err; + + try { + for (; i < argsLen; i++) { + if (Array.isArray(args[i]) && args[i][0] === workerMsgCallbackId) { + const callbackId = args[i][1]; + args[i] = (...cbArgs) => { + postMessage( + [workerMsgCallbackId, callbackId, cbArgs] + ); + }; + } + } + ${ + isDev + ? ` + value = exports[method](...args); + if (!value || !value.then) { + throw new Error('The exported method "' + method + '" does not return a Promise, make sure it is an "async" function'); + } + value = await value; + ` + : ` + value = await exports[method](...args);` + } + + } catch (e) { + value = null; + if (e instanceof Error) { + err = { + isError: true, + value: { + message: e.message, + name: e.name, + stack: e.stack, + } + }; + } else { + err = { + isError: false, + value: e + }; + } + value = undefined; + } + + const transferables = getTransferables(value); + ${isDev ? `if (transferables.length > 0) console.debug('Transfering', transferables);` : ''} + + postMessage( + [workerMsgId, id, value, err], + transferables + ); + } +}); +`; + +const WORKER_HELPERS = ` +import { consoleError } from '${STENCIL_INTERNAL_ID}'; + +${GET_TRANSFERABLES} + +let pendingIds = 0; +let callbackIds = 0; +const pending = new Map(); +const callbacks = new Map(); + +export const createWorker = (workerPath, workerName, workerMsgId) => { + const worker = new Worker(workerPath, {name:workerName}); + + worker.addEventListener('message', ({data}) => { + if (data) { + const workerMsg = data[0]; + const id = data[1]; + const value = data[2]; + + if (workerMsg === workerMsgId) { + const err = data[3]; + const [resolve, reject, callbackIds] = pending.get(id); + pending.delete(id); + + if (err) { + const errObj = (err.isError) + ? Object.assign(new Error(err.value.message), err.value) + : err.value; + + consoleError(errObj); + reject(errObj); + } else { + if (callbackIds) { + callbackIds.forEach(id => callbacks.delete(id)); + } + resolve(value); + } + } else if (workerMsg === workerMsgId + '.cb') { + try { + callbacks.get(id)(...value); + } catch (e) { + consoleError(e); + } + } + } + }); + + return worker; +}; + +export const createWorkerProxy = (worker, workerMsgId, exportedMethod) => ( + (...args) => new Promise((resolve, reject) => { + let pendingId = pendingIds++; + let i = 0; + let argLen = args.length; + let mainData = [resolve, reject]; + pending.set(pendingId, mainData); + + for (; i < argLen; i++) { + if (typeof args[i] === 'function') { + const callbackId = callbackIds++; + callbacks.set(callbackId, args[i]); + args[i] = [workerMsgId + '.cb', callbackId]; + (mainData[2] = mainData[2] || []).push(callbackId); + } + } + const postMessage = (w) => ( + w.postMessage( + [workerMsgId, pendingId, exportedMethod, args], + getTransferables(args) + ) + ); + if (worker.then) { + worker.then(postMessage); + } else { + postMessage(worker); + } + }) +); +`; + +const getWorkerMain = (referenceId: string, workerName: string, workerMsgId: string) => { + return ` +import { createWorker } from '${WORKER_HELPER_ID}'; +export const workerName = '${workerName}'; +export const workerMsgId = '${workerMsgId}'; +export const workerPath = /*@__PURE__*/import.meta.ROLLDOWN_FILE_URL_${referenceId}; +export const worker = /*@__PURE__*/createWorker(workerPath, workerName, workerMsgId); +`; +}; + +const getInlineWorker = (referenceId: string, workerName: string, workerMsgId: string) => { + return ` +import { createWorker } from '${WORKER_HELPER_ID}'; +export const workerName = '${workerName}'; +export const workerMsgId = '${workerMsgId}'; +export const workerPath = /*@__PURE__*/import.meta.ROLLDOWN_FILE_URL_${referenceId}; +export let worker; +try { + // first try directly starting the worker with the URL + worker = /*@__PURE__*/createWorker(workerPath, workerName, workerMsgId); +} catch(e) { + // probably a cross-origin issue, try using a Blob instead + const blob = new Blob(['importScripts("' + workerPath + '")'], { type: 'text/javascript' }); + const url = URL.createObjectURL(blob); + worker = /*@__PURE__*/createWorker(url, workerName, workerMsgId); + URL.revokeObjectURL(url); +} +`; +}; + +const getMockedWorkerMain = () => { + // for the ssr build the workers won't actually work + // however, we still need to make the {worker} export + // kick-in otherwise bundling chokes + return ` +export const workerName = 'mocked-worker'; +export const workerMsgId = workerName; +export const workerPath = workerName; +export const worker = { name: workerName }; +`; +}; + +const getWorkerProxy = (workerEntryPath: string, exportedMethods: string[]) => { + return ` +import { createWorkerProxy } from '${WORKER_HELPER_ID}'; +import { worker, workerName, workerMsgId } from '${workerEntryPath}?worker'; +${exportedMethods + .map((exportedMethod) => { + return `export const ${exportedMethod} = /*@__PURE__*/createWorkerProxy(worker, workerMsgId, '${exportedMethod}');`; + }) + .join('\n')} +`; +}; + +const getInlineWorkerProxy = ( + workerEntryPath: string, + workerMsgId: string, + exportedMethods: string[], +) => { + return ` +import { createWorkerProxy } from '${WORKER_HELPER_ID}'; +const workerPromise = import('${workerEntryPath}?worker-inline').then(m => m.worker); +${exportedMethods + .map((exportedMethod) => { + return `export const ${exportedMethod} = /*@__PURE__*/createWorkerProxy(workerPromise, '${workerMsgId}', '${exportedMethod}');`; + }) + .join('\n')} +`; +}; diff --git a/src/compiler/cache.ts b/packages/core/src/compiler/cache.ts similarity index 92% rename from src/compiler/cache.ts rename to packages/core/src/compiler/cache.ts index a095d2dad21..d750870c000 100644 --- a/src/compiler/cache.ts +++ b/packages/core/src/compiler/cache.ts @@ -1,6 +1,6 @@ -import { join } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../declarations'; +import { join } from '../utils'; import { InMemoryFileSystem } from './sys/in-memory-fs'; export class Cache implements d.Cache { @@ -50,7 +50,9 @@ export class Cache implements d.Cache { if (this.failed >= MAX_FAILED) { if (!this.skip) { this.skip = true; - this.logger.debug(`cache had ${this.failed} failed ops, skip disk ops for remainder of build`); + this.logger.debug( + `cache had ${this.failed} failed ops, skip disk ops for remainder of build`, + ); } return null; } @@ -60,7 +62,7 @@ export class Cache implements d.Cache { result = await this.cacheFs.readFile(this.getCacheFilePath(key)); this.failed = 0; this.skip = false; - } catch (e: unknown) { + } catch { this.failed++; result = null; } @@ -76,7 +78,7 @@ export class Cache implements d.Cache { try { await this.cacheFs.writeFile(this.getCacheFilePath(key), value); return true; - } catch (e: unknown) { + } catch { this.failed++; return false; } @@ -143,7 +145,9 @@ export class Cache implements d.Cache { await Promise.all(promises); - this.logger.debug(`clearExpiredCache, cachedFileNames: ${cachedFileNames.length}, totalCleared: ${totalCleared}`); + this.logger.debug( + `clearExpiredCache, cachedFileNames: ${cachedFileNames.length}, totalCleared: ${totalCleared}`, + ); } this.logger.debug(`clearExpiredCache, set last clear`); diff --git a/packages/core/src/compiler/compiler.ts b/packages/core/src/compiler/compiler.ts new file mode 100644 index 00000000000..10607b3393c --- /dev/null +++ b/packages/core/src/compiler/compiler.ts @@ -0,0 +1,65 @@ +import ts from 'typescript'; +import type { Compiler, Config, Diagnostic, ValidatedConfig } from '@stencil/core'; + +import { isFunction } from '../utils'; +import { CompilerContext } from './build/compiler-ctx'; +import { createFullBuild } from './build/full-build'; +import { createWatchBuild } from './build/watch-build'; +import { Cache } from './cache'; +import { getConfig } from './sys/config'; +import { createInMemoryFs } from './sys/in-memory-fs'; +import { resolveModuleIdAsync } from './sys/resolve/resolve-module-async'; +import { patchTypescript } from './sys/typescript/typescript-sys'; +import { createSysWorker } from './sys/worker/sys-worker'; + +/** + * Generate a Stencil compiler instance + * @param userConfig a user-provided Stencil configuration to apply to the compiler instance + * @returns a new instance of a Stencil compiler + * @public + */ +export const createCompiler = async (userConfig: Config): Promise => { + // actual compiler code + const config: ValidatedConfig = getConfig(userConfig); + const diagnostics: Diagnostic[] = []; + const sys = config.sys; + const compilerCtx = new CompilerContext(); + + if (isFunction(config.sys.setupCompiler)) { + config.sys.setupCompiler({ ts }); + } + + compilerCtx.fs = createInMemoryFs(sys); + compilerCtx.cache = new Cache(config, createInMemoryFs(sys)); + await compilerCtx.cache.initCacheDir(); + + sys.resolveModuleId = (opts) => resolveModuleIdAsync(sys, compilerCtx.fs, opts); + compilerCtx.worker = createSysWorker(config); + + if (sys.events) { + // Pipe events from sys.events to compilerCtx + sys.events.on(compilerCtx.events.emit); + } + patchTypescript(config, compilerCtx.fs); + + const build = () => createFullBuild(config, compilerCtx); + + const createWatcher = () => createWatchBuild(config, compilerCtx); + + const destroy = async () => { + compilerCtx.reset(); + compilerCtx.events.unsubscribeAll(); + await sys.destroy(); + }; + + const compiler: Compiler = { + build, + createWatcher, + destroy, + sys, + }; + + config.logger.printDiagnostics(diagnostics); + + return compiler; +}; diff --git a/packages/core/src/compiler/config/_test_/fixtures/stencil.config.ts b/packages/core/src/compiler/config/_test_/fixtures/stencil.config.ts new file mode 100644 index 00000000000..24dafd6750d --- /dev/null +++ b/packages/core/src/compiler/config/_test_/fixtures/stencil.config.ts @@ -0,0 +1,5 @@ +import { Config } from '@stencil/core'; + +export const config: Config = { + hashedFileNameLength: 13, +}; diff --git a/packages/core/src/compiler/config/_test_/fixtures/stencil.config2.ts b/packages/core/src/compiler/config/_test_/fixtures/stencil.config2.ts new file mode 100644 index 00000000000..464a8d8ab12 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/fixtures/stencil.config2.ts @@ -0,0 +1,8 @@ +import { Config } from '@stencil/core'; + +export const config: Config = { + hashedFileNameLength: 27, + extras: { + enableImportInjection: true, + }, +}; diff --git a/src/compiler/config/test/load-config.spec.ts b/packages/core/src/compiler/config/_test_/load-config.spec.ts similarity index 78% rename from src/compiler/config/test/load-config.spec.ts rename to packages/core/src/compiler/config/_test_/load-config.spec.ts index 9fd5bb6c2be..e1a4ac9f1eb 100644 --- a/src/compiler/config/test/load-config.spec.ts +++ b/packages/core/src/compiler/config/_test_/load-config.spec.ts @@ -1,33 +1,37 @@ -import { mockCompilerSystem } from '@stencil/core/testing'; -import path from 'path'; -import ts from 'typescript'; +import { resolve, dirname } from 'node:path'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type * as d from '@stencil/core'; -import { ConfigFlags } from '../../../cli/config-flags'; -import type * as d from '../../../declarations'; +import { mockCompilerSystem } from '../../../testing'; import { normalizePath } from '../../../utils'; import { loadConfig } from '../load-config'; +vi.mock('typescript', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + default: { + ...actual, + getParsedCommandLineOfConfigFile: vi.fn().mockReturnValue({ + options: { + target: actual.ScriptTarget.ES2017, + module: actual.ModuleKind.ESNext, + }, + fileNames: [], + errors: [], + }), + }, + }; +}); + describe('load config', () => { - const configPath = require.resolve('./fixtures/stencil.config.ts'); - const configPath2 = require.resolve('./fixtures/stencil.config2.ts'); + const configPath = resolve(import.meta.dirname, 'fixtures/stencil.config.ts'); + const configPath2 = resolve(import.meta.dirname, 'fixtures/stencil.config2.ts'); let sys: d.CompilerSystem; beforeEach(() => { sys = mockCompilerSystem(); - - jest.spyOn(ts, 'getParsedCommandLineOfConfigFile').mockReturnValue({ - options: { - target: ts.ScriptTarget.ES2017, - module: ts.ModuleKind.ESNext, - }, - fileNames: [], - errors: [], - }); - }); - - afterEach(() => { - jest.clearAllMocks(); }); it("merges a user's configuration with a stencil.config file on disk", async () => { @@ -47,8 +51,6 @@ describe('load config', () => { // this field is defined on the `init` argument, and should override the value found in the config on disk expect(actualConfig).toBeDefined(); expect(actualConfig.hashedFileNameLength).toEqual(9); - // these fields are defined in the config file on disk, and should be present - expect(actualConfig.flags).toEqual({ dev: true }); expect(actualConfig.extras).toBeDefined(); expect(actualConfig.extras!.enableImportInjection).toBe(true); // respects custom root dir @@ -70,8 +72,6 @@ describe('load config', () => { expect(actualConfig.configPath).toBe(normalizePath(configPath)); // this field is defined in the config file on disk, and should be present expect(actualConfig.hashedFileNameLength).toBe(13); - // this field should default to an empty object literal, since it wasn't present in the config file - expect(actualConfig.flags).toEqual({}); }); describe('empty initialization argument', () => { @@ -86,7 +86,7 @@ describe('load config', () => { }); it('creates a tsconfig file when "initTsConfig" set', async () => { - const tsconfigPath = path.resolve(path.dirname(configPath), 'tsconfig.json'); + const tsconfigPath = resolve(dirname(configPath), 'tsconfig.json'); expect(sys.accessSync(tsconfigPath)).toBe(false); const loadedConfig = await loadConfig({ initTsConfig: true, configPath, sys }); expect(sys.accessSync(tsconfigPath)).toBe(true); @@ -102,7 +102,7 @@ describe('load config', () => { level: 'error', lines: [], messageText: `Unable to load TypeScript config file. Please create a "tsconfig.json" file within the "${normalizePath( - path.dirname(configPath), + dirname(configPath), )}" directory.`, relFilePath: undefined, type: 'build', diff --git a/src/compiler/config/test/validate-config-sourcemap.spec.ts b/packages/core/src/compiler/config/_test_/validate-config-sourcemap.spec.ts similarity index 89% rename from src/compiler/config/test/validate-config-sourcemap.spec.ts rename to packages/core/src/compiler/config/_test_/validate-config-sourcemap.spec.ts index d82b4b47026..e35758af9e5 100644 --- a/src/compiler/config/test/validate-config-sourcemap.spec.ts +++ b/packages/core/src/compiler/config/_test_/validate-config-sourcemap.spec.ts @@ -1,12 +1,25 @@ -import { mockCompilerSystem, mockLoadConfigInit } from '@stencil/core/testing'; +import { resolve } from 'node:path'; import ts from 'typescript'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type * as d from '@stencil/core'; -import type * as d from '../../../declarations'; +import { mockCompilerSystem, mockLoadConfigInit } from '../../../testing'; import { loadConfig } from '../load-config'; import { validateConfig } from '../validate-config'; +vi.mock('typescript', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + default: { + ...actual, + getParsedCommandLineOfConfigFile: vi.fn(), + }, + }; +}); + describe('stencil config - sourceMap option', () => { - const configPath = require.resolve('./fixtures/stencil.config.ts'); + const configPath = resolve(import.meta.dirname, 'fixtures/stencil.config.ts'); let sys = mockCompilerSystem(); /** @@ -34,7 +47,7 @@ describe('stencil config - sourceMap option', () => { * @param sourceMap The `sourceMap` option from the Stencil config. */ const mockTsConfigParser = (sourceMap: boolean) => { - jest.spyOn(ts, 'getParsedCommandLineOfConfigFile').mockReturnValue({ + vi.mocked(ts.getParsedCommandLineOfConfigFile).mockReturnValue({ options: { target: ts.ScriptTarget.ES2017, module: ts.ModuleKind.ESNext, @@ -48,10 +61,7 @@ describe('stencil config - sourceMap option', () => { beforeEach(() => { sys = mockCompilerSystem(); - }); - - afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('sets sourceMap to true when explicitly set to true', async () => { diff --git a/packages/core/src/compiler/config/_test_/validate-config.spec.ts b/packages/core/src/compiler/config/_test_/validate-config.spec.ts new file mode 100644 index 00000000000..0292a979186 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-config.spec.ts @@ -0,0 +1,518 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockCompilerSystem, mockLoadConfigInit, mockLogger } from '../../../testing'; +import { DOCS_CUSTOM, DOCS_JSON, DOCS_README, DOCS_VSCODE } from '../../../utils'; +import { isWatchIgnorePath } from '../../fs-watch/fs-watch-rebuild'; +import { validateConfig } from '../validate-config'; + +describe('validation', () => { + let userConfig: d.UnvalidatedConfig; + let bootstrapConfig: d.LoadConfigInit; + const logger = mockLogger(); + const sys = mockCompilerSystem(); + + beforeEach(() => { + userConfig = { + sys: sys, + logger: logger, + rootDir: '/User/some/path/', + namespace: 'Testing', + }; + bootstrapConfig = mockLoadConfigInit(); + }); + + describe('caching', () => { + it('should cache the validated config between calls if the same config is passed back in', () => { + const { config } = validateConfig(userConfig, {}); + const { config: secondRound } = validateConfig(config, {}); + // we should have object identity + expect(config === secondRound).toBe(true); + // objects should be deepEqual as well + expect(config).toEqual(secondRound); + }); + + it('should bust the cache if a different config is supplied than the cached one', () => { + // validate once, caching that result + const { config } = validateConfig(userConfig, {}); + // pass a new initial configuration + const { config: secondRound } = validateConfig({ ...userConfig }, {}); + // shouldn't have object equality with the earlier one + expect(config === secondRound).toBe(false); + }); + }); + + describe('devMode validation', () => { + it('defaults devMode to false (production) when not set by CLI', () => { + // devMode is not user-settable in stencil.config.ts; it is injected by + // the CLI via --dev flag. When absent the validator defaults to false. + const { config } = validateConfig({}, bootstrapConfig); + expect(config.devMode).toBe(false); + }); + + it('accepts devMode: true when injected by CLI (--dev flag path)', () => { + // Simulates the CLI having set devMode: true before calling validateConfig + const { config } = validateConfig({ devMode: true }, bootstrapConfig); + expect(config.devMode).toBe(true); + }); + + it('falls back to false when a non-boolean devMode value arrives (defensive)', () => { + // Should never happen in practice, but the validator guards against it + const devMode = 'not-a-bool' as unknown as boolean; + const { config } = validateConfig({ devMode }, bootstrapConfig); + expect(config.devMode).toBe(false); + }); + }); + + describe('allowInlineScripts', () => { + it('set allowInlineScripts true', () => { + userConfig.allowInlineScripts = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.allowInlineScripts).toBe(true); + }); + + it('set allowInlineScripts false', () => { + userConfig.allowInlineScripts = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.allowInlineScripts).toBe(false); + }); + + it('default allowInlineScripts true', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.allowInlineScripts).toBe(true); + }); + }); + + describe('transformAliasedImportPaths', () => { + it.each([true, false])('set transformAliasedImportPaths %p', (bool) => { + userConfig.transformAliasedImportPaths = bool; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.transformAliasedImportPaths).toBe(bool); + }); + + it('defaults `transformAliasedImportPaths` to true', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.transformAliasedImportPaths).toBe(true); + }); + }); + + describe('suppressReservedPublicNameWarnings', () => { + it.each([true, false])( + 'sets suppressReservedPublicNameWarnings to %p when provided', + (bool) => { + userConfig.suppressReservedPublicNameWarnings = bool; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.suppressReservedPublicNameWarnings).toBe(bool); + }, + ); + + it('defaults suppressReservedPublicNameWarnings to false', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.suppressReservedPublicNameWarnings).toBe(false); + }); + }); + + describe('enableCache', () => { + it('set enableCache true', () => { + userConfig.enableCache = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.enableCache).toBe(true); + }); + + it('set enableCache false', () => { + userConfig.enableCache = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.enableCache).toBe(false); + }); + + it('default enableCache true', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.enableCache).toBe(true); + }); + }); + + describe('buildAppCore', () => { + it('set buildAppCore true', () => { + userConfig.buildAppCore = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.buildAppCore).toBe(true); + }); + + it('set buildAppCore false', () => { + userConfig.buildAppCore = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.buildAppCore).toBe(false); + }); + + it('default buildAppCore true', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.buildAppCore).toBe(true); + }); + }); + + describe('hashed filenames', () => { + it('should error when hashedFileNameLength too large', () => { + userConfig.hashedFileNameLength = 33; + const validated = validateConfig(userConfig, bootstrapConfig); + expect(validated.diagnostics).toHaveLength(1); + }); + + it('should error when hashedFileNameLength too small', () => { + userConfig.hashedFileNameLength = 3; + const validated = validateConfig(userConfig, bootstrapConfig); + expect(validated.diagnostics).toHaveLength(1); + }); + + it('should set from hashedFileNameLength', () => { + userConfig.hashedFileNameLength = 28; + const validated = validateConfig(userConfig, bootstrapConfig); + expect(validated.config.hashedFileNameLength).toBe(28); + }); + + it('should set hashedFileNameLength', () => { + userConfig.hashedFileNameLength = 6; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashedFileNameLength).toBe(6); + }); + + it('should default hashedFileNameLength', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashedFileNameLength).toBe(8); + }); + + it('should default hashFileNames to false in watch mode despite prod mode', () => { + userConfig.watch = true; + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashFileNames).toBe(true); + }); + + it('should default hashFileNames to true in prod mode', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashFileNames).toBe(true); + }); + + it('should default hashFileNames to false in dev mode', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashFileNames).toBe(false); + }); + + it.each([true, false])('should set hashFileNames when hashFileNames===%b', (hashFileNames) => { + userConfig.hashFileNames = hashFileNames; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashFileNames).toBe(hashFileNames); + }); + + it('should set hashFileNames from function', () => { + (userConfig as any).hashFileNames = () => { + return true; + }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.hashFileNames).toBe(true); + }); + }); + + describe('minifyJs', () => { + it('should set minifyJs to true', () => { + userConfig.devMode = true; + userConfig.minifyJs = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.minifyJs).toBe(true); + }); + + it('should default minifyJs to true in prod mode', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.minifyJs).toBe(true); + }); + + it('should default minifyJs to false in dev mode', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.minifyJs).toBe(false); + }); + }); + + describe('minifyCss', () => { + it('should set minifyCss to true', () => { + userConfig.devMode = true; + userConfig.minifyCss = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.minifyCss).toBe(true); + }); + + it('should default minifyCss to true in prod mode', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.minifyCss).toBe(true); + }); + + it('should default minifyCss to false in dev mode', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.minifyCss).toBe(false); + }); + }); + + it('should default watch to false', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.watch).toBe(false); + }); + + it('should pass through devMode: false (CLI did not pass --dev)', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devMode).toBe(false); + }); + + it('should pass through devMode: true (CLI passed --dev)', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devMode).toBe(true); + }); + + it('should default devMode to false when not set', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devMode).toBe(false); + }); + + it.each([DOCS_JSON, DOCS_CUSTOM, DOCS_VSCODE])( + 'should not add "%s" output target by default', + (targetType) => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.outputTargets.some((o) => o.type === targetType)).toBe(false); + }, + ); + + it('should add "docs-readme" output target by default in production mode', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.outputTargets.some((o) => o.type === DOCS_README)).toBe(true); + }); + + it('should not add "docs-readme" output target in dev mode', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.outputTargets.some((o) => o.type === DOCS_README)).toBe(false); + }); + + it('should set devInspector false', () => { + userConfig.devInspector = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devInspector).toBe(false); + }); + + it('should set devInspector true', () => { + userConfig.devInspector = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devInspector).toBe(true); + }); + + it('should default devInspector false when devMode is false', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devInspector).toBe(false); + }); + + it('should default devInspector true when devMode is true', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.devInspector).toBe(true); + }); + + it('should default loader-bundle false and www true', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.outputTargets.some((o) => o.type === 'loader-bundle')).toBe(false); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should error for invalid outputTarget type', () => { + userConfig.outputTargets = [ + { + type: 'whatever', + } as any, + ]; + const validated = validateConfig(userConfig, bootstrapConfig); + expect(validated.diagnostics).toHaveLength(1); + }); + + it('should default outputTargets with www', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should set extras defaults', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.appendChildSlotFix).toBe(false); + expect(config.extras.cloneNodeFix).toBe(false); + expect(config.extras.lifecycleDOMEvents).toBe(false); + expect(config.extras.slotChildNodesFix).toBe(false); + expect(config.extras.initializeNextTick).toBe(false); + expect(config.extras.additionalTagTransformers).toBe(false); + expect(config.extras.scopedSlotTextContentFix).toBe(false); + }); + + describe('extras.additionalTagTransformers', () => { + it('set extras.additionalTagTransformers false', () => { + userConfig.extras = { additionalTagTransformers: false }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(false); + }); + + it('set extras.additionalTagTransformers true', () => { + userConfig.extras = { additionalTagTransformers: true }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(true); + }); + + it('set extras.additionalTagTransformers true, dev mode', () => { + userConfig.devMode = true; + userConfig.extras = { additionalTagTransformers: true }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(true); + }); + + it('prod mode, set extras.additionalTagTransformers', () => { + userConfig.devMode = false; + userConfig.extras = { additionalTagTransformers: true }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(true); + }); + + it('build extras.additionalTagTransformers when set to "prod" and in prod', () => { + userConfig.devMode = false; + userConfig.extras = { additionalTagTransformers: 'prod' }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(true); + }); + + it('do not build extras.additionalTagTransformers when set to "prod" and in dev', () => { + userConfig.devMode = true; + userConfig.extras = { additionalTagTransformers: 'prod' }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(false); + }); + + it('prod mode default to only modern and not extras.additionalTagTransformers', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.additionalTagTransformers).toBe(false); + }); + }); + + it('should set slot config based on `experimentalSlotFixes`', () => { + userConfig.extras = {}; + userConfig.extras.experimentalSlotFixes = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.appendChildSlotFix).toBe(true); + expect(config.extras.cloneNodeFix).toBe(true); + expect(config.extras.slotChildNodesFix).toBe(true); + expect(config.extras.scopedSlotTextContentFix).toBe(true); + }); + + it('should override slot fix config based on `experimentalSlotFixes`', () => { + // This test is to verify the flags get overwritten correctly even if an + // invalid config is ingested. Hence, the `any` cast + userConfig.extras = { + appendChildSlotFix: false, + slotChildNodesFix: false, + cloneNodeFix: false, + scopedSlotTextContentFix: false, + experimentalSlotFixes: true, + } as any; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.appendChildSlotFix).toBe(true); + expect(config.extras.cloneNodeFix).toBe(true); + expect(config.extras.slotChildNodesFix).toBe(true); + expect(config.extras.scopedSlotTextContentFix).toBe(true); + }); + + it('should set extras experimentalScopedSlotChanges `true` if set in user config', () => { + userConfig.extras = { + experimentalScopedSlotChanges: true, + }; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.extras.experimentalScopedSlotChanges).toBe(true); + }); + + it('should set taskQueue "async" by default', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.taskQueue).toBe('async'); + }); + + it('should set taskQueue', () => { + userConfig.taskQueue = 'congestionAsync'; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.taskQueue).toBe('congestionAsync'); + }); + + it('empty watchIgnoredRegex, all valid', () => { + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.watchIgnoredRegex).toEqual([]); + expect(isWatchIgnorePath(config, '/some/image.gif')).toBe(false); + expect(isWatchIgnorePath(config, '/some/typescript.ts')).toBe(false); + }); + + it('should change a single watchIgnoredRegex to an array', () => { + userConfig.watchIgnoredRegex = /\.(gif|jpe?g|png)$/i; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.watchIgnoredRegex).toHaveLength(1); + expect((config.watchIgnoredRegex as any[])[0]).toEqual(/\.(gif|jpe?g|png)$/i); + expect(isWatchIgnorePath(config, '/some/image.gif')).toBe(true); + expect(isWatchIgnorePath(config, '/some/typescript.ts')).toBe(false); + }); + + it('should clean up valid watchIgnoredRegex', () => { + userConfig.watchIgnoredRegex = [/\.(gif|jpe?g)$/i, null, 'me-regex' as any, /\.(png)$/i]; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.watchIgnoredRegex).toHaveLength(2); + expect((config.watchIgnoredRegex as any[])[0]).toEqual(/\.(gif|jpe?g)$/i); + expect((config.watchIgnoredRegex as any[])[1]).toEqual(/\.(png)$/i); + expect(isWatchIgnorePath(config, '/some/image.gif')).toBe(true); + expect(isWatchIgnorePath(config, '/some/image.jpg')).toBe(true); + expect(isWatchIgnorePath(config, '/some/image.png')).toBe(true); + expect(isWatchIgnorePath(config, '/some/typescript.ts')).toBe(false); + }); + + describe('sourceMap', () => { + it('sets the field to true when the set to true in the config', () => { + userConfig.sourceMap = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.sourceMap).toBe(true); + }); + + it('sets the field to false when set to false in the config', () => { + userConfig.sourceMap = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.sourceMap).toBe(false); + }); + + it('defaults to "dev" behavior when not set (true in dev mode)', () => { + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.sourceMap).toBe(true); + }); + + it('defaults to "dev" behavior when not set (false in prod mode)', () => { + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.sourceMap).toBe(false); + }); + + it('sets the field to true when set to "dev" and devMode is true', () => { + userConfig.sourceMap = 'dev'; + userConfig.devMode = true; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.sourceMap).toBe(true); + }); + + it('sets the field to false when set to "dev" and devMode is false', () => { + userConfig.sourceMap = 'dev'; + userConfig.devMode = false; + const { config } = validateConfig(userConfig, bootstrapConfig); + expect(config.sourceMap).toBe(false); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-copy.spec.ts b/packages/core/src/compiler/config/_test_/validate-copy.spec.ts new file mode 100644 index 00000000000..e86b2754388 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-copy.spec.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { validateCopy } from '../validate-copy'; + +describe('validate-copy', () => { + describe('validateCopy', () => { + it.each([false, null, undefined, []])( + 'returns an empty array when the copy task is `%s`', + (copyValue) => { + expect(validateCopy(copyValue, [])).toEqual([]); + }, + ); + + it('pushes default tasks not found in the original copy list', () => { + const defaultCopyTasks: d.CopyTask[] = [ + { src: 'defaultSrc' }, + { src: 'anotherDefaultSrc', dest: 'anotherDefaultDest' }, + ]; + + expect(validateCopy([], defaultCopyTasks)).toEqual(defaultCopyTasks); + }); + + it('combines provided and default tasks', () => { + const tasksToValidate: d.CopyTask[] = [ + { src: 'someSrc', dest: 'someDest', keepDirStructure: true, warn: false }, + ]; + const defaultCopyTasks: d.CopyTask[] = [ + { src: 'defaultSrc' }, + { src: 'anotherDefaultSrc', dest: 'anotherDefaultDest' }, + ]; + + expect(validateCopy(tasksToValidate, defaultCopyTasks)).toEqual([ + ...tasksToValidate, + ...defaultCopyTasks, + ]); + }); + + it('prefers provided tasks over default tasks', () => { + const tasksToValidate: d.CopyTask[] = [ + { src: 'aDuplicateSrc', dest: 'someDest', keepDirStructure: true, warn: false }, + ]; + const defaultCopyTasks: d.CopyTask[] = [ + { src: 'aDuplicateSrc' }, + { src: 'anotherDefaultSrc', dest: 'anotherDefaultDest' }, + ]; + + // the first task from the default task list is not used + expect(validateCopy(tasksToValidate, defaultCopyTasks)).toEqual([ + { src: 'aDuplicateSrc', dest: 'someDest', keepDirStructure: true, warn: false }, + { src: 'anotherDefaultSrc', dest: 'anotherDefaultDest' }, + ]); + }); + + it('de-duplicates copy tasks', () => { + const copyTask: d.CopyTask = { + src: 'aDuplicateSrc', + dest: 'someDest', + keepDirStructure: true, + warn: false, + }; + const tasksToValidate: d.CopyTask[] = [{ ...copyTask }, { ...copyTask }]; + + expect(validateCopy(tasksToValidate, [])).toEqual([{ ...copyTask }]); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-custom.spec.ts b/packages/core/src/compiler/config/_test_/validate-custom.spec.ts new file mode 100644 index 00000000000..4ad5f1beb7c --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-custom.spec.ts @@ -0,0 +1,39 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { buildWarn } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateCustom', () => { + let userConfig: d.Config; + + beforeEach(() => { + userConfig = mockConfig(); + }); + + it('should log warning', () => { + userConfig.outputTargets = [ + { + type: 'custom', + name: 'test', + validate: (_, diagnostics) => { + const warn = buildWarn(diagnostics); + warn.messageText = 'test warning'; + }, + generator: async () => { + return; + }, + }, + ]; + const { diagnostics } = validateConfig(userConfig, mockLoadConfigInit()); + expect(diagnostics.length).toBe(1); + expect(diagnostics[0]).toEqual({ + header: 'Build Warn', + level: 'warn', + lines: [], + messageText: 'test warning', + type: 'build', + }); + }); +}); diff --git a/src/compiler/config/test/validate-dev-server.spec.ts b/packages/core/src/compiler/config/_test_/validate-dev-server.spec.ts similarity index 78% rename from src/compiler/config/test/validate-dev-server.spec.ts rename to packages/core/src/compiler/config/_test_/validate-dev-server.spec.ts index 57aba93a547..ece4d2a630c 100644 --- a/src/compiler/config/test/validate-dev-server.spec.ts +++ b/packages/core/src/compiler/config/_test_/validate-dev-server.spec.ts @@ -1,8 +1,8 @@ -import { mockLoadConfigInit } from '@stencil/core/testing'; import path from 'path'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; -import { ConfigFlags, createConfigFlags } from '../../../cli/config-flags'; -import type * as d from '../../../declarations'; +import { mockLoadConfigInit } from '../../../testing'; import { normalizePath } from '../../../utils'; import { validateConfig } from '../validate-config'; @@ -10,42 +10,37 @@ describe('validateDevServer', () => { const root = path.resolve('/'); let inputConfig: d.UnvalidatedConfig; let inputDevServerConfig: d.DevServerConfig; - let flags: ConfigFlags; beforeEach(() => { inputDevServerConfig = {}; - flags = createConfigFlags({ serve: true }); inputConfig = { sys: {} as any, rootDir: normalizePath(path.join(root, 'some', 'path')), devServer: inputDevServerConfig, - flags, namespace: 'Testing', }; }); it('should default address', () => { const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.address).toBe('0.0.0.0'); + // Default to localhost to avoid Chrome's Private Network Access policy + expect(config.devServer.address).toBe('localhost'); }); - it.each(['https://localhost', 'http://localhost', 'https://localhost/', 'http://localhost/', 'localhost/'])( - 'should remove extraneous stuff from address %p', - (address) => { - inputConfig.devServer = { ...inputDevServerConfig, address }; - const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.address).toBe('localhost'); - }, - ); - - it('should set address', () => { - inputConfig.devServer = { ...inputDevServerConfig, address: '123.123.123.123' }; + it.each([ + 'https://localhost', + 'http://localhost', + 'https://localhost/', + 'http://localhost/', + 'localhost/', + ])('should remove extraneous stuff from address %p', (address) => { + inputConfig.devServer = { ...inputDevServerConfig, address }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.address).toBe('123.123.123.123'); + expect(config.devServer.address).toBe('localhost'); }); - it('should set address from flags', () => { - inputConfig.flags = { ...flags, address: '123.123.123.123' }; + it('should set address', () => { + inputConfig.devServer = { ...inputDevServerConfig, address: '123.123.123.123' }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.address).toBe('123.123.123.123'); }); @@ -85,7 +80,9 @@ describe('validateDevServer', () => { it('should set relative root', () => { inputConfig.devServer = { ...inputDevServerConfig, root: 'my-rel-root' }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.root).toBe(normalizePath(path.join(root, 'some', 'path', 'my-rel-root'))); + expect(config.devServer.root).toBe( + normalizePath(path.join(root, 'some', 'path', 'my-rel-root')), + ); }); it('should set absolute root', () => { @@ -94,7 +91,9 @@ describe('validateDevServer', () => { root: normalizePath(path.join(root, 'some', 'path', 'my-abs-root')), }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.root).toBe(normalizePath(path.join(root, 'some', 'path', 'my-abs-root'))); + expect(config.devServer.root).toBe( + normalizePath(path.join(root, 'some', 'path', 'my-abs-root')), + ); }); it('should default gzip', () => { @@ -135,15 +134,21 @@ describe('validateDevServer', () => { expect(config.devServer.port).toBe(3333); }); - it.each(['localhost:20/', 'localhost:20'])('should set port from address %p if no port prop', (address) => { - inputConfig.devServer = { ...inputDevServerConfig, address }; - const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.port).toBe(20); - expect(config.devServer.address).toBe('localhost'); - }); + it.each(['localhost:20/', 'localhost:20'])( + 'should set port from address %p if no port prop', + (address) => { + inputConfig.devServer = { ...inputDevServerConfig, address }; + const { config } = validateConfig(inputConfig, mockLoadConfigInit()); + expect(config.devServer.port).toBe(20); + expect(config.devServer.address).toBe('localhost'); + }, + ); it('should set address, port null, protocol', () => { - inputConfig.devServer = { ...inputDevServerConfig, address: 'https://subdomain.stenciljs.com/' }; + inputConfig.devServer = { + ...inputDevServerConfig, + address: 'https://subdomain.stenciljs.com/', + }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.port).toBe(undefined); expect(config.devServer.address).toBe('subdomain.stenciljs.com'); @@ -156,12 +161,6 @@ describe('validateDevServer', () => { expect(config.devServer.port).toBe(4444); }); - it('should set port from flags', () => { - inputConfig.flags = { ...flags, port: 4444 }; - const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.port).toBe(4444); - }); - it('should default strictPort to false', () => { const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.strictPort).toBe(false); @@ -185,13 +184,16 @@ describe('validateDevServer', () => { expect(config.devServer.historyApiFallback!.index).toBe('index.html'); }); - it.each([1, []])('should default historyApiFallback when an invalid value (%s) is provided', (badValue) => { - // this test explicitly checks for a bad value in the stencil.config file, hence the type assertion - inputConfig.devServer = { ...inputDevServerConfig, historyApiFallback: badValue as any }; - const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.historyApiFallback).toBeDefined(); - expect(config.devServer.historyApiFallback!.index).toBe('index.html'); - }); + it.each([1, []])( + 'should default historyApiFallback when an invalid value (%s) is provided', + (badValue) => { + // this test explicitly checks for a bad value in the stencil.config file, hence the type assertion + inputConfig.devServer = { ...inputDevServerConfig, historyApiFallback: badValue as any }; + const { config } = validateConfig(inputConfig, mockLoadConfigInit()); + expect(config.devServer.historyApiFallback).toBeDefined(); + expect(config.devServer.historyApiFallback!.index).toBe('index.html'); + }, + ); it('should set historyApiFallback', () => { inputConfig.devServer = { ...inputDevServerConfig, historyApiFallback: {} }; @@ -210,7 +212,10 @@ describe('validateDevServer', () => { it('should disable historyApiFallback', () => { // we intentionally set the value to `null` for the purposes of this test, hence the type assertion - inputConfig.devServer = { ...inputDevServerConfig, historyApiFallback: null as unknown as d.HistoryApiFallback }; + inputConfig.devServer = { + ...inputDevServerConfig, + historyApiFallback: null as unknown as d.HistoryApiFallback, + }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.historyApiFallback).toBe(null); }); @@ -226,22 +231,15 @@ describe('validateDevServer', () => { expect(config.devServer.reloadStrategy).toBe('pageReload'); }); - it('should default openBrowser', () => { - const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.openBrowser).toBe(true); - }); - - it('should set openBrowser', () => { - inputConfig.devServer = { ...inputDevServerConfig, openBrowser: false }; + it('should default openBrowser to false', () => { const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.openBrowser).toBe(false); }); - it('should set openBrowser from flag', () => { - // the flags field should have been set up in the `beforeEach` block for this test, hence the bang operator - inputConfig.flags!.open = false; + it('should set openBrowser', () => { + inputConfig.devServer = { ...inputDevServerConfig, openBrowser: true }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.openBrowser).toBe(false); + expect(config.devServer.openBrowser).toBe(true); }); it('should default http protocol', () => { @@ -250,7 +248,10 @@ describe('validateDevServer', () => { }); it('should set https protocol if credentials are set', () => { - inputConfig.devServer = { ...inputDevServerConfig, https: { key: 'fake-key', cert: 'fake-cert' } }; + inputConfig.devServer = { + ...inputDevServerConfig, + https: { key: 'fake-key', cert: 'fake-cert' }, + }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.protocol).toBe('https'); }); @@ -267,12 +268,6 @@ describe('validateDevServer', () => { expect(config.devServer.ssr).toBe(false); }); - it('should set ssr from flag', () => { - inputConfig.flags = { ...flags, ssr: true }; - const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.ssr).toBe(true); - }); - it('should set ssr false by default', () => { const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.ssr).toBe(false); @@ -280,16 +275,18 @@ describe('validateDevServer', () => { it('should set default srcIndexHtml from config', () => { const { config } = validateConfig(inputConfig, mockLoadConfigInit()); - expect(config.devServer.srcIndexHtml).toBe(normalizePath(path.join(root, 'some', 'path', 'src', 'index.html'))); + expect(config.devServer.srcIndexHtml).toBe( + normalizePath(path.join(root, 'some', 'path', 'src', 'index.html')), + ); }); - it('should set srcIndexHtml from config', () => { + it('should set prerenderConfig from output target when ssr is true', () => { const wwwOutputTarget: d.OutputTargetWww = { type: 'www', prerenderConfig: normalizePath(path.join(root, 'some', 'path', 'prerender.config.ts')), }; inputConfig.outputTargets = [wwwOutputTarget]; - inputConfig.flags = { ...flags, ssr: true }; + inputConfig.devServer = { ...inputDevServerConfig, ssr: true }; const { config } = validateConfig(inputConfig, mockLoadConfigInit()); expect(config.devServer.prerenderConfig).toBe(wwwOutputTarget.prerenderConfig); }); diff --git a/src/compiler/config/test/validate-docs.spec.ts b/packages/core/src/compiler/config/_test_/validate-docs.spec.ts similarity index 78% rename from src/compiler/config/test/validate-docs.spec.ts rename to packages/core/src/compiler/config/_test_/validate-docs.spec.ts index 730fb0b632d..73d3f5c29d1 100644 --- a/src/compiler/config/test/validate-docs.spec.ts +++ b/packages/core/src/compiler/config/_test_/validate-docs.spec.ts @@ -1,6 +1,7 @@ -import type * as d from '@stencil/core/declarations'; -import { mockConfig, mockLoadConfigInit } from '@stencil/core/testing'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; +import { mockConfig, mockLoadConfigInit } from '../../../testing'; import { DEFAULT_TARGET_COMPONENT_STYLES } from '../constants'; import { validateConfig } from '../validate-config'; @@ -12,9 +13,6 @@ describe('validateDocs', () => { }); it('readme docs dir', () => { - // the flags field is expected to have been set by the mock creation function for unvalidated configs, hence the - // bang operator - userConfig.flags!.docs = true; userConfig.outputTargets = [ { type: 'docs-readme', @@ -22,7 +20,9 @@ describe('validateDocs', () => { } as d.OutputTargetDocsReadme, ]; const { config } = validateConfig(userConfig, mockLoadConfigInit()); - const o = config.outputTargets.find((o) => o.type === 'docs-readme') as d.OutputTargetDocsReadme; + const o = config.outputTargets.find( + (o) => o.type === 'docs-readme', + ) as d.OutputTargetDocsReadme; expect(o.dir).toContain('my-dir'); }); @@ -39,7 +39,9 @@ describe('validateDocs', () => { it('should use default values for docs.markdown.targetComponent', () => { const { config } = validateConfig(userConfig, mockLoadConfigInit()); - expect(config.docs.markdown.targetComponent.background).toBe(DEFAULT_TARGET_COMPONENT_STYLES.background); + expect(config.docs.markdown.targetComponent.background).toBe( + DEFAULT_TARGET_COMPONENT_STYLES.background, + ); }); it('should use user values for docs.markdown.targetComponent.background', () => { @@ -53,7 +55,9 @@ describe('validateDocs', () => { }, }); const { config } = validateConfig(userConfig, mockLoadConfigInit()); - expect(config.docs.markdown.targetComponent.background).toBe(userConfig.docs.markdown.targetComponent.background); + expect(config.docs.markdown.targetComponent.background).toBe( + userConfig.docs.markdown.targetComponent.background, + ); }); it('should use user values for docs.markdown.targetComponent.textColor', () => { @@ -67,6 +71,8 @@ describe('validateDocs', () => { }, }); const { config } = validateConfig(userConfig, mockLoadConfigInit()); - expect(config.docs.markdown.targetComponent.textColor).toBe(userConfig.docs.markdown.targetComponent.textColor); + expect(config.docs.markdown.targetComponent.textColor).toBe( + userConfig.docs.markdown.targetComponent.textColor, + ); }); }); diff --git a/src/compiler/config/test/validate-hydrated.spec.ts b/packages/core/src/compiler/config/_test_/validate-hydrated.spec.ts similarity index 92% rename from src/compiler/config/test/validate-hydrated.spec.ts rename to packages/core/src/compiler/config/_test_/validate-hydrated.spec.ts index 6205e63fb4a..479b9bb2cb6 100644 --- a/src/compiler/config/test/validate-hydrated.spec.ts +++ b/packages/core/src/compiler/config/_test_/validate-hydrated.spec.ts @@ -1,4 +1,5 @@ -import * as d from '@stencil/core/declarations'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; import { validateHydrated } from '../validate-hydrated'; diff --git a/src/compiler/config/test/validate-namespace.spec.ts b/packages/core/src/compiler/config/_test_/validate-namespace.spec.ts similarity index 96% rename from src/compiler/config/test/validate-namespace.spec.ts rename to packages/core/src/compiler/config/_test_/validate-namespace.spec.ts index 142c0dff205..879b0f95105 100644 --- a/src/compiler/config/test/validate-namespace.spec.ts +++ b/packages/core/src/compiler/config/_test_/validate-namespace.spec.ts @@ -1,4 +1,5 @@ -import type * as d from '@stencil/core/declarations'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; import { validateNamespace } from '../validate-namespace'; diff --git a/packages/core/src/compiler/config/_test_/validate-output-assets.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-assets.spec.ts new file mode 100644 index 00000000000..a78ff179287 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-assets.spec.ts @@ -0,0 +1,202 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { ASSETS, GLOBAL_STYLE, isOutputTargetAssets, join, resolve } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateAssetsOutputTarget', () => { + let config: d.Config; + + const rootDir = resolve('/'); + const defaultDir = join(rootDir, 'dist', 'assets'); + + beforeEach(() => { + config = mockConfig(); + }); + + describe('defaults', () => { + it('sets correct default directory', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets).toBeDefined(); + expect(assets?.dir).toBe(defaultDir); + }); + + it('uses custom directory when specified', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + dir: 'my-assets-output', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets?.dir).toBe(join(rootDir, 'my-assets-output')); + }); + + it('defaults skipInDev to false', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets?.skipInDev).toBe(false); + }); + + it('preserves skipInDev when explicitly set to true', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + skipInDev: true, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets?.skipInDev).toBe(true); + }); + }); + + describe('auto-generation', () => { + it('auto-generates assets output even when no output targets are configured', () => { + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets).toBeDefined(); + expect(assets?.type).toBe(ASSETS); + expect(assets?.dir).toBe(defaultDir); + }); + + it('does not duplicate assets if already explicitly configured', () => { + config.outputTargets = [{ type: ASSETS, dir: 'custom-assets' }]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assetsTargets = validatedConfig.outputTargets.filter(isOutputTargetAssets); + expect(assetsTargets).toHaveLength(1); + expect(assetsTargets[0].dir).toBe(join(rootDir, 'custom-assets')); + }); + + it('auto-generates in both dev and prod mode', () => { + // Dev mode + const devConfig = mockConfig({ devMode: true }); + devConfig.outputTargets = []; + + const { config: validatedDevConfig } = validateConfig(devConfig, mockLoadConfigInit()); + expect(validatedDevConfig.outputTargets.find(isOutputTargetAssets)).toBeDefined(); + + // Prod mode + const prodConfig = mockConfig({ devMode: false }); + prodConfig.outputTargets = []; + + const { config: validatedProdConfig } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(validatedProdConfig.outputTargets.find(isOutputTargetAssets)).toBeDefined(); + }); + }); + + describe('absolute paths', () => { + it('handles absolute paths correctly', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + dir: '/absolute/path/to/assets', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets?.dir).toBe('/absolute/path/to/assets'); + }); + + it('resolves relative paths from root directory', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + dir: 'relative/assets/path', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets?.dir).toBe(join(rootDir, 'relative/assets/path')); + }); + }); + + describe('explicit configuration', () => { + it('preserves all explicit configuration values', () => { + const target: d.OutputTargetAssets = { + type: ASSETS, + dir: 'custom-dir', + skipInDev: true, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + expect(assets).toBeDefined(); + expect(assets?.dir).toBe(join(rootDir, 'custom-dir')); + expect(assets?.skipInDev).toBe(true); + }); + }); + + describe('interaction with other output targets', () => { + it('does not interfere with global-style output target', () => { + config.globalStyle = 'src/global.css'; + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + // Both should exist + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + const globalStyle = validatedConfig.outputTargets.find((o) => o.type === GLOBAL_STYLE); + + expect(assets).toBeDefined(); + expect(globalStyle).toBeDefined(); + }); + + it('assets and global-style share the same default directory', () => { + config.globalStyle = 'src/global.css'; + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assets = validatedConfig.outputTargets.find(isOutputTargetAssets); + const globalStyle = validatedConfig.outputTargets.find( + (o) => o.type === GLOBAL_STYLE, + ) as d.OutputTargetGlobalStyle; + + expect(assets?.dir).toBe(defaultDir); + expect(globalStyle?.dir).toBe(defaultDir); + }); + }); + + describe('multiple assets targets', () => { + it('allows multiple assets output targets with different directories', () => { + config.outputTargets = [ + { type: ASSETS, dir: 'dist/assets1' }, + { type: ASSETS, dir: 'dist/assets2' }, + ]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const assetsTargets = validatedConfig.outputTargets.filter(isOutputTargetAssets); + expect(assetsTargets).toHaveLength(2); + expect(assetsTargets[0].dir).toBe(join(rootDir, 'dist/assets1')); + expect(assetsTargets[1].dir).toBe(join(rootDir, 'dist/assets2')); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-global-style.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-global-style.spec.ts new file mode 100644 index 00000000000..a13357f4300 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-global-style.spec.ts @@ -0,0 +1,656 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { ASSETS, GLOBAL_STYLE, isOutputTargetGlobalStyle, join, resolve } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateGlobalStyleOutputTarget', () => { + let config: d.Config; + + const rootDir = resolve('/'); + const defaultDir = join(rootDir, 'dist', 'assets'); + + beforeEach(() => { + config = mockConfig(); + }); + + describe('defaults', () => { + it('sets correct default directory', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle).toBeDefined(); + expect(globalStyle?.dir).toBe(defaultDir); + }); + + it('uses custom directory when specified', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + dir: 'my-styles-output', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.dir).toBe(join(rootDir, 'my-styles-output')); + }); + + it('defaults copyToLoaderBrowser to true', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.copyToLoaderBrowser).toBe(true); + }); + + it('preserves copyToLoaderBrowser when explicitly set to false', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + copyToLoaderBrowser: false, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.copyToLoaderBrowser).toBe(false); + }); + + it('defaults skipInDev to false', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.skipInDev).toBe(false); + }); + + it('preserves skipInDev when explicitly set to true', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + skipInDev: true, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.skipInDev).toBe(true); + }); + }); + + describe('auto-generation', () => { + it('auto-generates global-style output when globalStyle config is set', () => { + config.globalStyle = 'src/global.css'; + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle).toBeDefined(); + expect(globalStyle?.type).toBe(GLOBAL_STYLE); + expect(globalStyle?.dir).toBe(defaultDir); + expect(globalStyle?.copyToLoaderBrowser).toBe(true); + }); + + it('does not auto-generate global-style output when globalStyle config is not set', () => { + config.globalStyle = undefined; + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle).toBeUndefined(); + }); + + it('does not duplicate global-style if already explicitly configured', () => { + config.globalStyle = 'src/global.css'; + config.outputTargets = [{ type: GLOBAL_STYLE, dir: 'custom-styles' }]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyleTargets = validatedConfig.outputTargets.filter(isOutputTargetGlobalStyle); + expect(globalStyleTargets).toHaveLength(1); + expect(globalStyleTargets[0].dir).toBe(join(rootDir, 'custom-styles')); + }); + + it('auto-generates in both dev and prod mode', () => { + // Dev mode + const devConfig = mockConfig({ devMode: true }); + devConfig.globalStyle = 'src/global.css'; + devConfig.outputTargets = []; + + const { config: validatedDevConfig } = validateConfig(devConfig, mockLoadConfigInit()); + expect(validatedDevConfig.outputTargets.find(isOutputTargetGlobalStyle)).toBeDefined(); + + // Prod mode + const prodConfig = mockConfig({ devMode: false }); + prodConfig.globalStyle = 'src/global.css'; + prodConfig.outputTargets = []; + + const { config: validatedProdConfig } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(validatedProdConfig.outputTargets.find(isOutputTargetGlobalStyle)).toBeDefined(); + }); + }); + + describe('absolute paths', () => { + it('handles absolute paths correctly', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + dir: '/absolute/path/to/styles', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.dir).toBe('/absolute/path/to/styles'); + }); + + it('resolves relative paths from root directory', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + dir: 'relative/styles/path', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.dir).toBe(join(rootDir, 'relative/styles/path')); + }); + }); + + describe('explicit configuration', () => { + it('preserves all explicit configuration values', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + dir: 'custom-dir', + copyToLoaderBrowser: false, + skipInDev: true, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle).toBeDefined(); + expect(globalStyle?.dir).toBe(join(rootDir, 'custom-dir')); + expect(globalStyle?.copyToLoaderBrowser).toBe(false); + expect(globalStyle?.skipInDev).toBe(true); + }); + }); + + describe('explicit input property', () => { + it('uses explicit input path when provided', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: './src/theme.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.input).toBe(join(rootDir, 'src/theme.css')); + }); + + it('explicit input takes precedence over globalStyle config', () => { + config.globalStyle = './src/global.css'; + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: './src/theme.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + // The explicit input should be used, not globalStyle + expect(globalStyle?.input).toBe(join(rootDir, 'src/theme.css')); + }); + + it('falls back to globalStyle config when no explicit input', () => { + config.globalStyle = './src/global.css'; + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + // No explicit input + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + // Should use globalStyle config since no explicit input + expect(globalStyle?.input).toBe(join(rootDir, 'src/global.css')); + }); + + it('input is undefined when neither explicit input nor globalStyle set', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.input).toBeUndefined(); + }); + + it('resolves relative input paths from root directory', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: 'styles/main.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.input).toBe(join(rootDir, 'styles/main.css')); + }); + + it('handles absolute input paths correctly', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: '/absolute/path/to/styles.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.input).toBe('/absolute/path/to/styles.css'); + }); + }); + + describe('fileName derivation', () => { + it('uses explicit fileName when provided', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: './src/styles.css', + fileName: 'custom-name.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.fileName).toBe('custom-name.css'); + }); + + it('uses input basename when explicit input provided and no fileName', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: './src/my-theme.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.fileName).toBe('my-theme.css'); + }); + + it('uses {namespace}.css when globalStyle config used (no explicit input)', () => { + config.globalStyle = './src/global.css'; + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + // No explicit input or fileName + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + // Should use namespace.css for backwards compat, not 'global.css' + // mockConfig uses 'testing' as fsNamespace + expect(globalStyle?.fileName).toBe('testing.css'); + }); + + it('uses {namespace}.css when no input at all', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + // mockConfig uses 'testing' as fsNamespace + expect(globalStyle?.fileName).toBe('testing.css'); + }); + + it('uses custom namespace for fileName when globalStyle config used', () => { + config.namespace = 'MyLib'; + config.globalStyle = './src/global.css'; + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.fileName).toBe('mylib.css'); + }); + }); + + describe('multiple global-style outputs', () => { + it('supports multiple global-style outputs with different inputs', () => { + config.outputTargets = [ + { + type: GLOBAL_STYLE, + input: './src/theme.css', + }, + { + type: GLOBAL_STYLE, + input: './src/utilities.css', + }, + ]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyles = validatedConfig.outputTargets.filter(isOutputTargetGlobalStyle); + expect(globalStyles).toHaveLength(2); + expect(globalStyles[0].input).toBe(join(rootDir, 'src/theme.css')); + expect(globalStyles[0].fileName).toBe('theme.css'); + expect(globalStyles[1].input).toBe(join(rootDir, 'src/utilities.css')); + expect(globalStyles[1].fileName).toBe('utilities.css'); + }); + + it('supports multiple global-style outputs with custom directories', () => { + config.outputTargets = [ + { + type: GLOBAL_STYLE, + input: './src/theme.css', + dir: 'dist/css', + }, + { + type: GLOBAL_STYLE, + input: './src/utilities.css', + dir: 'dist/other-css', + }, + ]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyles = validatedConfig.outputTargets.filter(isOutputTargetGlobalStyle); + expect(globalStyles).toHaveLength(2); + expect(globalStyles[0].dir).toBe(join(rootDir, 'dist/css')); + expect(globalStyles[1].dir).toBe(join(rootDir, 'dist/other-css')); + }); + + it('supports mixed explicit input and globalStyle fallback', () => { + config.globalStyle = './src/global.css'; + config.outputTargets = [ + { + type: GLOBAL_STYLE, + input: './src/theme.css', // explicit input + }, + { + type: GLOBAL_STYLE, + // no input - will use globalStyle + }, + ]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyles = validatedConfig.outputTargets.filter(isOutputTargetGlobalStyle); + expect(globalStyles).toHaveLength(2); + expect(globalStyles[0].input).toBe(join(rootDir, 'src/theme.css')); + expect(globalStyles[0].fileName).toBe('theme.css'); + expect(globalStyles[1].input).toBe(join(rootDir, 'src/global.css')); + expect(globalStyles[1].fileName).toBe('testing.css'); // namespace-based (mockConfig uses 'testing') + }); + }); + + describe('interaction with other output targets', () => { + it('does not interfere with assets output target', () => { + config.globalStyle = 'src/global.css'; + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + // Both should exist + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + const assets = validatedConfig.outputTargets.find((o) => o.type === ASSETS); + + expect(globalStyle).toBeDefined(); + expect(assets).toBeDefined(); + }); + + it('global-style and assets can have the same directory', () => { + config.globalStyle = 'src/global.css'; + config.outputTargets = [ + { type: GLOBAL_STYLE, dir: 'dist/assets' }, + { type: ASSETS, dir: 'dist/assets' }, + ]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + const assets = validatedConfig.outputTargets.find((o) => o.type === ASSETS); + + expect(globalStyle?.dir).toBe(join(rootDir, 'dist/assets')); + expect((assets as d.OutputTargetAssets)?.dir).toBe(join(rootDir, 'dist/assets')); + }); + }); + + describe('inject option', () => { + it('defaults inject to none for explicit global-style with input', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + input: './src/theme.css', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('none'); + }); + + it('defaults inject to client for auto-generated global-style from globalStyle config', () => { + config.globalStyle = './src/global.css'; + config.outputTargets = []; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('client'); + }); + + it('defaults inject to client for explicit global-style using globalStyle config fallback', () => { + config.globalStyle = './src/global.css'; + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + // No explicit input - will use globalStyle config + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('client'); + }); + + it('defaults inject to none for explicit global-style with no input and no globalStyle config', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('none'); + }); + + it('preserves inject: client when explicitly set', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + inject: 'client', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('client'); + }); + + it('preserves inject: all when explicitly set', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + inject: 'all', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('all'); + }); + + it('preserves inject: none when explicitly set', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + inject: 'none', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('none'); + }); + + it('defaults to none for invalid inject values', () => { + const target: d.OutputTargetGlobalStyle = { + type: GLOBAL_STYLE, + inject: 'invalid' as 'none', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyle = validatedConfig.outputTargets.find(isOutputTargetGlobalStyle); + expect(globalStyle?.inject).toBe('none'); + }); + + it('supports different inject values for multiple global-style outputs', () => { + config.outputTargets = [ + { + type: GLOBAL_STYLE, + input: './src/theme.css', + inject: 'client', + }, + { + type: GLOBAL_STYLE, + input: './src/utilities.css', + inject: 'all', + }, + { + type: GLOBAL_STYLE, + input: './src/reset.css', + inject: 'none', + }, + ]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const globalStyles = validatedConfig.outputTargets.filter(isOutputTargetGlobalStyle); + expect(globalStyles).toHaveLength(3); + expect(globalStyles[0].inject).toBe('client'); + expect(globalStyles[1].inject).toBe('all'); + expect(globalStyles[2].inject).toBe('none'); + }); + }); + + describe('diagnostics and warnings', () => { + it('emits warning when both globalStyle config and explicit input are used', () => { + config.globalStyle = './src/global.css'; + config.outputTargets = [ + { + type: GLOBAL_STYLE, + input: './src/theme.css', // explicit input conflicts with globalStyle config + }, + ]; + + const { diagnostics } = validateConfig(config, mockLoadConfigInit()); + + const warnings = diagnostics.filter((d) => d.level === 'warn'); + expect(warnings.length).toBeGreaterThan(0); + + const globalStyleWarning = warnings.find( + (w) => w.messageText.includes('globalStyle') && w.messageText.includes('global-style'), + ); + expect(globalStyleWarning).toBeDefined(); + expect(globalStyleWarning?.messageText).toContain('Choose one approach'); + }); + + it('does not emit warning when only globalStyle config is used', () => { + config.globalStyle = './src/global.css'; + config.outputTargets = []; + + const { diagnostics } = validateConfig(config, mockLoadConfigInit()); + + const warnings = diagnostics.filter((d) => d.level === 'warn'); + const globalStyleWarning = warnings.find( + (w) => w.messageText.includes('globalStyle') && w.messageText.includes('global-style'), + ); + expect(globalStyleWarning).toBeUndefined(); + }); + + it('does not emit warning when only explicit input is used (no globalStyle config)', () => { + config.globalStyle = undefined; + config.outputTargets = [ + { + type: GLOBAL_STYLE, + input: './src/theme.css', + }, + ]; + + const { diagnostics } = validateConfig(config, mockLoadConfigInit()); + + const warnings = diagnostics.filter((d) => d.level === 'warn'); + const globalStyleWarning = warnings.find( + (w) => w.messageText.includes('globalStyle') && w.messageText.includes('global-style'), + ); + expect(globalStyleWarning).toBeUndefined(); + }); + + it('does not emit warning when explicit global-style has no input (uses globalStyle fallback)', () => { + config.globalStyle = './src/global.css'; + config.outputTargets = [ + { + type: GLOBAL_STYLE, + // No explicit input - will fallback to globalStyle + }, + ]; + + const { diagnostics } = validateConfig(config, mockLoadConfigInit()); + + const warnings = diagnostics.filter((d) => d.level === 'warn'); + const globalStyleWarning = warnings.find( + (w) => w.messageText.includes('globalStyle') && w.messageText.includes('global-style'), + ); + expect(globalStyleWarning).toBeUndefined(); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-loader-bundle.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-loader-bundle.spec.ts new file mode 100644 index 00000000000..86e0d21cc07 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-loader-bundle.spec.ts @@ -0,0 +1,169 @@ +import path from 'path'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { + ASSETS, + COPY, + DIST_LAZY, + join, + LOADER_BUNDLE, + STENCIL_REBUNDLE, + TYPES, +} from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateLoaderBundleOutputTarget', () => { + // use Node's resolve() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) + const rootDir = path.resolve('/'); + + let userConfig: d.Config; + beforeEach(() => { + userConfig = mockConfig({ fsNamespace: 'testing' }); + }); + + it('should set loader-bundle values (dev mode - browser bundle only)', () => { + const outputTarget: d.OutputTargetLoaderBundle = { + type: LOADER_BUNDLE, + dir: 'my-dist', + buildDir: 'my-build', + empty: false, + cjs: true, + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + // In dev mode, only browser bundle is generated (no distribution bundles) + expect(config.outputTargets).toEqual([ + { + type: ASSETS, + dir: join(rootDir, 'dist', 'assets'), + skipInDev: false, + }, + { + buildDir: join(rootDir, 'my-dist', 'my-build'), + cjs: true, + copy: [], + dir: join(rootDir, 'my-dist'), + empty: false, + loaderPath: 'loader', + type: LOADER_BUNDLE, + skipInDev: true, + }, + { + type: DIST_LAZY, + esmDir: join(rootDir, 'my-dist', 'my-build', 'testing'), + isBrowserBuild: true, + empty: false, + }, + { + type: COPY, + dir: join(rootDir, 'my-dist', 'my-build', 'testing'), + copy: [], + }, + ]); + }); + + it('should generate distribution bundles in production mode', () => { + const prodConfig = mockConfig({ devMode: false, fsNamespace: 'testing' }); + const outputTarget: d.OutputTargetLoaderBundle = { + type: LOADER_BUNDLE, + dir: 'my-dist', + buildDir: 'my-build', + empty: false, + cjs: true, + }; + prodConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + + // In production mode, distribution bundles ARE generated + const distLazyOutputs = config.outputTargets.filter((o) => o.type === DIST_LAZY); + expect(distLazyOutputs).toHaveLength(2); // browser bundle + distribution bundle + + const distributionBundle = distLazyOutputs.find( + (o) => (o as d.OutputTargetDistLazy).esmIndexFile, + ) as d.OutputTargetDistLazy; + expect(distributionBundle).toBeDefined(); + expect(distributionBundle.esmDir).toBe(join(rootDir, 'my-dist', 'esm')); + expect(distributionBundle.cjsDir).toBe(join(rootDir, 'my-dist', 'cjs')); + }); + + it('should set defaults when outputTargets loader-bundle is empty', () => { + userConfig.outputTargets = [{ type: LOADER_BUNDLE }]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const outputTarget = config.outputTargets.find( + (o) => o.type === LOADER_BUNDLE, + ) as d.OutputTargetLoaderBundle; + expect(outputTarget).toBeDefined(); + expect(outputTarget.dir).toBe(join(rootDir, 'dist', 'loader-bundle')); + expect(outputTarget.buildDir).toBe(join(rootDir, 'dist', 'loader-bundle')); + expect(outputTarget.empty).toBe(true); + }); + + it('should default to not add loader-bundle when outputTargets exists, but without loader-bundle', () => { + userConfig.outputTargets = []; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === LOADER_BUNDLE)).toBe(false); + }); + + it('defaults cjs to false when not specified', () => { + const outputTarget: d.OutputTargetLoaderBundle = { + type: LOADER_BUNDLE, + dir: 'my-dist', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const validated = config.outputTargets.find( + (o) => o.type === LOADER_BUNDLE, + ) as d.OutputTargetLoaderBundle; + expect(validated.cjs).toBe(false); + }); + + it('defaults skipInDev to true (distribution bundles skip in dev, browser bundle always builds)', () => { + const outputTarget: d.OutputTargetLoaderBundle = { + type: LOADER_BUNDLE, + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const validated = config.outputTargets.find( + (o) => o.type === LOADER_BUNDLE, + ) as d.OutputTargetLoaderBundle; + expect(validated.skipInDev).toBe(true); + }); + + describe('production mode auto-generation', () => { + let prodConfig: d.Config; + + beforeEach(() => { + prodConfig = mockConfig({ devMode: false, fsNamespace: 'testing' }); + }); + + it('auto-generates types alongside loader-bundle in production mode', () => { + prodConfig.outputTargets = [{ type: LOADER_BUNDLE }]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === TYPES)).toBe(true); + expect(config.outputTargets.some((o) => o.type === LOADER_BUNDLE)).toBe(true); + }); + + it('auto-generates stencil-rebundle alongside loader-bundle in production mode', () => { + prodConfig.outputTargets = [{ type: LOADER_BUNDLE }]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === STENCIL_REBUNDLE)).toBe(true); + }); + + it('does not duplicate types if already explicitly configured', () => { + prodConfig.outputTargets = [{ type: LOADER_BUNDLE }, { type: TYPES, dir: 'my-types' }]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(config.outputTargets.filter((o) => o.type === TYPES)).toHaveLength(1); + }); + + it('does not auto-generate types or stencil-rebundle in dev mode', () => { + const devConfig = mockConfig({ devMode: true, fsNamespace: 'testing' }); + devConfig.outputTargets = [{ type: LOADER_BUNDLE }]; + const { config } = validateConfig(devConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === TYPES)).toBe(false); + expect(config.outputTargets.some((o) => o.type === STENCIL_REBUNDLE)).toBe(false); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-ssr.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-ssr.spec.ts new file mode 100644 index 00000000000..c3df38f040a --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-ssr.spec.ts @@ -0,0 +1,285 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { isOutputTargetSsr, join, resolve, SSR, WWW } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateSsrOutputTarget', () => { + let config: d.Config; + + const rootDir = resolve('/'); + const defaultDir = join(rootDir, 'dist', 'ssr'); + + beforeEach(() => { + config = mockConfig(); + }); + + describe('defaults', () => { + it('sets correct default directory', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr).toBeDefined(); + expect(ssr?.dir).toBe(defaultDir); + }); + + it('uses custom directory when specified', () => { + const target: d.OutputTargetSsr = { + type: SSR, + dir: 'my-ssr-output', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.dir).toBe(join(rootDir, 'my-ssr-output')); + }); + + it('defaults empty to true', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.empty).toBe(true); + }); + + it('preserves empty when explicitly set to false', () => { + const target: d.OutputTargetSsr = { + type: SSR, + empty: false, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.empty).toBe(false); + }); + + it('defaults minify to false', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.minify).toBe(false); + }); + + it('preserves minify when explicitly set to true', () => { + const target: d.OutputTargetSsr = { + type: SSR, + minify: true, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.minify).toBe(true); + }); + + it('defaults cjs to false', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.cjs).toBe(false); + }); + + it('preserves cjs when explicitly set to true', () => { + const target: d.OutputTargetSsr = { + type: SSR, + cjs: true, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.cjs).toBe(true); + }); + + it('adds Node.js built-ins to external array', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.external).toContain('fs'); + expect(ssr?.external).toContain('path'); + expect(ssr?.external).toContain('crypto'); + }); + + it('preserves custom externals and adds Node.js built-ins', () => { + const target: d.OutputTargetSsr = { + type: SSR, + external: ['my-custom-external'], + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.external).toContain('my-custom-external'); + expect(ssr?.external).toContain('fs'); + expect(ssr?.external).toContain('path'); + expect(ssr?.external).toContain('crypto'); + }); + }); + + describe('skipInDev', () => { + it('defaults skipInDev to true when devServer.ssr is not enabled', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.skipInDev).toBe(true); + }); + + it('defaults skipInDev to false when devServer.ssr is enabled', () => { + const target: d.OutputTargetSsr = { + type: SSR, + }; + config.outputTargets = [target]; + config.devServer = { ssr: true }; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.skipInDev).toBe(false); + }); + + it('preserves skipInDev when explicitly set to false', () => { + const target: d.OutputTargetSsr = { + type: SSR, + skipInDev: false, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.skipInDev).toBe(false); + }); + + it('preserves skipInDev when explicitly set to true even with devServer.ssr', () => { + const target: d.OutputTargetSsr = { + type: SSR, + skipInDev: true, + }; + config.outputTargets = [target]; + config.devServer = { ssr: true }; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr?.skipInDev).toBe(true); + }); + }); + + describe('auto-creation', () => { + it('auto-creates SSR output target when prerender is enabled with www output', () => { + const wwwTarget: d.OutputTargetWww = { + type: WWW, + indexHtml: 'index.html', + }; + config.outputTargets = [wwwTarget]; + config.prerender = true; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr).toBeDefined(); + expect(ssr?.type).toBe(SSR); + }); + + it('auto-creates SSR output target when ssr config is enabled with www output', () => { + const wwwTarget: d.OutputTargetWww = { + type: WWW, + indexHtml: 'index.html', + }; + config.outputTargets = [wwwTarget]; + config.ssr = true; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr).toBeDefined(); + expect(ssr?.type).toBe(SSR); + }); + + it('auto-creates SSR output target when www output has no explicit indexHtml (defaults to index.html)', () => { + // Note: The www validator sets a default indexHtml of 'index.html' when not provided, + // so SSR will still be auto-created + const wwwTarget: d.OutputTargetWww = { + type: WWW, + }; + config.outputTargets = [wwwTarget]; + config.prerender = true; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr).toBeDefined(); + expect(ssr?.type).toBe(SSR); + }); + + it('does not auto-create SSR output target when prerender is false', () => { + const wwwTarget: d.OutputTargetWww = { + type: WWW, + indexHtml: 'index.html', + }; + config.outputTargets = [wwwTarget]; + config.prerender = false; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssr = validatedConfig.outputTargets.find(isOutputTargetSsr); + expect(ssr).toBeUndefined(); + }); + + it('does not duplicate SSR output target if already explicitly configured', () => { + const wwwTarget: d.OutputTargetWww = { + type: WWW, + indexHtml: 'index.html', + }; + const ssrTarget: d.OutputTargetSsr = { + type: SSR, + dir: 'my-custom-ssr', + }; + config.outputTargets = [wwwTarget, ssrTarget]; + config.prerender = true; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const ssrTargets = validatedConfig.outputTargets.filter(isOutputTargetSsr); + expect(ssrTargets).toHaveLength(1); + expect(ssrTargets[0].dir).toBe(join(rootDir, 'my-custom-ssr')); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-standalone.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-standalone.spec.ts new file mode 100644 index 00000000000..15f110623b9 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-standalone.spec.ts @@ -0,0 +1,444 @@ +import path from 'path'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { + ASSETS, + COPY, + LOADER_BUNDLE, + STENCIL_REBUNDLE, + STANDALONE, + TYPES, + join, +} from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validate-output-standalone', () => { + describe('validateStandalone', () => { + // use Node's resolve() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) + const rootDir = path.resolve('/'); + const defaultDistDir = join(rootDir, 'dist', 'standalone'); + const distCustomElementsDir = 'my-standalone'; + + // Assets output target is auto-generated for all builds + const assetsOutputTarget: d.OutputTargetAssets = { + type: ASSETS, + dir: join(rootDir, 'dist', 'assets'), + skipInDev: false, + }; + + let userConfig: d.Config; + + beforeEach(() => { + userConfig = mockConfig(); + }); + + it('generates a default standalone output target', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: true, + externalRuntime: true, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + + it('uses a provided export behavior over the default value', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + customElementsExportBehavior: 'single-export-module', + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: true, + externalRuntime: true, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'single-export-module', + skipInDev: false, + }, + ]); + }); + + it('uses the default export behavior if the specified value is invalid', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + customElementsExportBehavior: 'not-a-valid-option' as d.CustomElementsExportBehavior, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: true, + externalRuntime: true, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + + it('uses a provided dir field over a default directory', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + dir: distCustomElementsDir, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: join(rootDir, distCustomElementsDir), + empty: true, + externalRuntime: true, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + + describe('"empty" field', () => { + it('defaults the "empty" field to true if not provided', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + externalRuntime: false, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: true, + externalRuntime: false, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + + it('defaults the "empty" field to true it\'s not a boolean', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + empty: undefined, + externalRuntime: false, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: true, + externalRuntime: false, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + }); + + describe('"externalRuntime" field', () => { + it('defaults the "externalRuntime" field to true if not provided', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + empty: false, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: false, + externalRuntime: true, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + + it('defaults the "externalRuntime" field to true it\'s not a boolean', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + empty: false, + externalRuntime: undefined, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: STANDALONE, + copy: [], + dir: defaultDistDir, + empty: false, + externalRuntime: true, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + }); + + describe('copy tasks', () => { + it('copies existing copy tasks over to the output target', () => { + const copyOutputTarget: d.CopyTask = { + src: 'mock/src', + dest: 'mock/dest', + }; + const copyOutputTarget2: d.CopyTask = { + src: 'mock/src2', + dest: 'mock/dest2', + }; + + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + copy: [copyOutputTarget, copyOutputTarget2], + dir: distCustomElementsDir, + empty: false, + externalRuntime: false, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toEqual([ + assetsOutputTarget, + { + type: COPY, + dir: rootDir, + copy: [copyOutputTarget, copyOutputTarget2], + }, + { + type: STANDALONE, + copy: [copyOutputTarget, copyOutputTarget2], + dir: join(rootDir, distCustomElementsDir), + empty: false, + externalRuntime: false, + autoLoader: { fileName: 'loader', autoStart: true }, + customElementsExportBehavior: 'default', + skipInDev: false, + }, + ]); + }); + }); + + describe('"autoLoader" field', () => { + it('normalizes autoLoader: true to an object with defaults', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + autoLoader: true, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const distCustomElementsTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(distCustomElementsTarget.autoLoader).toEqual({ + fileName: 'loader', + autoStart: true, + }); + }); + + it('normalizes autoLoader object with partial options', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + autoLoader: { fileName: 'my-loader' }, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const distCustomElementsTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(distCustomElementsTarget.autoLoader).toEqual({ + fileName: 'my-loader', + autoStart: true, + }); + }); + + it('normalizes autoLoader object with autoStart: false', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + autoLoader: { autoStart: false }, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const distCustomElementsTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(distCustomElementsTarget.autoLoader).toEqual({ + fileName: 'loader', + autoStart: false, + }); + }); + + it('defaults autoLoader to enabled object when not provided', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const distCustomElementsTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(distCustomElementsTarget.autoLoader).toEqual({ + fileName: 'loader', + autoStart: true, + }); + }); + + it('does not set autoLoader when explicitly false', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + autoLoader: false, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const distCustomElementsTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(distCustomElementsTarget.autoLoader).toBe(false); + }); + }); + + describe('"skipInDev" field', () => { + it('defaults to false when standalone is the primary output (no loader-bundle)', () => { + const outputTarget: d.OutputTargetStandalone = { + type: STANDALONE, + }; + userConfig.outputTargets = [outputTarget]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const standaloneTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(standaloneTarget.skipInDev).toBe(false); + }); + + it('defaults to true when loader-bundle is also configured (standalone is secondary)', () => { + userConfig.outputTargets = [{ type: STANDALONE }, { type: LOADER_BUNDLE }]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const standaloneTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(standaloneTarget.skipInDev).toBe(true); + }); + + it('respects explicit skipInDev: false even with loader-bundle', () => { + userConfig.outputTargets = [ + { type: STANDALONE, skipInDev: false }, + { type: LOADER_BUNDLE }, + ]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const standaloneTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(standaloneTarget.skipInDev).toBe(false); + }); + + it('respects explicit skipInDev: true even without loader-bundle', () => { + userConfig.outputTargets = [{ type: STANDALONE, skipInDev: true }]; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const standaloneTarget = config.outputTargets.find( + (o) => o.type === STANDALONE, + ) as d.OutputTargetStandalone; + + expect(standaloneTarget.skipInDev).toBe(true); + }); + }); + }); + + describe('production mode auto-generation', () => { + let prodConfig: d.Config; + + beforeEach(() => { + prodConfig = mockConfig({ devMode: false }); + }); + + it('auto-generates types alongside standalone in production mode', () => { + prodConfig.outputTargets = [{ type: STANDALONE }]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === TYPES)).toBe(true); + expect(config.outputTargets.some((o) => o.type === STANDALONE)).toBe(true); + }); + + it('auto-generates stencil-rebundle alongside standalone in production mode', () => { + prodConfig.outputTargets = [{ type: STANDALONE }]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === STENCIL_REBUNDLE)).toBe(true); + }); + + it('does not duplicate types if already explicitly configured', () => { + prodConfig.outputTargets = [{ type: STANDALONE }, { type: TYPES, dir: 'my-types' }]; + const { config } = validateConfig(prodConfig, mockLoadConfigInit()); + expect(config.outputTargets.filter((o) => o.type === TYPES)).toHaveLength(1); + }); + + it('does not auto-generate types or stencil-rebundle in dev mode', () => { + const devConfig = mockConfig({ devMode: true }); + devConfig.outputTargets = [{ type: STANDALONE }]; + const { config } = validateConfig(devConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === TYPES)).toBe(false); + expect(config.outputTargets.some((o) => o.type === STENCIL_REBUNDLE)).toBe(false); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-stencil-rebundle.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-stencil-rebundle.spec.ts new file mode 100644 index 00000000000..08cd317189d --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-stencil-rebundle.spec.ts @@ -0,0 +1,83 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { isOutputTargetStencilRebundle, join, resolve, STENCIL_REBUNDLE } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateStencilRebundleOutputTarget', () => { + let config: d.Config; + + const rootDir = resolve('/'); + const defaultDir = join(rootDir, 'dist', 'stencil-rebundle'); + + beforeEach(() => { + config = mockConfig(); + }); + + it('sets correct default values', () => { + const target: d.OutputTargetStencilRebundle = { + type: STENCIL_REBUNDLE, + empty: false, + dir: null, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const stencilRebundle = validatedConfig.outputTargets.find(isOutputTargetStencilRebundle); + expect(stencilRebundle).toEqual({ + type: STENCIL_REBUNDLE, + empty: false, + dir: defaultDir, + transformAliasedImportPaths: true, + skipInDev: true, + }); + }); + + it('sets specified directory', () => { + const target: d.OutputTargetStencilRebundle = { + type: STENCIL_REBUNDLE, + empty: false, + dir: '/my-dist', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const stencilRebundle = validatedConfig.outputTargets.find(isOutputTargetStencilRebundle); + expect(stencilRebundle).toEqual({ + type: STENCIL_REBUNDLE, + empty: false, + skipInDev: true, + dir: '/my-dist', + transformAliasedImportPaths: true, + }); + }); + + describe('transformAliasedImportPaths', () => { + it.each([false, true])( + "sets option '%s' when explicitly '%s' in config", + (transformAliasedImportPaths: boolean) => { + const target: d.OutputTargetStencilRebundle = { + type: STENCIL_REBUNDLE, + empty: false, + dir: null, + transformAliasedImportPaths, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const stencilRebundle = validatedConfig.outputTargets.find(isOutputTargetStencilRebundle); + expect(stencilRebundle).toEqual({ + type: STENCIL_REBUNDLE, + empty: false, + dir: defaultDir, + transformAliasedImportPaths, + skipInDev: true, + }); + }, + ); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-types.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-types.spec.ts new file mode 100644 index 00000000000..ba85ea2f211 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-types.spec.ts @@ -0,0 +1,157 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { isOutputTargetTypes, join, resolve, TYPES } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateTypesOutputTarget', () => { + let config: d.Config; + + const rootDir = resolve('/'); + const defaultDir = join(rootDir, 'dist', 'types'); + + beforeEach(() => { + config = mockConfig(); + }); + + describe('defaults', () => { + it('sets correct default directory', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types).toBeDefined(); + expect(types?.dir).toBe(defaultDir); + }); + + it('uses custom directory when specified', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + dir: 'my-types-output', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.dir).toBe(join(rootDir, 'my-types-output')); + }); + + it('defaults empty to true', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.empty).toBe(true); + }); + + it('preserves empty when explicitly set to false', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + empty: false, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.empty).toBe(false); + }); + + it('defaults skipInDev to true', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.skipInDev).toBe(true); + }); + + it('preserves skipInDev when explicitly set to false', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + skipInDev: false, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.skipInDev).toBe(false); + }); + }); + + describe('explicit configuration', () => { + it('auto-generates types in production mode when www is auto-added as default', () => { + // When no output targets are specified, www is auto-added as the default output target. + // In production mode, types and stencil-rebundle are auto-generated alongside www. + config.outputTargets = []; + (config as d.UnvalidatedConfig).devMode = false; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + // Types should be auto-generated because www is added as default + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types).toBeDefined(); + expect(types?.dir).toBe(join(resolve('/'), 'dist', 'types')); + }); + + it('preserves explicit types configuration', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + dir: 'custom-types', + empty: false, + skipInDev: false, + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types).toBeDefined(); + expect(types?.dir).toBe(join(rootDir, 'custom-types')); + expect(types?.empty).toBe(false); + expect(types?.skipInDev).toBe(false); + }); + }); + + describe('absolute paths', () => { + it('handles absolute paths correctly', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + dir: '/absolute/path/to/types', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.dir).toBe('/absolute/path/to/types'); + }); + + it('resolves relative paths from root directory', () => { + const target: d.OutputTargetTypes = { + type: TYPES, + dir: 'relative/types/path', + }; + config.outputTargets = [target]; + + const { config: validatedConfig } = validateConfig(config, mockLoadConfigInit()); + + const types = validatedConfig.outputTargets.find(isOutputTargetTypes); + expect(types?.dir).toBe(join(rootDir, 'relative/types/path')); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-output-www.spec.ts b/packages/core/src/compiler/config/_test_/validate-output-www.spec.ts new file mode 100644 index 00000000000..71b5eb1316e --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-output-www.spec.ts @@ -0,0 +1,513 @@ +import path from 'path'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockLoadConfigInit } from '../../../testing'; +import { + isOutputTargetCopy, + isOutputTargetDistLazy, + isOutputTargetSsr, + isOutputTargetStandalone, + isOutputTargetWww, + join, + LOADER_BUNDLE, + STANDALONE, +} from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validateOutputTargetWww', () => { + // use Node's resolve() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) + const rootDir = path.resolve('/'); + let userConfig: d.Config; + + beforeEach(() => { + userConfig = { + rootDir: rootDir, + }; + }); + + it('should have default value', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + dir: path.join('www', 'docs'), + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + + expect(config.outputTargets).toEqual([ + { + dir: '/dist/types', + empty: true, + skipInDev: true, + type: 'types', + }, + { + dir: '/dist/stencil-rebundle', + empty: true, + skipInDev: true, + transformAliasedImportPaths: true, + type: 'stencil-rebundle', + }, + { + dir: '/dist/assets', + skipInDev: false, + type: 'assets', + }, + { + appDir: join(rootDir, 'www', 'docs'), + baseUrl: '/', + buildDir: join(rootDir, 'www', 'docs', 'build'), + bundleMode: 'loader', + dir: join(rootDir, 'www', 'docs'), + empty: true, + indexHtml: join(rootDir, 'www', 'docs', 'index.html'), + serviceWorker: { + dontCacheBustURLsMatching: /p-\w{8}/, + globDirectory: join(rootDir, 'www', 'docs'), + globIgnores: [ + '**/host.config.json', + '**/*.system.entry.js', + '**/*.system.js', + '**/app.js', + '**/app.css', + ], + globPatterns: ['*.html', '**/*.{js,css,json}'], + swDest: join(rootDir, 'www', 'docs', 'sw.js'), + }, + type: 'www', + }, + { + dir: join(rootDir, 'www', 'docs', 'build'), + esmDir: join(rootDir, 'www', 'docs', 'build'), + isBrowserBuild: true, + type: 'dist-lazy', + }, + { + dir: join(rootDir, 'www', 'docs', 'build'), + type: 'copy', + }, + { + copy: [ + { + src: 'assets', + warn: false, + }, + { + src: 'manifest.json', + warn: false, + }, + ], + dir: join(rootDir, 'www', 'docs'), + type: 'copy', + }, + { + dir: join(rootDir, 'src'), + footer: '*Built with [StencilJS](https://stenciljs.com/)*', + strict: false, + type: 'docs-readme', + skipInDev: true, + }, + ]); + }); + + it('should www with sub directory', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + dir: path.join('www', 'docs'), + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.dir).toBe(join(rootDir, 'www', 'docs')); + expect(www.appDir).toBe(join(rootDir, 'www', 'docs')); + expect(www.buildDir).toBe(join(rootDir, 'www', 'docs', 'build')); + expect(www.indexHtml).toBe(join(rootDir, 'www', 'docs', 'index.html')); + }); + + it('should set www values', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + dir: 'my-www', + buildDir: 'my-build', + indexHtml: 'my-index.htm', + empty: false, + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.type).toBe('www'); + expect(www.dir).toBe(join(rootDir, 'my-www')); + expect(www.buildDir).toBe(join(rootDir, 'my-www', 'my-build')); + expect(www.indexHtml).toBe(join(rootDir, 'my-www', 'my-index.htm')); + expect(www.empty).toBe(false); + }); + + it('should default to add www when outputTargets is undefined', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets).toHaveLength(8); // types + stencil-rebundle (auto-gen in prod) + assets + www + dist-lazy + copy×2 + docs-readme + + const outputTarget = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + expect(outputTarget.dir).toBe(join(rootDir, 'www')); + expect(outputTarget.buildDir).toBe(join(rootDir, 'www', 'build')); + expect(outputTarget.indexHtml).toBe(join(rootDir, 'www', 'index.html')); + expect(outputTarget.empty).toBe(true); + }); + + describe('baseUrl', () => { + it('baseUrl does not end with / with dir set', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + dir: 'my-www', + baseUrl: '/docs', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.type).toBe('www'); + expect(www.dir).toBe(join(rootDir, 'my-www')); + expect(www.baseUrl).toBe('/docs/'); + expect(www.appDir).toBe(join(rootDir, 'my-www/docs')); + + expect(www.buildDir).toBe(join(rootDir, 'my-www', 'docs', 'build')); + expect(www.indexHtml).toBe(join(rootDir, 'my-www', 'docs', 'index.html')); + }); + + it('baseUrl does not end with /', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + baseUrl: '/docs', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.type).toBe('www'); + expect(www.dir).toBe(join(rootDir, 'www')); + expect(www.baseUrl).toBe('/docs/'); + expect(www.appDir).toBe(join(rootDir, 'www/docs')); + + expect(www.buildDir).toBe(join(rootDir, 'www', 'docs', 'build')); + expect(www.indexHtml).toBe(join(rootDir, 'www', 'docs', 'index.html')); + }); + + it('baseUrl is a full url', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + baseUrl: 'https://example.com/docs', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.type).toBe('www'); + expect(www.dir).toBe(join(rootDir, 'www')); + expect(www.baseUrl).toBe('https://example.com/docs/'); + expect(www.appDir).toBe(join(rootDir, 'www/docs')); + + expect(www.buildDir).toBe(join(rootDir, 'www', 'docs', 'build')); + expect(www.indexHtml).toBe(join(rootDir, 'www', 'docs', 'index.html')); + }); + }); + + describe('copy', () => { + it('should add copy tasks', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + dir: path.join('www', 'docs'), + copy: [ + { + src: 'index-modules.html', + dest: 'index-2.html', + }, + ], + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + + const copyTargets = config.outputTargets.filter(isOutputTargetCopy); + expect(copyTargets).toEqual([ + { + dir: join(rootDir, 'www', 'docs', 'build'), + type: 'copy', + }, + { + copy: [ + { + dest: 'index-2.html', + src: 'index-modules.html', + }, + { + src: 'assets', + warn: false, + }, + { + src: 'manifest.json', + warn: false, + }, + ], + dir: join(rootDir, 'www', 'docs'), + type: 'copy', + }, + ]); + }); + + it('should replace copy tasks', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + dir: path.join('www', 'docs'), + copy: [ + { + src: 'assets', + dest: 'assets2', + }, + ], + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + + const copyTargets = config.outputTargets.filter(isOutputTargetCopy); + expect(copyTargets).toEqual([ + { + dir: join(rootDir, 'www', 'docs', 'build'), + type: 'copy', + }, + { + copy: [ + { + dest: 'assets2', + src: 'assets', + }, + { + src: 'manifest.json', + warn: false, + }, + ], + dir: join(rootDir, 'www', 'docs'), + type: 'copy', + }, + ]); + }); + + it('should disable copy tasks', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + dir: path.join('www', 'docs'), + copy: null, + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + + const copyTargets = config.outputTargets.filter(isOutputTargetCopy); + expect(copyTargets).toEqual([ + { + dir: join(rootDir, 'www', 'docs', 'build'), + type: 'copy', + }, + { + copy: [], + dir: join(rootDir, 'www', 'docs'), + type: 'copy', + }, + ]); + }); + }); + + describe('bundleMode', () => { + it('should default to lazy bundleMode', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('loader'); + expect(config.outputTargets.some(isOutputTargetDistLazy)).toBe(true); + expect(config.outputTargets.some(isOutputTargetStandalone)).toBe(false); + }); + + it('should support standalone bundleMode', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + bundleMode: 'standalone', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('standalone'); + expect(config.outputTargets.some(isOutputTargetDistLazy)).toBe(false); + expect(config.outputTargets.some(isOutputTargetStandalone)).toBe(true); + }); + + it('should configure standalone output with autoLoader', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + bundleMode: 'standalone', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const standalone = config.outputTargets.find( + isOutputTargetStandalone, + ) as d.OutputTargetStandalone; + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(standalone).toBeDefined(); + expect(standalone.dir).toBe(www.buildDir); + expect(standalone.autoLoader).toEqual({ + fileName: 'app', // config.fsNamespace defaults to 'app' + autoStart: true, + }); + expect(standalone.externalRuntime).toBe(false); + expect(standalone.skipInDev).toBe(false); + }); + + it('should use custom namespace for standalone autoLoader fileName', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + bundleMode: 'standalone', + }; + userConfig.outputTargets = [outputTarget]; + userConfig.namespace = 'MyComponents'; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const standalone = config.outputTargets.find( + isOutputTargetStandalone, + ) as d.OutputTargetStandalone; + + expect(standalone.autoLoader).toEqual({ + fileName: 'mycomponents', // fsNamespace is lowercase + autoStart: true, + }); + }); + + it('should normalize invalid bundleMode to lazy', () => { + const outputTarget: d.OutputTargetWww = { + type: 'www', + // @ts-expect-error - testing invalid value + bundleMode: 'invalid', + }; + userConfig.outputTargets = [outputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('loader'); + expect(config.outputTargets.some(isOutputTargetDistLazy)).toBe(true); + }); + + it('should auto-detect standalone bundleMode when standalone is configured without loader-bundle', () => { + userConfig.outputTargets = [{ type: 'www' }, { type: STANDALONE }]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('standalone'); + }); + + it('should default to lazy bundleMode when loader-bundle is configured', () => { + userConfig.outputTargets = [{ type: 'www' }, { type: LOADER_BUNDLE }]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('loader'); + }); + + it('should default to lazy bundleMode when both loader-bundle and standalone are configured', () => { + userConfig.outputTargets = [{ type: 'www' }, { type: LOADER_BUNDLE }, { type: STANDALONE }]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('loader'); + }); + + it('should respect explicit bundleMode even when it contradicts auto-detection', () => { + userConfig.outputTargets = [{ type: 'www', bundleMode: 'loader' }, { type: STANDALONE }]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + + expect(www.bundleMode).toBe('loader'); + }); + }); + + describe('ssr', () => { + it('should not add hydrate by default', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === 'ssr')).toBe(false); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should not add hydrate with user www', () => { + const wwwOutputTarget: d.OutputTargetWww = { + type: 'www', + }; + userConfig.outputTargets = [wwwOutputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === 'ssr')).toBe(false); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should add hydrate with user hydrate and www outputs', () => { + const wwwOutputTarget: d.OutputTargetWww = { + type: 'www', + }; + const hydrateOutputTarget: d.OutputTargetSsr = { + type: 'ssr', + }; + userConfig.outputTargets = [wwwOutputTarget, hydrateOutputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === 'ssr')).toBe(true); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should add hydrate with prerender config', () => { + userConfig.prerender = true; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === 'ssr')).toBe(true); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should add hydrate with ssr config', () => { + userConfig.ssr = true; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === 'ssr')).toBe(true); + expect(config.outputTargets.some((o) => o.type === 'www')).toBe(true); + }); + + it('should add externals and defaults', () => { + const hydrateOutputTarget: d.OutputTargetSsr = { + type: 'ssr', + external: ['lodash', 'left-pad'], + }; + userConfig.outputTargets = [hydrateOutputTarget]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const o = config.outputTargets.find(isOutputTargetSsr) as d.OutputTargetSsr; + expect(o.external).toContain('lodash'); + expect(o.external).toContain('left-pad'); + expect(o.external).toContain('fs'); + expect(o.external).toContain('path'); + expect(o.external).toContain('crypto'); + }); + + it('should add node builtins to external by default', () => { + userConfig.prerender = true; + + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const o = config.outputTargets.find(isOutputTargetSsr) as d.OutputTargetSsr; + expect(o.external).toContain('fs'); + expect(o.external).toContain('path'); + expect(o.external).toContain('crypto'); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-package-json.spec.ts b/packages/core/src/compiler/config/_test_/validate-package-json.spec.ts new file mode 100644 index 00000000000..5f2a22f3975 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-package-json.spec.ts @@ -0,0 +1,477 @@ +import { mockBuildCtx, mockCompilerCtx, mockValidatedConfig } from '@stencil/core/testing'; +import { describe, expect, it, beforeEach } from 'vitest'; +import type * as d from '@stencil/core'; + +import { validateBuildPackageJson } from '../validate-package-json'; + +describe('validateBuildPackageJson', () => { + let config: d.ValidatedConfig; + let compilerCtx: d.CompilerCtx; + let buildCtx: d.BuildCtx; + + beforeEach(() => { + config = mockValidatedConfig({ + outputTargets: [], + }); + + compilerCtx = mockCompilerCtx(config); + compilerCtx.fs.accessSync = () => true; + + buildCtx = mockBuildCtx(config, compilerCtx); + buildCtx.packageJson.module = 'dist/loader-bundle/index.js'; + buildCtx.packageJson.types = 'dist/types/index.d.ts'; + buildCtx.packageJson.type = 'module'; + }); + + it('should not validate when no distributable outputs are configured', async () => { + config.outputTargets = []; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(0); + }); + + it('should not validate in watch mode', async () => { + config.watch = true; + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = 'wrong/path.js'; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(0); + }); + + describe('module field', () => { + it('should warn when module path is missing', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + delete buildCtx.packageJson.module; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(1); + expect(buildCtx.diagnostics[0].level).toBe('warn'); + expect(buildCtx.diagnostics[0].messageText).toContain( + 'package.json "module" property is required', + ); + }); + + it('should warn when module path does not exist', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = 'wrong/path/index.js'; + compilerCtx.fs.accessSync = () => false; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(1); + expect(buildCtx.diagnostics[0].level).toBe('warn'); + expect(buildCtx.diagnostics[0].messageText).toContain("doesn't exist"); + expect(buildCtx.diagnostics[0].messageText).toContain('./dist/loader-bundle/index.js'); + }); + + it('should not warn when module path exists but differs from recommended', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = 'custom/path/index.js'; + compilerCtx.fs.accessSync = () => true; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(0); + }); + + it('should not warn when module path matches recommended', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = './dist/loader-bundle/index.js'; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(0); + }); + + it('should recommend standalone paths when only standalone is configured', async () => { + config.outputTargets = [ + { + type: 'standalone', + dir: '/dist/standalone', + }, + ]; + delete buildCtx.packageJson.module; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const moduleWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"module"')); + expect(moduleWarning).toBeDefined(); + expect(moduleWarning!.messageText).toContain('./dist/standalone/index.js'); + }); + + it('should prefer loader-bundle over standalone for module path', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + { + type: 'standalone', + dir: '/dist/standalone', + }, + ]; + delete buildCtx.packageJson.module; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const moduleWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"module"')); + expect(moduleWarning).toBeDefined(); + expect(moduleWarning!.messageText).toContain('./dist/loader-bundle/index.js'); + }); + }); + + describe('types field', () => { + it('should warn when types path is missing', async () => { + config.outputTargets = [ + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ]; + delete buildCtx.packageJson.types; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(1); + expect(buildCtx.diagnostics[0].level).toBe('warn'); + expect(buildCtx.diagnostics[0].messageText).toContain( + 'package.json "types" property is required', + ); + }); + + it('should warn when types path does not end with .d.ts', async () => { + config.outputTargets = [ + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ]; + buildCtx.packageJson.types = 'dist/types/index.ts'; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(1); + expect(buildCtx.diagnostics[0].level).toBe('warn'); + expect(buildCtx.diagnostics[0].messageText).toContain('must have a ".d.ts" extension'); + }); + + it('should error when types file does not exist', async () => { + config.outputTargets = [ + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ]; + buildCtx.packageJson.types = './dist/types/index.d.ts'; + compilerCtx.fs.accessSync = () => false; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(1); + expect(buildCtx.diagnostics[0].level).toBe('error'); + expect(buildCtx.diagnostics[0].messageText).toContain("doesn't exist"); + }); + + it('should not warn when types path exists but differs from recommended', async () => { + config.outputTargets = [ + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ]; + buildCtx.packageJson.types = './dist/types/custom.d.ts'; + compilerCtx.fs.accessSync = () => true; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(0); + }); + + it('should not warn when types path matches recommended', async () => { + config.outputTargets = [ + { + type: 'types', + dir: '/dist/types', + empty: true, + skipInDev: true, + }, + ]; + buildCtx.packageJson.types = './dist/types/index.d.ts'; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + expect(buildCtx.diagnostics.length).toBe(0); + }); + }); + + describe('main field', () => { + it('should warn when main is missing but CJS output is enabled', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: true, + skipInDev: false, + }, + ]; + delete buildCtx.packageJson.main; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const mainWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"main"')); + expect(mainWarning).toBeDefined(); + expect(mainWarning!.level).toBe('warn'); + expect(mainWarning!.messageText).toContain('./dist/loader-bundle/index.cjs'); + }); + + it('should warn when main does not exist (CJS enabled)', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: true, + skipInDev: false, + }, + ]; + buildCtx.packageJson.main = 'wrong/path.js'; + compilerCtx.fs.accessSync = () => false; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const mainWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"main"')); + expect(mainWarning).toBeDefined(); + expect(mainWarning!.level).toBe('warn'); + expect(mainWarning!.messageText).toContain("doesn't exist"); + }); + + it('should not warn when main exists but differs from recommended', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: true, + skipInDev: false, + }, + ]; + buildCtx.packageJson.main = 'custom/path.cjs'; + compilerCtx.fs.accessSync = () => true; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const mainWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"main"')); + expect(mainWarning).toBeUndefined(); + }); + + it('should warn when main does not exist (CJS not enabled)', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.main = 'dist/index.cjs'; + compilerCtx.fs.accessSync = () => false; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const mainWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"main"')); + expect(mainWarning).toBeDefined(); + expect(mainWarning!.level).toBe('warn'); + expect(mainWarning!.messageText).toContain("doesn't exist"); + }); + + it('should not warn about main when CJS is not enabled', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + delete buildCtx.packageJson.main; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const mainWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"main"')); + expect(mainWarning).toBeUndefined(); + }); + }); + + describe('type field', () => { + it('should warn when type is not "module" and only ESM output is configured', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = './dist/loader-bundle/index.js'; + delete buildCtx.packageJson.type; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const typeWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"type"')); + expect(typeWarning).toBeDefined(); + expect(typeWarning!.level).toBe('warn'); + expect(typeWarning!.messageText).toContain('"module"'); + }); + + it('should warn about type when CJS output is enabled but type is not module', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: true, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = './dist/loader-bundle/index.js'; + buildCtx.packageJson.main = './dist/loader-bundle/index.cjs'; + delete buildCtx.packageJson.type; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + // In v5, we always recommend type: "module" - CJS uses .cjs extension which works regardless + const typeWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"type"')); + expect(typeWarning).toBeDefined(); + expect(typeWarning!.level).toBe('warn'); + expect(typeWarning!.messageText).toContain('"module"'); + }); + + it('should not warn about type when type is "module" with CJS output', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: true, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = './dist/loader-bundle/index.js'; + buildCtx.packageJson.main = './dist/loader-bundle/index.cjs'; + buildCtx.packageJson.type = 'module'; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const typeWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"type"')); + expect(typeWarning).toBeUndefined(); + }); + + it('should not warn when type is "module" and only ESM output is configured', async () => { + config.outputTargets = [ + { + type: 'loader-bundle', + dir: '/dist/loader-bundle', + buildDir: '/dist/loader-bundle', + copy: [], + empty: true, + cjs: false, + skipInDev: false, + }, + ]; + buildCtx.packageJson.module = './dist/loader-bundle/index.js'; + buildCtx.packageJson.type = 'module'; + + await validateBuildPackageJson(config, compilerCtx, buildCtx); + + const typeWarning = buildCtx.diagnostics.find((d) => d.messageText.includes('"type"')); + expect(typeWarning).toBeUndefined(); + }); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-paths.spec.ts b/packages/core/src/compiler/config/_test_/validate-paths.spec.ts new file mode 100644 index 00000000000..e88b52574d7 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-paths.spec.ts @@ -0,0 +1,165 @@ +import path from 'path'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockCompilerSystem, mockLoadConfigInit, mockLogger } from '../../../testing'; +import { isOutputTargetWww, join } from '../../../utils'; +import { validateConfig } from '../validate-config'; + +describe('validatePaths', () => { + let userConfig: d.Config; + const logger = mockLogger(); + const sys = mockCompilerSystem(); + + // use Node's resolve() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) + const ROOT = path.resolve('/'); + + beforeEach(() => { + userConfig = { + sys: sys as any, + logger: logger, + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + rootDir: path.join(ROOT, 'User', 'my-app'), + namespace: 'Testing', + }; + }); + + it('should set absolute cacheDir', () => { + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + userConfig.cacheDir = path.join(ROOT, 'some', 'custom', 'cache'); + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.cacheDir).toBe(join(ROOT, 'some', 'custom', 'cache')); + }); + + it('should set relative cacheDir', () => { + userConfig.cacheDir = 'custom-cache'; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.cacheDir).toBe(join(ROOT, 'User', 'my-app', 'custom-cache')); + }); + + it('should set default cacheDir', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.cacheDir).toBe(join(ROOT, 'User', 'my-app', '.stencil')); + }); + + it('should set default wwwIndexHtml and convert to absolute path', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + expect(path.basename(www.indexHtml!)).toBe('index.html'); + expect(path.isAbsolute(www.indexHtml!)).toBe(true); + }); + + it('should convert a custom wwwIndexHtml to absolute path', () => { + userConfig.outputTargets = [ + { + type: 'www', + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + indexHtml: path.join('assets', 'custom-index.html'), + }, + ] as d.OutputTargetWww[]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + expect(path.basename(www.indexHtml!)).toBe('custom-index.html'); + expect(path.isAbsolute(www.indexHtml!)).toBe(true); + }); + + it('should set default indexHtmlSrc and convert to absolute path', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(path.basename(config.srcIndexHtml)).toBe('index.html'); + expect(path.isAbsolute(config.srcIndexHtml)).toBe(true); + }); + + it('should set emptyDist to false', () => { + userConfig.outputTargets = [ + { + type: 'www', + empty: false, + }, + ] as d.OutputTargetWww[]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + expect(www.empty).toBe(false); + }); + + it('should set default emptyWWW to true', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + expect(www.empty).toBe(true); + }); + + it('should set emptyWWW to false', () => { + userConfig.outputTargets = [ + { + type: 'www', + empty: false, + }, + ] as d.OutputTargetWww[]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + expect(www.empty).toBe(false); + }); + + // v5: collectionDir and typesDir are now separate output targets (stencil-rebundle and types) + // These properties no longer exist on loader-bundle output target + + it('should set default build dir and convert to absolute path', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + // the path will be normalized by Stencil us use '/', split on that regardless of platform + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + const parts = www.buildDir!.split('/'); + expect(parts[parts.length - 1]).toBe('build'); + expect(parts[parts.length - 2]).toBe('www'); + expect(path.isAbsolute(www.buildDir!)).toBe(true); + }); + + it('should set build dir w/ custom www', () => { + userConfig.outputTargets = [ + { + type: 'www', + dir: 'custom-www', + }, + ] as d.OutputTargetWww[]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + // the path will be normalized by Stencil us use '/', split on that regardless of platform + const www = config.outputTargets.find(isOutputTargetWww) as d.OutputTargetWww; + const parts = www.buildDir!.split('/'); + expect(parts[parts.length - 1]).toBe('build'); + expect(parts[parts.length - 2]).toBe('custom-www'); + expect(path.isAbsolute(www.buildDir!)).toBe(true); + }); + + it('should set default src dir and convert to absolute path', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(path.basename(config.srcDir)).toBe('src'); + expect(path.isAbsolute(config.srcDir)).toBe(true); + }); + + it('should set src dir and convert to absolute path', () => { + userConfig.srcDir = 'app'; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(path.basename(config.srcDir)).toBe('app'); + expect(path.isAbsolute(config.srcDir)).toBe(true); + }); + + it('should convert globalScript to absolute path, if a globalScript property was provided', () => { + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + userConfig.globalScript = path.join('src', 'global', 'index.ts'); + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(path.basename(config.globalScript!)).toBe('index.ts'); + expect(path.isAbsolute(config.globalScript!)).toBe(true); + }); + + it('should convert globalStyle string to absolute path array, if a globalStyle property was provided', () => { + // use Node's join() here to simulate a user using either Win/Posix separators (depending on the platform these + // tests are run on) for their input + userConfig.globalStyle = path.join('src', 'global', 'styles.css'); + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(path.basename(config.globalStyle!)).toBe('styles.css'); + expect(path.isAbsolute(config.globalStyle!)).toBe(true); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-rolldown-config.spec.ts b/packages/core/src/compiler/config/_test_/validate-rolldown-config.spec.ts new file mode 100644 index 00000000000..c7437a7d8d1 --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-rolldown-config.spec.ts @@ -0,0 +1,83 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { validateRolldownConfig } from '../validate-rolldown-config'; + +describe('validateStats', () => { + let config: d.Config; + + beforeEach(() => { + config = {}; + }); + + it('should use default if no config provided', () => { + const rolldownConfig = validateRolldownConfig(config); + expect(rolldownConfig).toEqual({ + inputOptions: {}, + outputOptions: {}, + }); + }); + + it('should set based on inputOptions if provided', () => { + config.rolldownConfig = { + inputOptions: { + context: 'window', + }, + }; + const rolldownConfig = validateRolldownConfig(config); + expect(rolldownConfig).toEqual({ + inputOptions: { + context: 'window', + }, + outputOptions: {}, + }); + }); + + it('should use default if inputOptions is not provided but outputOptions is', () => { + config.rolldownConfig = { + outputOptions: { + globals: { + jquery: '$', + }, + }, + }; + + const rolldownConfig = validateRolldownConfig(config); + expect(rolldownConfig).toEqual({ + inputOptions: {}, + outputOptions: { + globals: { + jquery: '$', + }, + }, + }); + }); + + it('should pass all valid config data through and not those that are extraneous', () => { + config.rolldownConfig = { + inputOptions: { + context: 'window', + external: 'external_symbol', + notAnOption: {}, + }, + outputOptions: { + globals: { + jquery: '$', + }, + }, + } as d.RolldownConfig; + + const rolldownConfig = validateRolldownConfig(config); + expect(rolldownConfig).toEqual({ + inputOptions: { + context: 'window', + external: 'external_symbol', + }, + outputOptions: { + globals: { + jquery: '$', + }, + }, + }); + }); +}); diff --git a/src/compiler/config/test/validate-service-worker.spec.ts b/packages/core/src/compiler/config/_test_/validate-service-worker.spec.ts similarity index 91% rename from src/compiler/config/test/validate-service-worker.spec.ts rename to packages/core/src/compiler/config/_test_/validate-service-worker.spec.ts index 66de1a68bcb..ac7914a4c82 100644 --- a/src/compiler/config/test/validate-service-worker.spec.ts +++ b/packages/core/src/compiler/config/_test_/validate-service-worker.spec.ts @@ -1,8 +1,8 @@ -import type * as d from '@stencil/core/declarations'; -import { OutputTargetWww } from '@stencil/core/declarations'; -import { mockCompilerSystem, mockLogger, mockValidatedConfig } from '@stencil/core/testing'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; +import type { OutputTargetWww } from '@stencil/core'; -import { createConfigFlags } from '../../../cli/config-flags'; +import { mockCompilerSystem, mockLogger, mockValidatedConfig } from '../../../testing'; import { validateServiceWorker } from '../validate-service-worker'; describe('validateServiceWorker', () => { @@ -13,7 +13,6 @@ describe('validateServiceWorker', () => { beforeEach(() => { config = mockValidatedConfig({ devMode: false, - flags: createConfigFlags(), fsNamespace: 'app', hydratedFlag: null, logger: mockLogger(), @@ -21,7 +20,6 @@ describe('validateServiceWorker', () => { packageJsonFilePath: '/package.json', rootDir: '/', sys: mockCompilerSystem(), - testing: {}, transformAliasedImportPaths: true, }); }); @@ -41,7 +39,9 @@ describe('validateServiceWorker', () => { if (target.serviceWorker) { return target.serviceWorker; } else { - throw new Error('the serviceWorker on the provided target was unexpectedly falsy, so this test needs to fail!'); + throw new Error( + 'the serviceWorker on the provided target was unexpectedly falsy, so this test needs to fail!', + ); } } @@ -161,14 +161,14 @@ describe('validateServiceWorker', () => { expect(outputTarget.serviceWorker).toBe(null); }); - it('should create sw config when in devMode if flag serviceWorker', () => { + it('should create sw config when in devMode if generateServiceWorker is true', () => { outputTarget = { type: 'www', appDir: '/www', serviceWorker: true as any, }; config.devMode = true; - config.flags.serviceWorker = true; + config.generateServiceWorker = true; validateServiceWorker(config, outputTarget); expect(outputTarget.serviceWorker).not.toBe(null); }); diff --git a/packages/core/src/compiler/config/_test_/validate-stats.spec.ts b/packages/core/src/compiler/config/_test_/validate-stats.spec.ts new file mode 100644 index 00000000000..790bb3f98db --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-stats.spec.ts @@ -0,0 +1,43 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockConfig, mockLoadConfigInit } from '../../../testing'; +import { validateConfig } from '../validate-config'; + +describe('validateStats', () => { + let userConfig: d.Config; + + beforeEach(() => { + userConfig = mockConfig(); + }); + + it('uses stats config, custom path', () => { + userConfig.outputTargets = [ + { + type: 'stats', + file: 'custom-path.json', + } as d.OutputTargetStats, + ]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const o = config.outputTargets.find((o) => o.type === 'stats') as d.OutputTargetStats; + expect(o).toBeDefined(); + expect(o.file).toContain('custom-path.json'); + }); + + it('uses stats config, defaults file', () => { + userConfig.outputTargets = [ + { + type: 'stats', + }, + ]; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + const o = config.outputTargets.find((o) => o.type === 'stats') as d.OutputTargetStats; + expect(o).toBeDefined(); + expect(o.file).toContain('stencil-stats.json'); + }); + + it('default no stats', () => { + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.outputTargets.some((o) => o.type === 'stats')).toBe(false); + }); +}); diff --git a/packages/core/src/compiler/config/_test_/validate-workers.spec.ts b/packages/core/src/compiler/config/_test_/validate-workers.spec.ts new file mode 100644 index 00000000000..011626acb0d --- /dev/null +++ b/packages/core/src/compiler/config/_test_/validate-workers.spec.ts @@ -0,0 +1,41 @@ +import path from 'path'; +import { beforeEach, describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { mockLoadConfigInit, mockLogger } from '../../../testing'; +import { validateConfig } from '../validate-config'; + +describe('validate-workers', () => { + let userConfig: d.Config; + const logger = mockLogger(); + + beforeEach(() => { + userConfig = { + sys: { + path: path, + } as any, + logger: logger, + rootDir: '/', + namespace: 'Testing', + }; + }); + + it('set maxConcurrentWorkers, but dont let it go under 0', () => { + userConfig.maxConcurrentWorkers = -1; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.maxConcurrentWorkers).toBe(0); + }); + + it('limits maxConcurrentWorkers in CI mode', () => { + userConfig.ci = true; + userConfig.maxConcurrentWorkers = 8; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.maxConcurrentWorkers).toBe(4); + }); + + it('set maxConcurrentWorkers', () => { + userConfig.maxConcurrentWorkers = 4; + const { config } = validateConfig(userConfig, mockLoadConfigInit()); + expect(config.maxConcurrentWorkers).toBe(4); + }); +}); diff --git a/packages/core/src/compiler/config/config-utils.ts b/packages/core/src/compiler/config/config-utils.ts new file mode 100644 index 00000000000..73abce682c6 --- /dev/null +++ b/packages/core/src/compiler/config/config-utils.ts @@ -0,0 +1,70 @@ +import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; + +import { isBoolean, join } from '../../utils'; + +export const getAbsolutePath = (config: d.ValidatedConfig, dir: string) => { + if (!isAbsolute(dir)) { + dir = join(config.rootDir, dir); + } + return dir; +}; + +/** + * Set a boolean configuration value with a default. + * + * If the config already has a value for `configName`, use it. + * Otherwise, set it to `defaultValue`. + * + * Note: CLI flags are now merged into config before validation, + * so this function no longer needs to know about flags. + * + * @param config the config that we want to update + * @param configName the key we're setting on the config + * @param defaultValue the default value we should set! + */ +export const setBooleanConfig = ( + config: d.UnvalidatedConfig, + configName: K, + defaultValue: d.Config[K], +) => { + const userConfigName = getUserConfigName(config, configName); + + if (typeof config[userConfigName] === 'function') { + config[userConfigName] = !!config[userConfigName](); + } + + if (isBoolean(config[userConfigName])) { + (config[configName] as boolean) = config[userConfigName]; + } else { + config[configName] = defaultValue; + } +}; + +/** + * Find any possibly mis-capitalized configuration names on the config, logging + * and warning if one is found. + * + * @param config the user-supplied config that we're dealing with + * @param correctConfigName the configuration name that we're checking for right now + * @returns a string container a mis-capitalized config name found on the + * config object, if any. + */ +const getUserConfigName = ( + config: d.UnvalidatedConfig, + correctConfigName: keyof d.Config, +): string => { + const userConfigNames = Object.keys(config); + + for (const userConfigName of userConfigNames) { + if (userConfigName.toLowerCase() === correctConfigName.toLowerCase()) { + if (userConfigName !== correctConfigName) { + config.logger?.warn(`config "${userConfigName}" should be "${correctConfigName}"`); + return userConfigName; + } + break; + } + } + + return correctConfigName; +}; diff --git a/packages/core/src/compiler/config/constants.ts b/packages/core/src/compiler/config/constants.ts new file mode 100644 index 00000000000..11951fd59df --- /dev/null +++ b/packages/core/src/compiler/config/constants.ts @@ -0,0 +1,13 @@ +import type * as d from '@stencil/core'; + +type DefaultTargetComponentConfig = d.Config['docs']['markdown']['targetComponent']; + +export const DEFAULT_DEV_MODE = false; +export const DEFAULT_HASHED_FILENAME_LENGTH = 8; +export const MIN_HASHED_FILENAME_LENGTH = 4; +export const MAX_HASHED_FILENAME_LENGTH = 32; +export const DEFAULT_NAMESPACE = 'App'; +export const DEFAULT_TARGET_COMPONENT_STYLES: DefaultTargetComponentConfig = { + background: '#f9f', + textColor: '#333', +}; diff --git a/src/compiler/config/load-config.ts b/packages/core/src/compiler/config/load-config.ts similarity index 93% rename from src/compiler/config/load-config.ts rename to packages/core/src/compiler/config/load-config.ts index b69cff10ea3..a2dddc33452 100644 --- a/src/compiler/config/load-config.ts +++ b/packages/core/src/compiler/config/load-config.ts @@ -1,8 +1,13 @@ -import { createNodeSys } from '@sys-api-node'; -import { buildError, catchError, hasError, isString, normalizePath } from '@utils'; import { dirname } from 'path'; - -import type { Diagnostic, LoadConfigInit, LoadConfigResults, UnvalidatedConfig } from '../../declarations'; +import type { + Diagnostic, + LoadConfigInit, + LoadConfigResults, + UnvalidatedConfig, +} from '@stencil/core'; + +import { createNodeSys } from '../../sys/node'; +import { buildError, catchError, hasError, isString, normalizePath } from '../../utils'; import { nodeRequire } from '../sys/node-require'; import { validateTsConfig } from '../sys/typescript/typescript-config'; import { validateConfig } from './validate-config'; @@ -85,7 +90,9 @@ export const loadConfig = async (init: LoadConfigInit = {}): Promise => { +const loadConfigFile = async ( + diagnostics: Diagnostic[], + configPath: string, +): Promise => { let config: UnvalidatedConfig | null = null; if (isString(configPath)) { diff --git a/packages/core/src/compiler/config/outputs/index.ts b/packages/core/src/compiler/config/outputs/index.ts new file mode 100644 index 00000000000..e3ff83b47a1 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/index.ts @@ -0,0 +1,152 @@ +import type * as d from '@stencil/core'; + +import { + ASSETS, + buildError, + buildWarn, + DIST_LAZY, + GLOBAL_STYLE, + isOutputTargetAssets, + isOutputTargetGlobalStyle, + isOutputTargetStencilRebundle, + isOutputTargetTypes, + isString, + isValidConfigOutputTarget, + STENCIL_REBUNDLE, + TYPES, + VALID_CONFIG_OUTPUT_TARGETS, +} from '../../../utils'; + +/** + * Internal output target types that are programmatically generated by validators. + * These should NOT be reprocessed if validation is called multiple times. + */ +const INTERNAL_OUTPUT_TARGETS = [DIST_LAZY] as const; +import { validateAssets } from './validate-assets'; +import { validateCustomOutput } from './validate-custom-output'; +import { validateDocs } from './validate-docs'; +import { validateGlobalStyle } from './validate-global-style'; +import { validateLazy } from './validate-lazy'; +import { validateLoaderBundle } from './validate-loader-bundle'; +import { validateSsr } from './validate-ssr'; +import { validateStandalone } from './validate-standalone'; +import { validateStats } from './validate-stats'; +import { validateStencilRebundle } from './validate-stencil-rebundle'; +import { validateTypes } from './validate-types'; +import { validateWww } from './validate-www'; + +export const validateOutputTargets = (config: d.ValidatedConfig, diagnostics: d.Diagnostic[]) => { + // Filter out internal output targets that may have been added in a previous validation pass. + // This handles the case where validateConfig is called multiple times on the same config object. + const userOutputs = (config.outputTargets || []) + .filter( + (o) => !INTERNAL_OUTPUT_TARGETS.includes(o.type as (typeof INTERNAL_OUTPUT_TARGETS)[number]), + ) + .slice(); + + userOutputs.forEach((outputTarget) => { + if (!isValidConfigOutputTarget(outputTarget.type)) { + const err = buildError(diagnostics); + err.messageText = `Invalid outputTarget type "${ + outputTarget.type + }". Valid outputTarget types include: ${VALID_CONFIG_OUTPUT_TARGETS.map((t) => `"${t}"`).join(', ')}`; + } + }); + + // Auto-generate types and stencil-rebundle in production builds unless explicitly configured + autoGenerateOutputs(config, userOutputs, diagnostics); + + config.outputTargets = [ + ...validateTypes(config, userOutputs), + ...validateStencilRebundle(config, userOutputs), + ...validateGlobalStyle(config, userOutputs), + ...validateAssets(config, userOutputs), + ...validateStandalone(config, userOutputs), + ...validateCustomOutput(config, diagnostics, userOutputs), + ...validateLazy(config, userOutputs), + ...validateWww(config, diagnostics, userOutputs), + ...validateLoaderBundle(config, userOutputs), + ...validateDocs(config, diagnostics, userOutputs), + ...validateStats(config, userOutputs), + ]; + + // SSR also gets info from the www output + config.outputTargets = [ + ...config.outputTargets, + ...validateSsr(config, [...userOutputs, ...config.outputTargets]), + ]; +}; + +/** + * Auto-generate output targets unless explicitly configured. + * + * Output targets that are auto-generated by default to ensure parity between all distribution strategies: + * - `types` and `stencil-rebundle`: auto-generated in production builds + * - `global-style`: auto-generated when `globalStyle` is configured AND no explicit outputs with `input` (dev and prod) + * - `assets`: always auto-generated (dev and prod) - the actual copy is a no-op if no assetsDirs + * + * @param config The Stencil configuration object + * @param userOutputs The array of user-defined output targets to validate and potentially augment + * @param diagnostics The diagnostics array for warnings/errors + */ +const autoGenerateOutputs = ( + config: d.ValidatedConfig, + userOutputs: d.OutputTarget[], + diagnostics: d.Diagnostic[], +) => { + const hasExplicitTypes = userOutputs.some(isOutputTargetTypes); + const hasExplicitStencilRebundle = userOutputs.some(isOutputTargetStencilRebundle); + const hasExplicitAssets = userOutputs.some(isOutputTargetAssets); + + // Check for explicit global-style outputs + const explicitGlobalStyles = userOutputs.filter(isOutputTargetGlobalStyle); + const hasExplicitGlobalStyleWithInput = explicitGlobalStyles.some((o) => + isString((o as d.OutputTargetGlobalStyle).input), + ); + + // Warn if both globalStyle config AND explicit global-style with input are used + if (config.globalStyle && hasExplicitGlobalStyleWithInput) { + const warn = buildWarn(diagnostics); + warn.messageText = + `Both "globalStyle" config and explicit "global-style" output target with "input" are configured. ` + + `Choose one approach. The explicit output target's "input" will be used; "globalStyle" config will be ignored ` + + `for those outputs.`; + } + + // Auto-generate global-style output if: + // - globalStyle is configured in config + // - AND no explicit global-style outputs exist (user hasn't taken control) + // This runs in both dev and prod since global styles are needed for development + if (config.globalStyle && explicitGlobalStyles.length === 0) { + userOutputs.push({ + type: GLOBAL_STYLE, + } as d.OutputTargetGlobalStyle); + } + + // Auto-generate assets output if not explicitly configured + // This runs in both dev and prod - the actual copy is a no-op if no components have assetsDirs + if (!hasExplicitAssets) { + userOutputs.push({ + type: ASSETS, + } as d.OutputTargetAssets); + } + + // Skip types and stencil-rebundle auto-generation in dev mode + if (config.devMode) { + return; + } + + // Auto-generate types output if not explicitly configured + if (!hasExplicitTypes) { + userOutputs.push({ + type: TYPES, + } as d.OutputTargetTypes); + } + + // Auto-generate stencil-rebundle output if not explicitly configured + if (!hasExplicitStencilRebundle) { + userOutputs.push({ + type: STENCIL_REBUNDLE, + } as d.OutputTargetStencilRebundle); + } +}; diff --git a/packages/core/src/compiler/config/outputs/validate-assets.ts b/packages/core/src/compiler/config/outputs/validate-assets.ts new file mode 100644 index 00000000000..97353334d20 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-assets.ts @@ -0,0 +1,29 @@ +import type * as d from '@stencil/core'; + +import { isBoolean, isOutputTargetAssets } from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; + +/** + * Validate and return assets output targets. + * + * The assets output copies all component `assetsDirs` to a unified location + * that all distribution strategies can access via runtime `getAssetPath()` resolution. + * + * auto-generated when components have `assetsDirs` unless explicitly configured. + * + * @param config a validated configuration object + * @param userOutputs an array of output targets + * @returns an array of validated assets output targets + */ +export const validateAssets = ( + config: d.ValidatedConfig, + userOutputs: d.OutputTarget[], +): d.OutputTargetAssets[] => { + return userOutputs.filter(isOutputTargetAssets).map((outputTarget) => { + return { + ...outputTarget, + dir: getAbsolutePath(config, outputTarget.dir ?? 'dist/assets'), + skipInDev: isBoolean(outputTarget.skipInDev) ? outputTarget.skipInDev : false, + }; + }); +}; diff --git a/packages/core/src/compiler/config/outputs/validate-custom-output.ts b/packages/core/src/compiler/config/outputs/validate-custom-output.ts new file mode 100644 index 00000000000..7f69ae2e36e --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-custom-output.ts @@ -0,0 +1,34 @@ +import type * as d from '@stencil/core'; + +import { catchError, COPY, isBoolean, isOutputTargetCustom } from '../../../utils'; + +export const validateCustomOutput = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], + userOutputs: d.OutputTarget[], +) => { + return userOutputs.filter(isOutputTargetCustom).map((o) => { + // Custom outputs skip in dev by default (framework wrappers etc.) + if (!isBoolean(o.skipInDev)) { + o.skipInDev = true; + } + + if (o.validate) { + const localDiagnostics: d.Diagnostic[] = []; + try { + o.validate(config, diagnostics); + } catch (e: any) { + catchError(localDiagnostics, e); + } + if (o.copy && o.copy.length > 0) { + config.outputTargets.push({ + type: COPY, + dir: config.rootDir, + copy: [...o.copy], + }); + } + diagnostics.push(...localDiagnostics); + } + return o; + }); +}; diff --git a/packages/core/src/compiler/config/outputs/validate-docs.ts b/packages/core/src/compiler/config/outputs/validate-docs.ts new file mode 100644 index 00000000000..3da7e156754 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-docs.ts @@ -0,0 +1,180 @@ +import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; + +import { + buildError, + DOCS_JSON, + DOCS_README, + isBoolean, + isFunction, + isOutputTargetDocsCustom, + isOutputTargetDocsCustomElementsManifest, + isOutputTargetDocsJson, + isOutputTargetDocsReadme, + isOutputTargetDocsVscode, + isString, + join, +} from '../../../utils'; +import { NOTE } from '../../docs/constants'; + +export const validateDocs = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], + userOutputs: d.OutputTarget[], +) => { + const docsOutputs: d.OutputTarget[] = []; + + // json docs from --docsJson flag (set via config.docsJsonPath) + if (isString(config.docsJsonPath)) { + docsOutputs.push( + validateJsonDocsOutputTarget(config, diagnostics, { + type: DOCS_JSON, + file: config.docsJsonPath, + }), + ); + } + + // json docs + const jsonDocsOutputs = userOutputs.filter(isOutputTargetDocsJson); + jsonDocsOutputs.forEach((jsonDocsOutput) => { + docsOutputs.push(validateJsonDocsOutputTarget(config, diagnostics, jsonDocsOutput)); + }); + + // Auto-add docs-readme in production mode, or when --docs flag is used + // (In dev mode without --docs flag, user must explicitly configure docs-readme) + if (!config.devMode || config._docsFlag) { + if (!userOutputs.some(isOutputTargetDocsReadme)) { + // didn't provide a docs config, so let's add one + docsOutputs.push(validateReadmeOutputTarget(config, { type: DOCS_README })); + } + } + + // readme docs + const readmeDocsOutputs = userOutputs.filter(isOutputTargetDocsReadme); + readmeDocsOutputs.forEach((readmeDocsOutput) => { + docsOutputs.push(validateReadmeOutputTarget(config, readmeDocsOutput)); + }); + + // custom docs + const customDocsOutputs = userOutputs.filter(isOutputTargetDocsCustom); + customDocsOutputs.forEach((jsonDocsOutput) => { + docsOutputs.push(validateCustomDocsOutputTarget(config, diagnostics, jsonDocsOutput)); + }); + + // vscode docs + const vscodeDocsOutputs = userOutputs.filter(isOutputTargetDocsVscode); + vscodeDocsOutputs.forEach((vscodeDocsOutput) => { + docsOutputs.push(validateVScodeDocsOutputTarget(config, diagnostics, vscodeDocsOutput)); + }); + + // custom elements manifest docs + const customElementsManifestOutputs = userOutputs.filter( + isOutputTargetDocsCustomElementsManifest, + ); + customElementsManifestOutputs.forEach((cemOutput) => { + docsOutputs.push(validateCustomElementsManifestOutputTarget(config, cemOutput)); + }); + + return docsOutputs; +}; + +const validateReadmeOutputTarget = ( + config: d.ValidatedConfig, + outputTarget: d.OutputTargetDocsReadme, +) => { + if (!isString(outputTarget.dir)) { + outputTarget.dir = config.srcDir; + } + + if (!isAbsolute(outputTarget.dir)) { + outputTarget.dir = join(config.rootDir, outputTarget.dir); + } + + if (outputTarget.footer == null) { + outputTarget.footer = NOTE; + } + outputTarget.strict = !!outputTarget.strict; + // docs targets skip in dev by default, unless --docs flag was used + if (!isBoolean(outputTarget.skipInDev)) { + outputTarget.skipInDev = !config._docsFlag; + } + return outputTarget; +}; + +const validateJsonDocsOutputTarget = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], + outputTarget: d.OutputTargetDocsJson, +) => { + if (!isString(outputTarget.file)) { + const err = buildError(diagnostics); + err.messageText = `docs-json outputTarget missing the "file" option`; + } + + if (!isAbsolute(outputTarget.file)) { + outputTarget.file = join(config.rootDir, outputTarget.file); + } + if (isString(outputTarget.typesFile) && !isAbsolute(outputTarget.typesFile)) { + outputTarget.typesFile = join(config.rootDir, outputTarget.typesFile); + } else if (outputTarget.typesFile !== null && outputTarget.file.endsWith('.json')) { + outputTarget.typesFile = outputTarget.file.replace(/\.json$/, '.d.ts'); + } + outputTarget.strict = !!outputTarget.strict; + // docs targets skip in dev by default, unless --docs flag was used + if (!isBoolean(outputTarget.skipInDev)) { + outputTarget.skipInDev = !config._docsFlag; + } + return outputTarget; +}; + +const validateCustomDocsOutputTarget = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], + outputTarget: d.OutputTargetDocsCustom, +) => { + if (!isFunction(outputTarget.generator)) { + const err = buildError(diagnostics); + err.messageText = `docs-custom outputTarget missing the "generator" function`; + } + + outputTarget.strict = !!outputTarget.strict; + // docs targets skip in dev by default, unless --docs flag was used + if (!isBoolean(outputTarget.skipInDev)) { + outputTarget.skipInDev = !config._docsFlag; + } + return outputTarget; +}; + +const validateVScodeDocsOutputTarget = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], + outputTarget: d.OutputTargetDocsVscode, +) => { + if (!isString(outputTarget.file)) { + const err = buildError(diagnostics); + err.messageText = `docs-vscode outputTarget missing the "file" path`; + } + // docs targets skip in dev by default, unless --docs flag was used + if (!isBoolean(outputTarget.skipInDev)) { + outputTarget.skipInDev = !config._docsFlag; + } + return outputTarget; +}; + +const validateCustomElementsManifestOutputTarget = ( + config: d.ValidatedConfig, + outputTarget: d.OutputTargetDocsCustomElementsManifest, +) => { + if (!isString(outputTarget.file)) { + outputTarget.file = 'custom-elements.json'; + } + if (!isAbsolute(outputTarget.file)) { + outputTarget.file = join(config.rootDir, outputTarget.file); + } + outputTarget.strict = !!outputTarget.strict; + // docs targets skip in dev by default, unless --docs flag was used + if (!isBoolean(outputTarget.skipInDev)) { + outputTarget.skipInDev = !config._docsFlag; + } + return outputTarget; +}; diff --git a/packages/core/src/compiler/config/outputs/validate-global-style.ts b/packages/core/src/compiler/config/outputs/validate-global-style.ts new file mode 100644 index 00000000000..2e41f23002a --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-global-style.ts @@ -0,0 +1,73 @@ +import { basename } from 'node:path'; +import type * as d from '@stencil/core'; + +import { isBoolean, isOutputTargetGlobalStyle, isString } from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; + +/** + * Validate and return global-style output targets. + * + * The global-style output generates a CSS file from an input stylesheet. + * Can use either: + * - Explicit `input` on the output target + * - Implicit `globalStyle` from config (for backwards compat) + * + * Multiple global-style outputs are supported for building separate CSS bundles. + * + * @param config a validated configuration object + * @param userOutputs an array of output targets + * @returns an array of validated global-style output targets + */ +export const validateGlobalStyle = ( + config: d.ValidatedConfig, + userOutputs: d.OutputTarget[], +): d.OutputTargetGlobalStyle[] => { + return userOutputs.filter(isOutputTargetGlobalStyle).map((outputTarget) => { + // Resolve input path - explicit input takes precedence over globalStyle config + const input = isString(outputTarget.input) + ? getAbsolutePath(config, outputTarget.input) + : (config.globalStyle ?? undefined); + + // Determine output filename + // Priority: explicit fileName > basename of input > {namespace}.css + let fileName: string; + if (isString(outputTarget.fileName)) { + fileName = outputTarget.fileName; + } else if (input) { + // Use input basename, but only if it's from explicit input (not globalStyle) + // For globalStyle compat, always use {namespace}.css + if (isString(outputTarget.input)) { + fileName = basename(input); + } else { + fileName = `${config.fsNamespace}.css`; + } + } else { + fileName = `${config.fsNamespace}.css`; + } + + // Validate inject option - must be 'none', 'client', or 'all' + // Default depends on how the output target was configured: + // - Auto-generated from globalStyle config (no explicit input) → 'client' (preserves v4 behavior) + // - Explicitly configured with input → 'none' (new v5 default for explicit configs) + const validInjectValues = ['none', 'client', 'all'] as const; + const isUsingGlobalStyleConfig = !isString(outputTarget.input) && !!config.globalStyle; + const defaultInject = isUsingGlobalStyleConfig ? 'client' : 'none'; + const inject = validInjectValues.includes( + outputTarget.inject as (typeof validInjectValues)[number], + ) + ? (outputTarget.inject as 'none' | 'client' | 'all') + : defaultInject; + + return { + ...outputTarget, + input, + fileName, + dir: getAbsolutePath(config, outputTarget.dir ?? 'dist/assets'), + copyToLoaderBrowser: isBoolean(outputTarget.copyToLoaderBrowser) + ? outputTarget.copyToLoaderBrowser + : true, + skipInDev: isBoolean(outputTarget.skipInDev) ? outputTarget.skipInDev : false, + inject, + }; + }); +}; diff --git a/packages/core/src/compiler/config/outputs/validate-lazy.ts b/packages/core/src/compiler/config/outputs/validate-lazy.ts new file mode 100644 index 00000000000..fe731ebbc6e --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-lazy.ts @@ -0,0 +1,17 @@ +import type * as d from '@stencil/core'; + +import { DIST_LAZY, isBoolean, isOutputTargetDistLazy, join } from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; + +export const validateLazy = (config: d.ValidatedConfig, userOutputs: d.OutputTarget[]) => { + return userOutputs.filter(isOutputTargetDistLazy).map((o) => { + const dir = getAbsolutePath(config, o.dir || join('dist', config.fsNamespace)); + const lazyOutput: d.OutputTargetDistLazy = { + type: DIST_LAZY, + esmDir: dir, + isBrowserBuild: true, + empty: isBoolean(o.empty) ? o.empty : true, + }; + return lazyOutput; + }); +}; diff --git a/packages/core/src/compiler/config/outputs/validate-loader-bundle.ts b/packages/core/src/compiler/config/outputs/validate-loader-bundle.ts new file mode 100644 index 00000000000..c3d24fb7045 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-loader-bundle.ts @@ -0,0 +1,126 @@ +import { isAbsolute } from 'node:path'; +import type * as d from '@stencil/core'; + +import { + COPY, + DIST_LAZY, + isBoolean, + isOutputTargetLoaderBundle, + isOutputTargetTypes, + isString, + join, +} from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; +import { validateCopy } from '../validate-copy'; + +/** + * Validate the "loader-bundle" output targets. + * + * This generates lazy-loaded component bundles with a loader infrastructure, + * optimized for CDN usage and applications that benefit from on-demand component loading. + * + * This function will also add internal output targets (lazy, global-styles, copy) + * to support the loader-bundle implementation. + * + * Note: In v5, `types` and `stencil-rebundle` outputs are auto-generated separately in production builds. + * The `./loader` export in package.json points directly to the esm/loader.js file. + * + * @param config the compiler config + * @param userOutputs a user-supplied list of output targets + * @returns a list of OutputTargets which have been validated + */ +export const validateLoaderBundle = ( + config: d.ValidatedConfig, + userOutputs: d.OutputTarget[], +): d.OutputTarget[] => { + const loaderBundleOutputTargets = userOutputs.filter(isOutputTargetLoaderBundle); + + const outputs: d.OutputTarget[] = []; + + for (const outputTarget of loaderBundleOutputTargets) { + const loaderBundleOutput = validateOutputTargetLoaderBundle(config, outputTarget); + outputs.push(loaderBundleOutput); + + const namespace = config.fsNamespace || 'app'; + const lazyDir = join(loaderBundleOutput.buildDir, namespace); + + // Lazy build for CDN (always generated, even in dev mode) + outputs.push({ + type: DIST_LAZY, + esmDir: lazyDir, + isBrowserBuild: true, + empty: loaderBundleOutput.empty, + }); + outputs.push({ + type: COPY, + dir: lazyDir, + copy: (loaderBundleOutput.copy ?? []).concat(), + }); + + // Distribution outputs (lazy bundles) + // Only generated in production mode or when skipInDev=false + if (!config.devMode || !loaderBundleOutput.skipInDev) { + const esmDir = join(loaderBundleOutput.dir, 'esm'); + const cjsDir = loaderBundleOutput.cjs ? join(loaderBundleOutput.dir, 'cjs') : undefined; + + // Find the types output target to get the types directory + const typesOutput = userOutputs.find(isOutputTargetTypes); + const typesDir = getAbsolutePath(config, typesOutput?.dir ?? 'dist/types'); + + // Create lazy output target for distributable bundles + outputs.push({ + type: DIST_LAZY, + esmDir, + cjsDir, + cjsIndexFile: loaderBundleOutput.cjs + ? join(loaderBundleOutput.dir, 'index.cjs') + : undefined, + esmIndexFile: join(loaderBundleOutput.dir, 'index.js'), + loaderDir: join(loaderBundleOutput.dir, loaderBundleOutput.loaderPath), + typesDir, + empty: loaderBundleOutput.empty, + }); + } + } + + return outputs; +}; + +/** + * Validate that an OutputTargetLoaderBundle object has what it needs to do its job. + * To enforce this, we have this function return `Required`, + * giving us a compile-time check that all properties are defined (with either + * user-supplied or default values). + * + * @param config the current config + * @param o the OutputTargetLoaderBundle object we want to validate + * @returns `Required`, i.e. `d.OutputTargetLoaderBundle` + * with all optional properties rendered un-optional. + */ +const validateOutputTargetLoaderBundle = ( + config: d.ValidatedConfig, + o: d.OutputTargetLoaderBundle, +): Required => { + // Create an object with default values for type inference + const outputTarget = { + ...o, + dir: getAbsolutePath(config, o.dir || DEFAULT_DIR), + buildDir: isString(o.buildDir) ? o.buildDir : DEFAULT_BUILD_DIR, + copy: validateCopy(o.copy ?? [], []), + empty: isBoolean(o.empty) ? o.empty : true, + cjs: isBoolean(o.cjs) ? o.cjs : false, + loaderPath: isString(o.loaderPath) ? o.loaderPath : DEFAULT_LOADER_PATH, + // loader-bundle skips distribution artifacts in dev mode by default, but always builds browser/CDN output + skipInDev: isBoolean(o.skipInDev) ? o.skipInDev : true, + } satisfies Required; + + if (!isAbsolute(outputTarget.buildDir)) { + outputTarget.buildDir = join(outputTarget.dir, outputTarget.buildDir); + } + + return outputTarget; +}; + +const DEFAULT_DIR = 'dist/loader-bundle'; +const DEFAULT_BUILD_DIR = ''; +const DEFAULT_LOADER_PATH = 'loader'; diff --git a/packages/core/src/compiler/config/outputs/validate-ssr.ts b/packages/core/src/compiler/config/outputs/validate-ssr.ts new file mode 100644 index 00000000000..2b052cd9320 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-ssr.ts @@ -0,0 +1,97 @@ +import { isAbsolute } from 'node:path'; +import type * as d from '@stencil/core'; + +import { + SSR, + isBoolean, + isOutputTargetLoaderBundle, + isOutputTargetSsr, + isOutputTargetWww, + isString, + join, +} from '../../../utils'; + +/** + * Validate the SSR (server-side rendering) output targets. + * + * This output target generates a script for server-side rendering and hydration, + * used for both SSR and static site generation (prerendering). + * + * If no explicit SSR output target is defined but prerendering or SSR is enabled, + * this validator will auto-create one. + * + * @param config the Stencil configuration + * @param userOutputs the output targets specified by the user + * @returns the validated SSR output targets + */ +export const validateSsr = (config: d.ValidatedConfig, userOutputs: d.OutputTarget[]) => { + const output: d.OutputTargetSsr[] = []; + + const hasSsrOutputTarget = userOutputs.some(isOutputTargetSsr); + + if (!hasSsrOutputTarget) { + // We don't already have an SSR output target + // Let's check if we need one based on other output targets + + const hasWwwOutput = userOutputs.filter(isOutputTargetWww).some((o) => isString(o.indexHtml)); + const shouldBuildSsr = config.prerender || config.ssr; + + if (hasWwwOutput && shouldBuildSsr) { + // We're prerendering a www output target, so we'll need an SSR script + let ssrDir: string; + const loaderBundleOutput = userOutputs.find(isOutputTargetLoaderBundle); + if (loaderBundleOutput != null && isString(loaderBundleOutput.dir)) { + ssrDir = join(loaderBundleOutput.dir, 'ssr'); + } else { + ssrDir = 'dist/ssr'; + } + + const ssrForWwwOutputTarget: d.OutputTargetSsr = { + type: SSR, + dir: ssrDir, + }; + userOutputs.push(ssrForWwwOutputTarget); + } + } + + const ssrOutputTargets = userOutputs.filter(isOutputTargetSsr); + + ssrOutputTargets.forEach((outputTarget) => { + if (!isString(outputTarget.dir)) { + // No directory given, use default + outputTarget.dir = 'dist/ssr'; + } + + if (!isAbsolute(outputTarget.dir)) { + outputTarget.dir = join(config.rootDir, outputTarget.dir); + } + + if (!isBoolean(outputTarget.empty)) { + outputTarget.empty = true; + } + + if (!isBoolean(outputTarget.minify)) { + outputTarget.minify = false; + } + + if (!isBoolean(outputTarget.cjs)) { + outputTarget.cjs = false; + } + + outputTarget.external = outputTarget.external || []; + + // Common Node.js built-ins that should remain external + outputTarget.external.push('fs'); + outputTarget.external.push('path'); + outputTarget.external.push('crypto'); + + // SSR skips in dev by default, unless devServer.ssr is enabled + if (!isBoolean(outputTarget.skipInDev)) { + outputTarget.skipInDev = !config.devServer?.ssr; + } + + output.push(outputTarget); + }); + + return output; +}; diff --git a/packages/core/src/compiler/config/outputs/validate-standalone.ts b/packages/core/src/compiler/config/outputs/validate-standalone.ts new file mode 100644 index 00000000000..ebbb6516af1 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-standalone.ts @@ -0,0 +1,98 @@ +import type { + OutputTarget, + OutputTargetCopy, + OutputTargetStandalone, + ValidatedConfig, +} from '@stencil/core'; + +import { CustomElementsExportBehaviorOptions } from '../../../declarations/stencil-public-compiler'; +import { + COPY, + isBoolean, + isOutputTargetLoaderBundle, + isOutputTargetStandalone, +} from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; +import { validateCopy } from '../validate-copy'; + +/** + * Validate one or more `standalone` output targets. + * + * Validation may involve back-filling fields that are omitted with sensible defaults + * and/or creating additional supporting output targets that were not explicitly + * defined by the user (e.g., copy tasks). + * + * Note: In v5, type declarations are auto-generated as a separate `types` output target + * in production builds, rather than being created here. + * + * @param config the Stencil configuration associated with the project being compiled + * @param userOutputs the output target(s) specified by the user + * @returns the validated output target(s) + */ +export const validateStandalone = ( + config: ValidatedConfig, + userOutputs: ReadonlyArray, +): ReadonlyArray => { + const defaultDir = 'dist/standalone'; + + // If loader-bundle is configured, standalone is secondary and skips in dev by default. + // If no loader-bundle, standalone is the primary output and should build in dev. + const hasLoaderBundle = userOutputs.some(isOutputTargetLoaderBundle); + + return userOutputs.filter(isOutputTargetStandalone).reduce( + (outputs, o) => { + const outputTarget = { + ...o, + dir: getAbsolutePath(config, o.dir || defaultDir), + skipInDev: isBoolean(o.skipInDev) ? o.skipInDev : hasLoaderBundle, + }; + + if (!isBoolean(outputTarget.empty)) { + outputTarget.empty = true; + } + if (!isBoolean(outputTarget.externalRuntime)) { + outputTarget.externalRuntime = true; + } + + // Export behavior must be defined on the validated target config and must + // be one of the export behavior valid values + if ( + outputTarget.customElementsExportBehavior == null || + !CustomElementsExportBehaviorOptions.includes(outputTarget.customElementsExportBehavior) + ) { + outputTarget.customElementsExportBehavior = 'default'; + } + + // Normalize autoLoader option (defaults to true) + if (outputTarget.autoLoader == null || outputTarget.autoLoader === true) { + outputTarget.autoLoader = { + fileName: 'loader', + autoStart: true, + }; + } else if (outputTarget.autoLoader === false) { + // keep as false (disabled) + } else if (typeof outputTarget.autoLoader === 'object') { + outputTarget.autoLoader = { + fileName: outputTarget.autoLoader.fileName || 'loader', + autoStart: outputTarget.autoLoader.autoStart !== false, + }; + } + + // Note: Type generation is now handled separately by auto-generated types output target in v5 + + outputTarget.copy = validateCopy(outputTarget.copy, []); + + if (outputTarget.copy.length > 0) { + outputs.push({ + type: COPY, + dir: config.rootDir, + copy: [...outputTarget.copy], + }); + } + outputs.push(outputTarget); + + return outputs; + }, + [] as (OutputTargetStandalone | OutputTargetCopy)[], + ); +}; diff --git a/src/compiler/config/outputs/validate-stats.ts b/packages/core/src/compiler/config/outputs/validate-stats.ts similarity index 77% rename from src/compiler/config/outputs/validate-stats.ts rename to packages/core/src/compiler/config/outputs/validate-stats.ts index 6d5e1ff2a19..311536ae7d6 100644 --- a/src/compiler/config/outputs/validate-stats.ts +++ b/packages/core/src/compiler/config/outputs/validate-stats.ts @@ -1,12 +1,12 @@ -import { isOutputTargetStats, join, STATS } from '@utils'; import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; -import type * as d from '../../../declarations'; +import { isOutputTargetStats, join, STATS } from '../../../utils'; export const validateStats = (userConfig: d.ValidatedConfig, userOutputs: d.OutputTarget[]) => { const outputTargets: d.OutputTargetStats[] = []; - if (userConfig.flags.stats) { + if (userConfig.statsJsonPath) { const hasOutputTarget = userOutputs.some(isOutputTargetStats); if (!hasOutputTarget) { const statsOutput: d.OutputTargetStats = { @@ -14,8 +14,8 @@ export const validateStats = (userConfig: d.ValidatedConfig, userOutputs: d.Outp }; // If --stats was provided with a path (string), use it; otherwise use default - if (typeof userConfig.flags.stats === 'string') { - statsOutput.file = userConfig.flags.stats; + if (typeof userConfig.statsJsonPath === 'string') { + statsOutput.file = userConfig.statsJsonPath; } outputTargets.push(statsOutput); diff --git a/packages/core/src/compiler/config/outputs/validate-stencil-rebundle.ts b/packages/core/src/compiler/config/outputs/validate-stencil-rebundle.ts new file mode 100644 index 00000000000..595ddb2c543 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-stencil-rebundle.ts @@ -0,0 +1,33 @@ +import type * as d from '@stencil/core'; + +import { isBoolean, isOutputTargetStencilRebundle } from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; + +/** + * Validate and return stencil-rebundle output targets. + * + * The stencil-rebundle output contains component source code, metadata, + * and configuration for downstream Stencil projects to re-compile and bundle. + * + * In v5, this is auto-generated in production builds unless explicitly configured. + * + * @param config a validated configuration object + * @param userOutputs an array of output targets + * @returns an array of validated stencil-rebundle output targets + */ +export const validateStencilRebundle = ( + config: d.ValidatedConfig, + userOutputs: d.OutputTarget[], +): d.OutputTargetStencilRebundle[] => { + return userOutputs.filter(isOutputTargetStencilRebundle).map((outputTarget) => { + return { + ...outputTarget, + transformAliasedImportPaths: isBoolean(outputTarget.transformAliasedImportPaths) + ? outputTarget.transformAliasedImportPaths + : true, + dir: getAbsolutePath(config, outputTarget.dir ?? 'dist/stencil-rebundle'), + empty: isBoolean(outputTarget.empty) ? outputTarget.empty : true, + skipInDev: isBoolean(outputTarget.skipInDev) ? outputTarget.skipInDev : true, + }; + }); +}; diff --git a/packages/core/src/compiler/config/outputs/validate-types.ts b/packages/core/src/compiler/config/outputs/validate-types.ts new file mode 100644 index 00000000000..f5965015441 --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-types.ts @@ -0,0 +1,30 @@ +import type * as d from '@stencil/core'; + +import { isBoolean, isOutputTargetTypes } from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; + +/** + * Validate and return types output targets. + * + * The types output generates TypeScript type definitions (.d.ts files) + * that can be shared across multiple output targets. + * + * In v5, this is auto-generated in production builds unless explicitly configured. + * + * @param config a validated configuration object + * @param userOutputs an array of output targets + * @returns an array of validated types output targets + */ +export const validateTypes = ( + config: d.ValidatedConfig, + userOutputs: d.OutputTarget[], +): d.OutputTargetTypes[] => { + return userOutputs.filter(isOutputTargetTypes).map((outputTarget) => { + return { + ...outputTarget, + dir: getAbsolutePath(config, outputTarget.dir ?? 'dist/types'), + empty: isBoolean(outputTarget.empty) ? outputTarget.empty : true, + skipInDev: isBoolean(outputTarget.skipInDev) ? outputTarget.skipInDev : true, + }; + }); +}; diff --git a/packages/core/src/compiler/config/outputs/validate-www.ts b/packages/core/src/compiler/config/outputs/validate-www.ts new file mode 100644 index 00000000000..7f18b0456da --- /dev/null +++ b/packages/core/src/compiler/config/outputs/validate-www.ts @@ -0,0 +1,175 @@ +import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; + +import { + ASSETS, + buildError, + COPY, + DIST_LAZY, + GLOBAL_STYLE, + isBoolean, + isOutputTargetLoaderBundle, + isOutputTargetStandalone, + isOutputTargetWww, + isString, + join, + STANDALONE, + STENCIL_REBUNDLE, + TYPES, + WWW, +} from '../../../utils'; +import { getAbsolutePath } from '../config-utils'; +import { validateCopy } from '../validate-copy'; +import { validatePrerender } from '../validate-prerender'; +import { validateServiceWorker } from '../validate-service-worker'; + +export const validateWww = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], + userOutputs: d.OutputTarget[], +) => { + // Only count 'real' user-configured output targets — exclude auto-generated + // outputs (types, stencil-rebundle, global-style, assets) that autoGenerateOutputs() + // may have injected into userOutputs before this function was called, so a bare config + // (no explicit output targets) still gets the default www output added. + const AUTO_GENERATED_TYPES = [TYPES, STENCIL_REBUNDLE, GLOBAL_STYLE, ASSETS] as const; + const hasOutputTargets = userOutputs.some( + (o) => !AUTO_GENERATED_TYPES.includes(o.type as (typeof AUTO_GENERATED_TYPES)[number]), + ); + const userWwwOutputs = userOutputs.filter(isOutputTargetWww); + + if (!hasOutputTargets) { + userWwwOutputs.push({ type: WWW }); + } + + // Auto-detect bundleMode based on configured primary output: + // If standalone is configured but NOT loader-bundle, default to 'standalone' + const hasLoaderBundle = userOutputs.some(isOutputTargetLoaderBundle); + const hasStandalone = userOutputs.some(isOutputTargetStandalone); + const defaultBundleMode = !hasLoaderBundle && hasStandalone ? 'standalone' : 'loader'; + + // Apply default bundleMode to www outputs that don't have it explicitly set + for (const wwwOutput of userWwwOutputs) { + if (wwwOutput.bundleMode == null) { + wwwOutput.bundleMode = defaultBundleMode; + } + } + + if (config.prerender && userWwwOutputs.length === 0) { + const err = buildError(diagnostics); + err.messageText = `You need at least one "www" output target configured in your stencil.config.ts, when the "--prerender" flag is used`; + } + + return userWwwOutputs.reduce( + ( + outputs: ( + | d.OutputTargetWww + | d.OutputTargetDistLazy + | d.OutputTargetStandalone + | d.OutputTargetCopy + )[], + o, + ) => { + const outputTarget = validateWwwOutputTarget(config, o, diagnostics); + outputs.push(outputTarget); + + const buildDir = outputTarget.buildDir; + + if (outputTarget.bundleMode === 'standalone') { + // Add standalone output target with auto-loader + outputs.push({ + type: STANDALONE, + dir: buildDir, + empty: false, // www handles emptying its own directory + externalRuntime: false, // inline runtime for simpler single-file deployment + autoLoader: { + fileName: config.fsNamespace, + autoStart: true, + }, + skipInDev: false, // always build for www + }); + } else { + // Default: Add dist-lazy output target + outputs.push({ + type: DIST_LAZY, + dir: buildDir, + esmDir: buildDir, + isBrowserBuild: true, + }); + } + + // Copy for user-defined copy tasks + outputs.push({ + type: COPY, + dir: buildDir, + }); + + // Copy for www + outputs.push({ + type: COPY, + dir: outputTarget.appDir, + copy: validateCopy(outputTarget.copy, [ + { src: 'assets', warn: false }, + { src: 'manifest.json', warn: false }, + ]), + }); + + return outputs; + }, + [], + ); +}; + +const validateWwwOutputTarget = ( + config: d.ValidatedConfig, + outputTarget: d.OutputTargetWww, + diagnostics: d.Diagnostic[], +) => { + // Normalize bundleMode (default to 'loader') + if (outputTarget.bundleMode !== 'standalone') { + outputTarget.bundleMode = 'loader'; + } + + if (!isString(outputTarget.baseUrl)) { + outputTarget.baseUrl = '/'; + } + + if (!outputTarget.baseUrl.endsWith('/')) { + // Make sure the baseUrl always finish with "/" + outputTarget.baseUrl += '/'; + } + + outputTarget.dir = getAbsolutePath(config, outputTarget.dir || 'www'); + + // Fix "dir" to account + const pathname = new URL(outputTarget.baseUrl, 'http://localhost/').pathname; + outputTarget.appDir = join(outputTarget.dir, pathname); + if (outputTarget.appDir.endsWith('/') || outputTarget.appDir.endsWith('\\')) { + outputTarget.appDir = outputTarget.appDir.substring(0, outputTarget.appDir.length - 1); + } + + if (!isString(outputTarget.buildDir)) { + outputTarget.buildDir = 'build'; + } + + if (!isAbsolute(outputTarget.buildDir)) { + outputTarget.buildDir = join(outputTarget.appDir, outputTarget.buildDir); + } + + if (!isString(outputTarget.indexHtml)) { + outputTarget.indexHtml = 'index.html'; + } + + if (!isAbsolute(outputTarget.indexHtml)) { + outputTarget.indexHtml = join(outputTarget.appDir, outputTarget.indexHtml); + } + + if (!isBoolean(outputTarget.empty)) { + outputTarget.empty = true; + } + + validatePrerender(config, diagnostics, outputTarget); + validateServiceWorker(config, outputTarget); + + return outputTarget; +}; diff --git a/src/compiler/config/transpile-options.ts b/packages/core/src/compiler/config/transpile-options.ts similarity index 87% rename from src/compiler/config/transpile-options.ts rename to packages/core/src/compiler/config/transpile-options.ts index 4b92fdd3676..49b6c7318a8 100644 --- a/src/compiler/config/transpile-options.ts +++ b/packages/core/src/compiler/config/transpile-options.ts @@ -1,16 +1,17 @@ -import { isString } from '@utils'; -import type { CompilerOptions } from 'typescript'; - import type { CompilerSystem, - Config, + UnvalidatedConfig, ImportData, TransformCssToEsmInput, TransformOptions, TranspileOptions, TranspileResults, -} from '../../declarations'; -import { STENCIL_INTERNAL_CLIENT_ID } from '../bundle/entry-alias-ids'; +} from '@stencil/core'; +import type { CompilerOptions } from 'typescript'; + +import { createNodeSys } from '../../sys/node'; +import { isString } from '../../utils'; +import { STENCIL_INTERNAL_CLIENT_PLATFORM_ID } from '../bundle/entry-alias-ids'; import { parseImportPath } from '../transformers/stencil-import-path'; export const getTranspileResults = (code: string, input: TranspileOptions) => { @@ -43,7 +44,7 @@ const transpileCtx = { sys: null as CompilerSystem }; */ interface TranspileConfig { compileOpts: TranspileOptions; - config: Config; + config: UnvalidatedConfig; transformOpts: TransformOptions; } @@ -58,13 +59,15 @@ export const getTranspileConfig = (input: TranspileOptions): TranspileConfig => if (input.sys) { transpileCtx.sys = input.sys; } else if (!transpileCtx.sys) { - transpileCtx.sys = require('../sys/node/index.js').createNodeSys(); + transpileCtx.sys = createNodeSys(); } const compileOpts: TranspileOptions = { componentExport: getTranspileConfigOpt(input.componentExport, VALID_EXPORT, 'customelement'), componentMetadata: getTranspileConfigOpt(input.componentMetadata, VALID_METADATA, null), - coreImportPath: isString(input.coreImportPath) ? input.coreImportPath : STENCIL_INTERNAL_CLIENT_ID, + coreImportPath: isString(input.coreImportPath) + ? input.coreImportPath + : STENCIL_INTERNAL_CLIENT_PLATFORM_ID, currentDirectory: isString(input.currentDirectory) ? input.currentDirectory : transpileCtx.sys.getCurrentDirectory(), @@ -73,7 +76,11 @@ export const getTranspileConfig = (input: TranspileOptions): TranspileConfig => module: getTranspileConfigOpt(input.module, VALID_MODULE, 'esm'), sourceMap: input.sourceMap === 'inline' ? 'inline' : input.sourceMap !== false, style: getTranspileConfigOpt(input.style, VALID_STYLE, 'static'), - styleImportData: getTranspileConfigOpt(input.styleImportData, VALID_STYLE_IMPORT_DATA, 'queryparams'), + styleImportData: getTranspileConfigOpt( + input.styleImportData, + VALID_STYLE_IMPORT_DATA, + 'queryparams', + ), target: getTranspileConfigOpt(input.target, VALID_TARGET, 'latest'), }; @@ -139,9 +146,10 @@ export const getTranspileConfig = (input: TranspileOptions): TranspileConfig => style: compileOpts.style as any, styleImportData: compileOpts.styleImportData as any, target: compileOpts.target as any, + extraFiles: input.extraFiles, }; - const config: Config = { + const config: UnvalidatedConfig = { _isTesting: true, devMode: true, enableCache: false, @@ -171,7 +179,7 @@ export const getTranspileCssConfig = ( file: results.inputFilePath, input: results.code, tag: importData && importData.tag, - tags: [...(compileOpts.tagsToTransform || importData?.tag)], + tags: [...(compileOpts.tagsToTransform || (importData?.tag ? [importData.tag] : []))], addTagTransformers: compileOpts && compileOpts.additionalTagTransformers === true, encapsulation: importData && importData.encapsulation, mode: importData && importData.mode, @@ -201,4 +209,13 @@ const VALID_MODULE = new Set(['cjs', 'esm']); const VALID_PROXY = new Set(['defineproperty', null]); const VALID_STYLE = new Set(['static']); const VALID_STYLE_IMPORT_DATA = new Set(['queryparams']); -const VALID_TARGET = new Set(['latest', 'esnext', 'es2020', 'es2019', 'es2018', 'es2017', 'es2016', 'es2015', 'es5']); +const VALID_TARGET = new Set([ + 'latest', + 'esnext', + 'es2020', + 'es2019', + 'es2018', + 'es2017', + 'es2016', + 'es2015', +]); diff --git a/packages/core/src/compiler/config/validate-config.ts b/packages/core/src/compiler/config/validate-config.ts new file mode 100644 index 00000000000..68daa04721b --- /dev/null +++ b/packages/core/src/compiler/config/validate-config.ts @@ -0,0 +1,281 @@ +import { + ConfigBundle, + ConfigExtras, + Diagnostic, + LoadConfigInit, + LogLevel, + UnvalidatedConfig, + ValidatedConfig, +} from '@stencil/core'; + +import { createNodeLogger, createNodeSys } from '../../sys/node'; +import { buildError, isBoolean, isNumber, isString, sortBy } from '../../utils'; +import { setBooleanConfig } from './config-utils'; +import { + DEFAULT_DEV_MODE, + DEFAULT_HASHED_FILENAME_LENGTH, + MAX_HASHED_FILENAME_LENGTH, + MIN_HASHED_FILENAME_LENGTH, +} from './constants'; +import { validateOutputTargets } from './outputs'; +import { validateDevServer } from './validate-dev-server'; +import { validateDocs } from './validate-docs'; +import { validateHydrated } from './validate-hydrated'; +import { validateDistNamespace } from './validate-namespace'; +import { validateNamespace } from './validate-namespace'; +import { validatePaths } from './validate-paths'; +import { validatePlugins } from './validate-plugins'; +import { validateRolldownConfig } from './validate-rolldown-config'; +import { validateWorkers } from './validate-workers'; + +/** + * Represents the results of validating a previously unvalidated configuration + */ +type ConfigValidationResults = { + /** + * The validated configuration, with well-known default values set if they weren't previously provided + */ + config: ValidatedConfig; + /** + * A collection of errors and warnings that occurred during the configuration validation process + */ + diagnostics: Diagnostic[]; +}; + +/** + * We never really want to re-run validation for a Stencil configuration. + * Besides the cost of doing so, our validation pipeline is unfortunately not + * idempotent, so we want to have a guarantee that even if we call + * {@link validateConfig} in a few places that the same configuration object + * won't be passed through multiple times. So we cache the result of our work + * here. + */ +let CACHED_VALIDATED_CONFIG: ValidatedConfig | null = null; + +/** + * Validate a Config object, ensuring that all its field are present and + * consistent with our expectations. This function transforms an + * {@link UnvalidatedConfig} to a {@link ValidatedConfig}. + * + * **NOTE**: this function _may_ return a previously-cached configuration + * object. It will do so if the cached object is `===` to the one passed in. + * + * @param userConfig an unvalidated config that we've gotten from a user + * @param bootstrapConfig the initial configuration provided by the user (or + * generated by Stencil) used to bootstrap configuration loading and validation + * @returns an object with config and diagnostics props + */ +export const validateConfig = ( + userConfig: UnvalidatedConfig = {}, + bootstrapConfig: LoadConfigInit, +): ConfigValidationResults => { + const diagnostics: Diagnostic[] = []; + + if (CACHED_VALIDATED_CONFIG !== null && CACHED_VALIDATED_CONFIG === userConfig) { + // We've previously done the work to validate a Stencil config. Since our + // overall validation pipeline is unfortunately not idempotent we do not + // want to simply validate again. Leaving aside the performance + // implications of needlessly repeating the validation, we don't want to do + // certain operations multiple times. + // + // For the sake of correctness we check both that the cache is not null and + // that it's the same object as the one passed in. + return { + config: userConfig as ValidatedConfig, + diagnostics, + }; + } + + const config = Object.assign({}, userConfig); + + const logger = bootstrapConfig.logger || config.logger || createNodeLogger(); + + // Log level: use config value or default to 'info' + // CLI is responsible for setting this based on --verbose/--debug flags + const logLevel: LogLevel = config.logLevel ?? 'info'; + logger.setLevel(logLevel); + + // devMode: set by the --dev CLI flag via mergeFlags before validateConfig is called. + // Not user-settable in stencil.config.ts. Default is false (production). + const devMode = isBoolean(config.devMode) ? config.devMode : DEFAULT_DEV_MODE; + + config._isTesting = !!( + process.env.VITEST || + process.env.PLAYWRIGHT_TEST || + process.env.TEST_WORKER_INDEX || + process.env.TEST_PARALLEL_INDEX || + process.env.NODE_ENV === 'test' + ); + + const hashFileNames = config.hashFileNames ?? !devMode; + + const validatedConfig: ValidatedConfig = { + devServer: {}, // assign `devServer` before spreading `config`, in the event 'devServer' is not a key on `config` + ...config, + devMode, + extras: config.extras || {}, + generateExportMaps: isBoolean(config.generateExportMaps) ? config.generateExportMaps : false, + hashFileNames, + hashedFileNameLength: config.hashedFileNameLength ?? DEFAULT_HASHED_FILENAME_LENGTH, + hydratedFlag: validateHydrated(config), + logLevel, + logger, + minifyCss: config.minifyCss ?? !devMode, + minifyJs: config.minifyJs ?? !devMode, + outputTargets: config.outputTargets ?? [], + rolldownConfig: validateRolldownConfig(config), + sourceMap: + config.sourceMap === true || + (devMode && (config.sourceMap === 'dev' || typeof config.sourceMap === 'undefined')), + sys: config.sys ?? bootstrapConfig.sys ?? createNodeSys({ logger }), + docs: validateDocs(config, logger), + transformAliasedImportPaths: isBoolean(userConfig.transformAliasedImportPaths) + ? userConfig.transformAliasedImportPaths + : true, + ...validateNamespace(config.namespace, config.fsNamespace, diagnostics), + ...validatePaths(config), + }; + + // Set the log file path on the logger if writeLog is enabled + if (validatedConfig.buildLogFilePath) { + logger.setLogFilePath(validatedConfig.buildLogFilePath); + } + + validatedConfig.extras.lifecycleDOMEvents = !!validatedConfig.extras.lifecycleDOMEvents; + validatedConfig.extras.initializeNextTick = !!validatedConfig.extras.initializeNextTick; + validatedConfig.extras.additionalTagTransformers = + validatedConfig.extras.additionalTagTransformers === true || + (!devMode && validatedConfig.extras.additionalTagTransformers === 'prod'); + + // TODO(STENCIL-914): remove when `experimentalSlotFixes` is the default behavior + // If the user set `experimentalSlotFixes` and any individual slot fix flags to `false`, we need to log a warning + // to the user that we will "override" the individual flags + if (validatedConfig.extras.experimentalSlotFixes === true) { + const possibleFlags: (keyof ConfigExtras)[] = [ + 'appendChildSlotFix', + 'slotChildNodesFix', + 'cloneNodeFix', + 'scopedSlotTextContentFix', + 'experimentalScopedSlotChanges', + ]; + const conflictingFlags = possibleFlags.filter((flag) => validatedConfig.extras[flag] === false); + if (conflictingFlags.length > 0) { + const warning = buildError(diagnostics); + warning.level = 'warn'; + warning.messageText = `If the 'experimentalSlotFixes' flag is enabled it will override any slot fix flags which are disabled. In particular, the following currently-disabled flags will be ignored: ${conflictingFlags.join( + ', ', + )}. Please update your Stencil config accordingly.`; + } + } + + // TODO(STENCIL-914): remove `experimentalSlotFixes` when it's the default behavior + validatedConfig.extras.experimentalSlotFixes = !!validatedConfig.extras.experimentalSlotFixes; + if (validatedConfig.extras.experimentalSlotFixes === true) { + validatedConfig.extras.appendChildSlotFix = true; + validatedConfig.extras.cloneNodeFix = true; + validatedConfig.extras.slotChildNodesFix = true; + validatedConfig.extras.scopedSlotTextContentFix = true; + validatedConfig.extras.experimentalScopedSlotChanges = true; + } else { + validatedConfig.extras.appendChildSlotFix = !!validatedConfig.extras.appendChildSlotFix; + validatedConfig.extras.cloneNodeFix = !!validatedConfig.extras.cloneNodeFix; + validatedConfig.extras.slotChildNodesFix = !!validatedConfig.extras.slotChildNodesFix; + validatedConfig.extras.scopedSlotTextContentFix = + !!validatedConfig.extras.scopedSlotTextContentFix; + // TODO(STENCIL-1086): remove this option when it's the default behavior + validatedConfig.extras.experimentalScopedSlotChanges = + !!validatedConfig.extras.experimentalScopedSlotChanges; + } + + // Set boolean config values with defaults + // CLI is responsible for merging flags into config before validation + setBooleanConfig(validatedConfig, 'watch', false); + setBooleanConfig(validatedConfig, 'profile', validatedConfig.devMode); + setBooleanConfig(validatedConfig, 'writeLog', false); + setBooleanConfig(validatedConfig, 'buildAppCore', true); + setBooleanConfig(validatedConfig, 'autoprefixCss', false); + setBooleanConfig(validatedConfig, 'validateTypes', !validatedConfig._isTesting); + setBooleanConfig(validatedConfig, 'allowInlineScripts', true); + setBooleanConfig(validatedConfig, 'suppressReservedPublicNameWarnings', false); + + if (!isString(validatedConfig.taskQueue)) { + validatedConfig.taskQueue = 'async'; + } + + // hash file names + if (!isBoolean(validatedConfig.hashFileNames)) { + validatedConfig.hashFileNames = !validatedConfig.devMode; + } + if (!isNumber(validatedConfig.hashedFileNameLength)) { + validatedConfig.hashedFileNameLength = DEFAULT_HASHED_FILENAME_LENGTH; + } + if (validatedConfig.hashedFileNameLength < MIN_HASHED_FILENAME_LENGTH) { + const err = buildError(diagnostics); + err.messageText = `validatedConfig.hashedFileNameLength must be at least ${MIN_HASHED_FILENAME_LENGTH} characters`; + } + if (validatedConfig.hashedFileNameLength > MAX_HASHED_FILENAME_LENGTH) { + const err = buildError(diagnostics); + err.messageText = `validatedConfig.hashedFileNameLength cannot be more than ${MAX_HASHED_FILENAME_LENGTH} characters`; + } + if (!validatedConfig.env) { + validatedConfig.env = {}; + } + + // outputTargets + validateOutputTargets(validatedConfig, diagnostics); + + // plugins + validatePlugins(validatedConfig, diagnostics); + + // dev server + validatedConfig.devServer = validateDevServer(validatedConfig, diagnostics); + + // bundles + if (Array.isArray(validatedConfig.bundles)) { + validatedConfig.bundles = sortBy( + validatedConfig.bundles, + (a: ConfigBundle) => a.components.length, + ); + } else { + validatedConfig.bundles = []; + } + + // exclude components (tag list) + if (!Array.isArray(validatedConfig.excludeComponents)) { + validatedConfig.excludeComponents = []; + } + + // validate how many workers we can use + validateWorkers(validatedConfig); + + // default devInspector to whatever devMode is + setBooleanConfig(validatedConfig, 'devInspector', validatedConfig.devMode); + + if (!validatedConfig._isTesting) { + validateDistNamespace(validatedConfig, diagnostics); + } + + setBooleanConfig(validatedConfig, 'enableCache', true); + + if ( + !Array.isArray(validatedConfig.watchIgnoredRegex) && + validatedConfig.watchIgnoredRegex != null + ) { + validatedConfig.watchIgnoredRegex = [validatedConfig.watchIgnoredRegex]; + } + validatedConfig.watchIgnoredRegex = ( + (validatedConfig.watchIgnoredRegex as RegExp[]) || [] + ).reduce((arr, reg) => { + if (reg instanceof RegExp) { + arr.push(reg); + } + return arr; + }, [] as RegExp[]); + + CACHED_VALIDATED_CONFIG = validatedConfig; + + return { + config: validatedConfig, + diagnostics, + }; +}; diff --git a/src/compiler/config/validate-copy.ts b/packages/core/src/compiler/config/validate-copy.ts similarity index 90% rename from src/compiler/config/validate-copy.ts rename to packages/core/src/compiler/config/validate-copy.ts index cb58cba79ec..26dbdae2db0 100644 --- a/src/compiler/config/validate-copy.ts +++ b/packages/core/src/compiler/config/validate-copy.ts @@ -1,6 +1,6 @@ -import { unique } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { unique } from '../../utils'; /** * Validate a series of {@link d.CopyTask}s diff --git a/src/compiler/config/validate-dev-server.ts b/packages/core/src/compiler/config/validate-dev-server.ts similarity index 79% rename from src/compiler/config/validate-dev-server.ts rename to packages/core/src/compiler/config/validate-dev-server.ts index c23a011b440..5b90e362e10 100644 --- a/src/compiler/config/validate-dev-server.ts +++ b/packages/core/src/compiler/config/validate-dev-server.ts @@ -1,20 +1,33 @@ -import { buildError, isBoolean, isNumber, isOutputTargetWww, isString, join, normalizePath } from '@utils'; import { isAbsolute } from 'path'; - -import type * as d from '../../declarations'; - -export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diagnostic[]): d.DevServerConfig => { +import type * as d from '@stencil/core'; + +import { + buildError, + isBoolean, + isNumber, + isOutputTargetWww, + isString, + join, + normalizePath, +} from '../../utils'; + +export const validateDevServer = ( + config: d.ValidatedConfig, + diagnostics: d.Diagnostic[], +): d.DevServerConfig => { if ((config.devServer === null || (config.devServer as any)) === false) { return {}; } - const { flags } = config; const devServer = { ...config.devServer }; - if (flags.address && isString(flags.address)) { - devServer.address = flags.address; + // Use devServerAddress from config (set via --address flag) + if (config.devServerAddress && isString(config.devServerAddress)) { + devServer.address = config.devServerAddress; } else if (!isString(devServer.address)) { - devServer.address = '0.0.0.0'; + // Use localhost instead of 0.0.0.0 to avoid Chrome's Private Network Access policy + // which blocks requests from less-private to more-private address spaces + devServer.address = 'localhost'; } // default to http for local dev @@ -44,7 +57,8 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag // so we can safely split on `:` here. const addressSplit = devServer.address.split(':'); - const isLocalhost = addressSplit[0] === 'localhost' || !isNaN(addressSplit[0].split('.')[0] as any); + const isLocalhost = + addressSplit[0] === 'localhost' || !isNaN(addressSplit[0].split('.')[0] as any); // if localhost we use 3333 as a default port let addressPort: number | undefined = isLocalhost ? 3333 : undefined; @@ -56,8 +70,9 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag } } - if (isNumber(flags.port)) { - devServer.port = flags.port; + // Use devServerPort from config (set via --port flag) + if (isNumber(config.devServerPort)) { + devServer.port = config.devServerPort; } else if (devServer.port !== null && !isNumber(devServer.port)) { if (isNumber(addressPort)) { devServer.port = addressPort; @@ -80,7 +95,7 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag } if (!isBoolean(devServer.openBrowser)) { - devServer.openBrowser = true; + devServer.openBrowser = false; } if (!isBoolean(devServer.websocket)) { @@ -91,7 +106,8 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag devServer.strictPort = false; } - if (flags.ssr) { + // Use ssr from config (set via --ssr flag) + if (config.ssr) { devServer.ssr = true; } else { devServer.ssr = !!devServer.ssr; @@ -111,7 +127,10 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag } if (devServer.historyApiFallback !== null) { - if (Array.isArray(devServer.historyApiFallback) || typeof devServer.historyApiFallback !== 'object') { + if ( + Array.isArray(devServer.historyApiFallback) || + typeof devServer.historyApiFallback !== 'object' + ) { devServer.historyApiFallback = {}; } @@ -124,9 +143,10 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag } } - if (flags.open === false) { + // Use devServerOpen from config (set via --open flag) + if (config.devServerOpen === false) { devServer.openBrowser = false; - } else if (flags.prerender && !config.watch) { + } else if (config.prerender && !config.watch) { devServer.openBrowser = false; } @@ -188,7 +208,7 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag devServer.excludeHmr = []; } - if (!config.devMode || config.buildEs5) { + if (!config.devMode) { devServer.experimentalDevModules = false; } else { devServer.experimentalDevModules = !!devServer.experimentalDevModules; diff --git a/packages/core/src/compiler/config/validate-docs.ts b/packages/core/src/compiler/config/validate-docs.ts new file mode 100644 index 00000000000..150cba8c1c4 --- /dev/null +++ b/packages/core/src/compiler/config/validate-docs.ts @@ -0,0 +1,47 @@ +import * as d from '@stencil/core'; +import { UnvalidatedConfig } from '@stencil/core'; + +import { isHexColor } from '../docs/readme/docs-util'; +import { DEFAULT_TARGET_COMPONENT_STYLES } from './constants'; + +/** + * Validate the `.docs` property on the supplied config object and + * return a properly-validated value. + * + * @param config the configuration we're examining + * @param logger the logger that will be set on the config + * @returns a suitable/default value for the docs property + */ +export const validateDocs = ( + config: UnvalidatedConfig, + logger: d.Logger, +): d.ValidatedConfig['docs'] => { + const { background: defaultBackground, textColor: defaultTextColor } = + DEFAULT_TARGET_COMPONENT_STYLES; + + let { background = defaultBackground, textColor = defaultTextColor } = + config.docs?.markdown?.targetComponent ?? DEFAULT_TARGET_COMPONENT_STYLES; + + if (!isHexColor(background)) { + logger.warn( + `'${background}' is not a valid hex color. The default value for diagram backgrounds ('${defaultBackground}') will be used.`, + ); + background = defaultBackground; + } + + if (!isHexColor(textColor)) { + logger.warn( + `'${textColor}' is not a valid hex color. The default value for diagram text ('${defaultTextColor}') will be used.`, + ); + textColor = defaultTextColor; + } + + return { + markdown: { + targetComponent: { + background, + textColor, + }, + }, + }; +}; diff --git a/src/compiler/config/validate-hydrated.ts b/packages/core/src/compiler/config/validate-hydrated.ts similarity index 90% rename from src/compiler/config/validate-hydrated.ts rename to packages/core/src/compiler/config/validate-hydrated.ts index de0424fddde..ff54d567a19 100644 --- a/src/compiler/config/validate-hydrated.ts +++ b/packages/core/src/compiler/config/validate-hydrated.ts @@ -1,6 +1,6 @@ -import { isString } from '@utils'; +import { HydratedFlag, UnvalidatedConfig } from '@stencil/core'; -import { HydratedFlag, UnvalidatedConfig } from '../../declarations'; +import { isString } from '../../utils'; /** * Validate the `.hydratedFlag` property on the supplied config object and @@ -24,7 +24,7 @@ export const validateHydrated = (config: UnvalidatedConfig): HydratedFlag | null // Here we start building up a default config since `.hydratedFlag` wasn't set to // `null` on the provided config. - const hydratedFlag: HydratedFlag = { ...(config.hydratedFlag ?? {}) }; + const hydratedFlag: HydratedFlag = { ...config.hydratedFlag }; if (!isString(hydratedFlag.name) || hydratedFlag.property === '') { hydratedFlag.name = `hydrated`; diff --git a/src/compiler/config/validate-namespace.ts b/packages/core/src/compiler/config/validate-namespace.ts similarity index 92% rename from src/compiler/config/validate-namespace.ts rename to packages/core/src/compiler/config/validate-namespace.ts index 5afffb13771..991ea29984f 100644 --- a/src/compiler/config/validate-namespace.ts +++ b/packages/core/src/compiler/config/validate-namespace.ts @@ -1,6 +1,6 @@ -import { buildError, dashToPascalCase, isOutputTargetDist, isString } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { buildError, dashToPascalCase, isOutputTargetLoaderBundle, isString } from '../../utils'; import { DEFAULT_NAMESPACE } from './constants'; /** @@ -28,7 +28,7 @@ export const validateNamespace = ( namespace = isString(namespace) ? namespace : DEFAULT_NAMESPACE; namespace = namespace.trim(); - const invalidNamespaceChars = namespace.replace(/(\w)|(\-)|(\$)/g, ''); + const invalidNamespaceChars = namespace.replace(/(\w)|(-)|(\$)/g, ''); if (invalidNamespaceChars !== '') { const err = buildError(diagnostics); err.messageText = `Namespace "${namespace}" contains invalid characters: ${invalidNamespaceChars}`; @@ -65,7 +65,7 @@ export const validateNamespace = ( }; export const validateDistNamespace = (config: d.UnvalidatedConfig, diagnostics: d.Diagnostic[]) => { - const hasDist = (config.outputTargets ?? []).some(isOutputTargetDist); + const hasDist = (config.outputTargets ?? []).some(isOutputTargetLoaderBundle); if (hasDist) { if (!isString(config.namespace) || config.namespace.toLowerCase() === 'app') { const err = buildError(diagnostics); diff --git a/packages/core/src/compiler/config/validate-package-json.ts b/packages/core/src/compiler/config/validate-package-json.ts new file mode 100644 index 00000000000..778b49bb0f6 --- /dev/null +++ b/packages/core/src/compiler/config/validate-package-json.ts @@ -0,0 +1,598 @@ +import { dirname } from 'path'; +import type * as d from '@stencil/core'; + +import { + buildJsonFileError, + COLLECTION_MANIFEST_FILE_NAME, + GENERATED_DTS, + isGlob, + isOutputTargetLoaderBundle, + isOutputTargetStandalone, + isOutputTargetStencilRebundle, + isOutputTargetTypes, + isString, + join, + normalizePath, + relative, +} from '../../utils'; + +/** + * Represents the recommended package.json field values based on configured output targets. + */ +interface PackageJsonRecommendations { + /** + * Recommended values for the "module" field (ESM entry points). + * Contains paths for each configured output target that produces an entry point. + */ + moduleOptions: string[]; + /** + * Recommended values for the "types" field. + * Contains paths based on configured output targets. + */ + typesOptions: string[]; + /** + * Recommended value for the "main" field (CJS entry point). + * Only set if loader-bundle has cjs: true. + */ + main: string | null; + /** + * Whether any output target produces CJS. + * Used to determine if "type": "module" should be recommended. + */ + hasCjsOutput: boolean; +} + +/** + * Auto-detect recommended package.json values based on configured output targets. + * + * In v5, output targets are peers - there's no "primary" output target. + * Instead, recommendations are derived from which outputs are configured: + * - `moduleOptions` contains entry points for each configured output (loader, standalone) + * - `typesOptions`: index.d.ts (if src/index.ts exists), otherwise loader.d.ts and/or standalone.d.ts based on outputs + * - `main` points to CJS output from loader-bundle (if cjs: true) + * + * @param config The validated Stencil configuration + * @param compilerCtx The compiler context (for file system access) + * @returns Recommended values for package.json fields + */ +const getPackageJsonRecommendations = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, +): PackageJsonRecommendations => { + const loaderBundle = config.outputTargets.find(isOutputTargetLoaderBundle); + const standalone = config.outputTargets.find(isOutputTargetStandalone); + const types = config.outputTargets.find(isOutputTargetTypes); + + // moduleOptions: collect entry points from each configured output target + const moduleOptions: string[] = []; + + // loader-bundle provides an entry at its dir (e.g. dist/loader-bundle/index.js) + if (loaderBundle?.dir) { + moduleOptions.push(normalizePath(relative(config.rootDir, join(loaderBundle.dir, 'index.js')))); + } + + // standalone provides an entry at its dir (defaults to dist/standalone) + if (standalone?.dir) { + moduleOptions.push(normalizePath(relative(config.rootDir, join(standalone.dir, 'index.js')))); + } + + // typesOptions: collect valid type entry points in priority order + // 1. index.d.ts if src/index.ts exists (user's defined package entry) + // 2. loader.d.ts if loader-bundle output is configured + // 3. standalone.d.ts if standalone output is configured + // 4. components.d.ts as last resort (only if no other options) + const typesOptions: string[] = []; + if (types?.dir) { + const srcIndexPath = join(config.srcDir, 'index.ts'); + const hasSrcIndex = compilerCtx.fs.accessSync(srcIndexPath); + + if (hasSrcIndex) { + typesOptions.push(normalizePath(relative(config.rootDir, join(types.dir, 'index.d.ts')))); + } else { + // Add output-specific types files + if (loaderBundle) { + typesOptions.push(normalizePath(relative(config.rootDir, join(types.dir, 'loader.d.ts')))); + } + if (standalone) { + typesOptions.push( + normalizePath(relative(config.rootDir, join(types.dir, 'standalone.d.ts'))), + ); + } + // Fallback to components.d.ts only if no output targets configured + if (typesOptions.length === 0) { + typesOptions.push(normalizePath(relative(config.rootDir, join(types.dir, GENERATED_DTS)))); + } + } + } + + // main: only if loader-bundle has CJS enabled + let main: string | null = null; + const hasCjsOutput = !!loaderBundle?.cjs; + if (loaderBundle?.dir && loaderBundle.cjs) { + main = normalizePath(relative(config.rootDir, join(loaderBundle.dir, 'index.cjs'))); + } + + return { moduleOptions, typesOptions, main, hasCjsOutput }; +}; + +// ============================================================================ +// Build-time validation (entry point) +// ============================================================================ + +/** + * Validate the package.json file for a project, checking that various fields + * are set correctly for the currently-configured output targets. + * + * This is the main entry point called during the build process. + * + * @param config the project's Stencil config + * @param compilerCtx the compiler context + * @param buildCtx the build context + * @returns an empty Promise + */ +export const validateBuildPackageJson = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, +): Promise => { + if (config.watch || buildCtx.packageJson == null) { + return; + } + + // Validate core package.json fields based on configured output targets + validatePackageJson(config, compilerCtx, buildCtx); + + // Validate stencil-rebundle specific fields + const stencilRebundleOutputTargets = config.outputTargets.filter(isOutputTargetStencilRebundle); + await Promise.all( + stencilRebundleOutputTargets.map((stencilRebundleOT) => + validateStencilRebundleFields(config, compilerCtx, buildCtx, stencilRebundleOT), + ), + ); +}; + +/** + * Validates a project's package.json fields against recommended values + * based on configured output targets. + * + * This replaces the old `validatePrimaryPackageOutputTarget` function. + * Instead of requiring users to mark a "primary" output target, + * validation is auto-detected based on which outputs are configured. + * + * @param config The Stencil project's config + * @param compilerCtx The project's compiler context + * @param buildCtx The project's build context + */ +const validatePackageJson = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, +): void => { + const recommendations = getPackageJsonRecommendations(config, compilerCtx); + + // No distributable outputs configured - nothing to validate + if (recommendations.moduleOptions.length === 0 && recommendations.typesOptions.length === 0) { + return; + } + + // Validate module field + if (recommendations.moduleOptions.length > 0) { + validateModuleField(config, compilerCtx, buildCtx, recommendations.moduleOptions); + } + + // Validate types field + if (recommendations.typesOptions.length > 0) { + validateTypesField(config, compilerCtx, buildCtx, recommendations.typesOptions); + } + + // Validate main field + if (recommendations.main) { + // CJS output is enabled - validate main is set correctly + validateMainField(config, compilerCtx, buildCtx, recommendations.main); + } else { + // CJS output not enabled - but if main is set, check it exists + validateMainFieldExists(config, compilerCtx, buildCtx); + } + + // Validate type: "module" field + validateTypeField(config, compilerCtx, buildCtx, recommendations); +}; + +// ============================================================================ +// Field validators +// ============================================================================ + +/** + * Formats module options for display in warning messages. + * Single option: "./dist/loader/index.js" + * Multiple options: "./dist/loader/index.js or ./dist/standalone/index.js" + * @param options the module options to format + * @returns a formatted string of module options for display in messages + */ +const formatModuleOptions = (options: string[]): string => { + if (options.length === 1) { + return options[0]; + } + return options.join(' or '); +}; + +/** + * Validates the "module" field in package.json. + * Checks that the field exists and points to a file that will be generated. + * @param config the current Stencil configuration + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param moduleOptions the recommended module paths based on configured output targets + */ +const validateModuleField = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + moduleOptions: string[], +): void => { + const currentModulePath = buildCtx.packageJson.module; + const suggestion = formatModuleOptions(moduleOptions); + + if (!isString(currentModulePath) || currentModulePath === '') { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "module" property is required when generating a distribution. It's recommended to set the "module" property to: ${suggestion}`, + '"module"', + ); + return; + } + + // Check if the current module path exists + const moduleFile = join(config.rootDir, currentModulePath); + const moduleFileExists = compilerCtx.fs.accessSync(moduleFile); + + if (!moduleFileExists) { + // File doesn't exist - provide helpful message with recommendation + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "module" property is set to "${currentModulePath}" which doesn't exist. Consider setting it to: ${suggestion}`, + '"module"', + ); + } + // If the file exists, don't warn even if it differs from recommendation +}; + +/** + * Validates the "types" field in package.json. + * Checks that the field exists, has a .d.ts extension, and points to a file that exists. + * @param config the current Stencil configuration + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param typesOptions the recommended paths for the "types" field based on configured output targets + */ +const validateTypesField = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + typesOptions: string[], +): void => { + const currentTypesPath = buildCtx.packageJson.types; + const suggestion = formatModuleOptions(typesOptions); + + if (!isString(currentTypesPath) || currentTypesPath === '') { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "types" property is required when generating a distribution. It's recommended to set the "types" property to: ${suggestion}`, + '"types"', + ); + return; + } + + if (!currentTypesPath.endsWith('.d.ts')) { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "types" file must have a ".d.ts" extension. The "types" property is currently set to: ${currentTypesPath}`, + '"types"', + ); + return; + } + + // Check if the types file exists - this is the primary validation + const typesFile = join(config.rootDir, currentTypesPath); + const typesFileExists = compilerCtx.fs.accessSync(typesFile); + + if (!typesFileExists) { + // File doesn't exist - provide helpful message with recommendation + packageJsonError( + config, + compilerCtx, + buildCtx, + `package.json "types" property is set to "${currentTypesPath}" which doesn't exist. Consider setting it to: ${suggestion}`, + '"types"', + ); + } + // If the file exists, don't warn even if it differs from recommendation +}; + +/** + * Validates the "main" field in package.json. + * Called when CJS output is being generated - checks that main is set correctly. + * @param config the current Stencil configuration + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param recommendedPath the recommended path for the main field based on output targets + */ +const validateMainField = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + recommendedPath: string, +): void => { + const currentMainPath = buildCtx.packageJson.main; + + if (!isString(currentMainPath) || currentMainPath === '') { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "main" property is recommended when generating CJS output. Set it to: ${recommendedPath}`, + '"main"', + ); + return; + } + + // Check if the current main path exists + const mainFile = join(config.rootDir, currentMainPath); + const mainFileExists = compilerCtx.fs.accessSync(mainFile); + + if (!mainFileExists) { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "main" property is set to "${currentMainPath}" which doesn't exist. Consider setting it to: ${recommendedPath}`, + '"main"', + ); + } +}; + +/** + * Validates that if "main" is set in package.json, the file actually exists. + * This is called regardless of whether CJS output is configured. + * @param config the current Stencil configuration + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + */ +const validateMainFieldExists = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, +): void => { + const currentMainPath = buildCtx.packageJson.main; + + if (!isString(currentMainPath) || currentMainPath === '') { + return; // No main field set, nothing to validate + } + + // Check if the current main path exists + const mainFile = join(config.rootDir, currentMainPath); + const mainFileExists = compilerCtx.fs.accessSync(mainFile); + + if (!mainFileExists) { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "main" property is set to "${currentMainPath}" which doesn't exist.`, + '"main"', + ); + } +}; + +/** + * Validates the "type" field in package.json. + * + * In v5, we always recommend "type": "module" when generating distributable outputs. + * @param config the current Stencil configuration + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param recommendations the recommended package.json field values based on configured output targets + */ +const validateTypeField = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + recommendations: PackageJsonRecommendations, +): void => { + const currentType = buildCtx.packageJson.type; + + // Always recommend "type": "module" when generating distributable outputs + // CJS output uses .cjs extension which works regardless of "type" field + if (recommendations.moduleOptions.length > 0 && currentType !== 'module') { + packageJsonWarn( + config, + compilerCtx, + buildCtx, + `package.json "type" property should be set to "module".`, + '"type"', + ); + } +}; + +// ============================================================================ +// Stencil-rebundle specific validation +// ============================================================================ + +/** + * Validate package.json contents specific to the `stencil-rebundle` output target, + * checking that the `files` array and `stencilRebundle` field are set correctly. + * @param config the Stencil configuration associated with the project being compiled + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param outputTarget the stencil-rebundle output target to validate against + */ +const validateStencilRebundleFields = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + outputTarget: d.OutputTargetStencilRebundle, +) => { + await Promise.all([ + validatePackageFiles(config, compilerCtx, buildCtx, outputTarget), + validateStencilRebundleField(config, compilerCtx, buildCtx, outputTarget), + ]); +}; + +/** + * Validate that the `files` field in `package.json` contains directories and + * files that are necessary for the `stencil-rebundle` output target. + * @param config the Stencil configuration associated with the project being compiled + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param outputTarget the stencil-rebundle output target to validate against + */ +const validatePackageFiles = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + outputTarget: d.OutputTargetStencilRebundle, +) => { + if (!config.devMode && Array.isArray(buildCtx.packageJson.files)) { + const actualDistDir = normalizePath(relative(config.rootDir, outputTarget.dir)); + + // Check if the files array contains the distribution directory directly, + // or a parent directory that would include it (e.g., "dist/" covers "dist/stencil-rebundle/") + const containsDistDir = buildCtx.packageJson.files.some((userPath) => { + // Normalize both paths: remove trailing slashes and leading ./ + const normalizedUserPath = normalizePath(userPath).replace(/\/$/, '').replace(/^\.\//, ''); + const normalizedDistDir = actualDistDir.replace(/\/$/, '').replace(/^\.\//, ''); + + // Exact match + if (normalizedUserPath === normalizedDistDir) { + return true; + } + + // Parent directory match (e.g., "dist" covers "dist/stencil-rebundle") + const userPathWithSlash = normalizedUserPath + '/'; + if (normalizedDistDir.startsWith(userPathWithSlash)) { + return true; + } + + return false; + }); + + if (!containsDistDir) { + const msg = `package.json "files" array must contain the distribution directory "${actualDistDir}/" when generating a distribution.`; + packageJsonWarn(config, compilerCtx, buildCtx, msg, `"files"`); + return; + } + + await Promise.all( + buildCtx.packageJson.files.map(async (pkgFile) => { + if (!isGlob(pkgFile)) { + const packageJsonDir = dirname(config.packageJsonFilePath); + const absPath = join(packageJsonDir, pkgFile); + + const hasAccess = await compilerCtx.fs.access(absPath); + if (!hasAccess) { + const msg = `Unable to find "${pkgFile}" within the package.json "files" array.`; + packageJsonError(config, compilerCtx, buildCtx, msg, `"${pkgFile}"`); + } + } + }), + ); + } +}; + +/** + * Check that the `stencilRebundle` field is set correctly in `package.json` for the + * `stencil-rebundle` output target. + * @param config the Stencil configuration associated with the project being compiled + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param outputTarget the output target to validate against + */ +const validateStencilRebundleField = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + outputTarget: d.OutputTargetStencilRebundle, +) => { + if (outputTarget.dir) { + const rebundleRel = normalizePath( + join(relative(config.rootDir, outputTarget.dir), COLLECTION_MANIFEST_FILE_NAME), + false, + ); + if ( + !buildCtx.packageJson.stencilRebundle || + normalizePath(buildCtx.packageJson.stencilRebundle, false) !== rebundleRel + ) { + const msg = `package.json "stencilRebundle" property should be set to ${rebundleRel} when generating a distribution bundle.`; + packageJsonWarn(config, compilerCtx, buildCtx, msg, `"stencilRebundle"`); + } + } +}; + +// ============================================================================ +// Logging utilities +// ============================================================================ + +/** + * Build a diagnostic for an error resulting from a particular field in a + * package.json file + * @param config the Stencil configuration associated with the project being compiled + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param msg the error message to display + * @param jsonField the specific package.json field related to the error (e.g. "module", "types", etc.) + * @returns a Diagnostic object representing the error + */ +const packageJsonError = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + msg: string, + jsonField: string, +): d.Diagnostic => { + const err = buildJsonFileError( + compilerCtx, + buildCtx.diagnostics, + config.packageJsonFilePath, + msg, + jsonField, + ); + err.header = `Package Json`; + return err; +}; + +/** + * Build a diagnostic for a warning resulting from a particular field in a + * package.json file + * @param config the Stencil configuration associated with the project being compiled + * @param compilerCtx the current compiler context + * @param buildCtx the context associated with the current build + * @param msg the warning message to display + * @param jsonField the specific package.json field related to the warning (e.g. "module", "types", etc.) + * @returns a Diagnostic object representing the warning + */ +const packageJsonWarn = ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + buildCtx: d.BuildCtx, + msg: string, + jsonField: string, +): d.Diagnostic => { + const warn = buildJsonFileError( + compilerCtx, + buildCtx.diagnostics, + config.packageJsonFilePath, + msg, + jsonField, + ); + warn.header = `Package Json`; + warn.level = 'warn'; + return warn; +}; diff --git a/src/compiler/config/validate-paths.ts b/packages/core/src/compiler/config/validate-paths.ts similarity index 86% rename from src/compiler/config/validate-paths.ts rename to packages/core/src/compiler/config/validate-paths.ts index b594e49dcf0..a4db3826abe 100644 --- a/src/compiler/config/validate-paths.ts +++ b/packages/core/src/compiler/config/validate-paths.ts @@ -1,7 +1,7 @@ -import { join, normalizePath } from '@utils'; import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { join, normalizePath } from '../../utils'; /** * The paths validated in this module. These fields can be incorporated into a @@ -43,7 +43,10 @@ export const validatePaths = (config: d.Config): ConfigPaths => { cacheDir = normalizePath(cacheDir); } - let srcIndexHtml = typeof config.srcIndexHtml !== 'string' ? join(srcDir, DEFAULT_INDEX_HTML) : config.srcIndexHtml; + let srcIndexHtml = + typeof config.srcIndexHtml !== 'string' + ? join(srcDir, DEFAULT_INDEX_HTML) + : config.srcIndexHtml; if (!isAbsolute(srcIndexHtml)) { srcIndexHtml = join(rootDir, srcIndexHtml); @@ -69,7 +72,9 @@ export const validatePaths = (config: d.Config): ConfigPaths => { if (config.writeLog) { validatedPaths.buildLogFilePath = - typeof config.buildLogFilePath === 'string' ? config.buildLogFilePath : DEFAULT_BUILD_LOG_FILE_NAME; + typeof config.buildLogFilePath === 'string' + ? config.buildLogFilePath + : DEFAULT_BUILD_LOG_FILE_NAME; if (!isAbsolute(validatedPaths.buildLogFilePath)) { validatedPaths.buildLogFilePath = join(rootDir, config.buildLogFilePath); diff --git a/packages/core/src/compiler/config/validate-plugins.ts b/packages/core/src/compiler/config/validate-plugins.ts new file mode 100644 index 00000000000..d945b02a0d3 --- /dev/null +++ b/packages/core/src/compiler/config/validate-plugins.ts @@ -0,0 +1,43 @@ +import type * as d from '@stencil/core'; + +import { buildWarn } from '../../utils'; + +export const validatePlugins = (config: d.UnvalidatedConfig, diagnostics: d.Diagnostic[]) => { + const userPlugins = config.plugins; + + if (!config.rolldownPlugins) { + config.rolldownPlugins = {}; + } + if (!Array.isArray(userPlugins)) { + config.plugins = []; + return; + } + + const rolldownPlugins = userPlugins.filter((plugin) => { + return !!(plugin && typeof plugin === 'object' && !plugin.pluginType); + }); + + const hasResolveNode = rolldownPlugins.some((p) => p.name === 'node-resolve'); + const hasCommonjs = rolldownPlugins.some((p) => p.name === 'commonjs'); + + if (hasCommonjs) { + const warn = buildWarn(diagnostics); + warn.messageText = `Stencil already uses "@rolldown/plugin-commonjs", please remove it from your "stencil.config.ts" plugins. + You can configure the commonjs settings using the "commonjs" property in "stencil.config.ts`; + } + + if (hasResolveNode) { + const warn = buildWarn(diagnostics); + warn.messageText = `Stencil already uses "@rolldown/plugin-commonjs", please remove it from your "stencil.config.ts" plugins. + You can configure the commonjs settings using the "commonjs" property in "stencil.config.ts`; + } + + config.rolldownPlugins.before = [ + ...(config.rolldownPlugins.before || []), + ...rolldownPlugins.filter(({ name }) => name !== 'node-resolve' && name !== 'commonjs'), + ]; + + config.plugins = userPlugins.filter((plugin) => { + return !!(plugin && typeof plugin === 'object' && plugin.pluginType); + }); +}; diff --git a/src/compiler/config/validate-prerender.ts b/packages/core/src/compiler/config/validate-prerender.ts similarity index 82% rename from src/compiler/config/validate-prerender.ts rename to packages/core/src/compiler/config/validate-prerender.ts index 78c52343cdb..9f1894ba9ff 100644 --- a/src/compiler/config/validate-prerender.ts +++ b/packages/core/src/compiler/config/validate-prerender.ts @@ -1,14 +1,15 @@ -import { buildError, isString, join, normalizePath } from '@utils'; import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { buildError, isString, join, normalizePath } from '../../utils'; export const validatePrerender = ( config: d.ValidatedConfig, diagnostics: d.Diagnostic[], outputTarget: d.OutputTargetWww, ) => { - if (!config.flags.ssr && !config.flags.prerender && config.flags.task !== 'prerender') { + // Skip prerender validation if neither ssr nor prerender is enabled + if (!config.ssr && !config.prerender) { return; } diff --git a/packages/core/src/compiler/config/validate-rolldown-config.ts b/packages/core/src/compiler/config/validate-rolldown-config.ts new file mode 100644 index 00000000000..98fcf867518 --- /dev/null +++ b/packages/core/src/compiler/config/validate-rolldown-config.ts @@ -0,0 +1,52 @@ +import type * as d from '@stencil/core'; + +import { isObject, pluck } from '../../utils'; + +/** + * Ensure that a valid baseline rolldown configuration is set on the validated + * config. + * + * If a config is present this will return a new config based on the user + * supplied one. + * + * If no config is present, this will return a default config. + * + * @param config a validated user-supplied configuration object + * @returns a validated rolldown configuration + */ +export const validateRolldownConfig = (config: d.Config): d.RolldownConfig => { + let cleanRolldownConfig = { ...DEFAULT_ROLLDOWN_CONFIG }; + + const rolldownConfig = config.rolldownConfig; + + if (!rolldownConfig || !isObject(rolldownConfig)) { + return cleanRolldownConfig; + } + + if (rolldownConfig.inputOptions && isObject(rolldownConfig.inputOptions)) { + cleanRolldownConfig = { + ...cleanRolldownConfig, + inputOptions: pluck(rolldownConfig.inputOptions, [ + 'context', + 'moduleContext', + 'treeshake', + 'external', + 'maxParallelFileOps', + ]), + }; + } + + if (rolldownConfig.outputOptions && isObject(rolldownConfig.outputOptions)) { + cleanRolldownConfig = { + ...cleanRolldownConfig, + outputOptions: pluck(rolldownConfig.outputOptions, ['globals']), + }; + } + + return cleanRolldownConfig; +}; + +const DEFAULT_ROLLDOWN_CONFIG: d.RolldownConfig = { + inputOptions: {}, + outputOptions: {}, +}; diff --git a/src/compiler/config/validate-service-worker.ts b/packages/core/src/compiler/config/validate-service-worker.ts similarity index 86% rename from src/compiler/config/validate-service-worker.ts rename to packages/core/src/compiler/config/validate-service-worker.ts index de067b309d3..edab299d67e 100644 --- a/src/compiler/config/validate-service-worker.ts +++ b/packages/core/src/compiler/config/validate-service-worker.ts @@ -1,7 +1,7 @@ -import { isString, join } from '@utils'; import { isAbsolute } from 'path'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { isString, join } from '../../utils'; /** * Validate that a service worker configuration is valid, if it is present and @@ -23,11 +23,14 @@ import type * as d from '../../declarations'; * configuration we want to validate. **Note**: the `.serviceWorker` object * _will be mutated_ if it is present. */ -export const validateServiceWorker = (config: d.ValidatedConfig, outputTarget: d.OutputTargetWww): void => { +export const validateServiceWorker = ( + config: d.ValidatedConfig, + outputTarget: d.OutputTargetWww, +): void => { if (outputTarget.serviceWorker === false) { return; } - if (config.devMode && !config.flags.serviceWorker) { + if (config.devMode && !config.generateServiceWorker) { outputTarget.serviceWorker = null; return; } @@ -77,8 +80,14 @@ export const validateServiceWorker = (config: d.ValidatedConfig, outputTarget: d outputTarget.serviceWorker.swSrc = join(config.rootDir, outputTarget.serviceWorker.swSrc); } - if (isString(outputTarget.serviceWorker.swDest) && !isAbsolute(outputTarget.serviceWorker.swDest)) { - outputTarget.serviceWorker.swDest = join(outputTarget.appDir ?? '', outputTarget.serviceWorker.swDest); + if ( + isString(outputTarget.serviceWorker.swDest) && + !isAbsolute(outputTarget.serviceWorker.swDest) + ) { + outputTarget.serviceWorker.swDest = join( + outputTarget.appDir ?? '', + outputTarget.serviceWorker.swDest, + ); } }; @@ -95,7 +104,6 @@ const addGlobIgnores = (config: d.ValidatedConfig, globIgnores: string[]) => { `**/*.system.entry.js`, `**/*.system.js`, `**/${config.fsNamespace}.js`, - `**/${config.fsNamespace}.esm.js`, `**/${config.fsNamespace}.css`, ); }; diff --git a/packages/core/src/compiler/config/validate-workers.ts b/packages/core/src/compiler/config/validate-workers.ts new file mode 100644 index 00000000000..d9d631ebfd6 --- /dev/null +++ b/packages/core/src/compiler/config/validate-workers.ts @@ -0,0 +1,19 @@ +import type * as d from '@stencil/core'; + +export const validateWorkers = (config: d.ValidatedConfig) => { + if (typeof config.maxConcurrentWorkers !== 'number') { + config.maxConcurrentWorkers = 8; + } + + // maxConcurrentWorkers is set via mergeFlags from --maxWorkers flag + // Reduce workers in CI environments for stability + if (config.ci) { + config.maxConcurrentWorkers = Math.min(config.maxConcurrentWorkers, 4); + } + + config.maxConcurrentWorkers = Math.max(Math.min(config.maxConcurrentWorkers, 16), 0); + + if (config.devServer) { + config.devServer.worker = config.maxConcurrentWorkers > 0; + } +}; diff --git a/src/compiler/docs/test/custom-elements-manifest.spec.ts b/packages/core/src/compiler/docs/_test_/custom-elements-manifest.spec.ts similarity index 98% rename from src/compiler/docs/test/custom-elements-manifest.spec.ts rename to packages/core/src/compiler/docs/_test_/custom-elements-manifest.spec.ts index 7d6d0c21ff1..ee7684afb3e 100644 --- a/src/compiler/docs/test/custom-elements-manifest.spec.ts +++ b/packages/core/src/compiler/docs/_test_/custom-elements-manifest.spec.ts @@ -1,16 +1,17 @@ import { mockCompilerCtx, mockValidatedConfig } from '@stencil/core/testing'; +import { describe, expect, it, MockInstance, beforeEach, afterEach, vi } from 'vitest'; +import type * as d from '@stencil/core'; -import type * as d from '../../../declarations'; import { generateCustomElementsManifestDocs } from '../cem'; describe('custom-elements-manifest', () => { let compilerCtx: d.CompilerCtx; - let writeFileSpy: jest.SpyInstance; + let writeFileSpy: MockInstance; beforeEach(() => { const config = mockValidatedConfig(); compilerCtx = mockCompilerCtx(config); - writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile'); + writeFileSpy = vi.spyOn(compilerCtx.fs, 'writeFile'); }); afterEach(() => { @@ -523,7 +524,10 @@ describe('custom-elements-manifest', () => { const writtenContent = JSON.parse(writeFileSpy.mock.calls[0][1]); const declaration = writtenContent.modules[0].declarations[0]; expect(declaration.cssProperties).toHaveLength(1); - expect(declaration.cssProperties[0]).toEqual({ name: '--my-color', description: 'The primary color' }); + expect(declaration.cssProperties[0]).toEqual({ + name: '--my-color', + description: 'The primary color', + }); }); it('includes deprecation info', async () => { diff --git a/src/compiler/docs/test/docs-util.spec.ts b/packages/core/src/compiler/docs/_test_/docs-util.spec.ts similarity index 94% rename from src/compiler/docs/test/docs-util.spec.ts rename to packages/core/src/compiler/docs/_test_/docs-util.spec.ts index 48ae9b8caf3..069e130d2dc 100644 --- a/src/compiler/docs/test/docs-util.spec.ts +++ b/packages/core/src/compiler/docs/_test_/docs-util.spec.ts @@ -1,4 +1,6 @@ -import { isHexColor, MarkdownTable } from '../../docs/readme/docs-util'; +import { describe, expect, it } from 'vitest'; + +import { isHexColor, MarkdownTable } from '../readme/docs-util'; describe('markdown-table', () => { it('header', () => { diff --git a/src/compiler/docs/test/generate-doc-data.spec.ts b/packages/core/src/compiler/docs/_test_/generate-doc-data.spec.ts similarity index 92% rename from src/compiler/docs/test/generate-doc-data.spec.ts rename to packages/core/src/compiler/docs/_test_/generate-doc-data.spec.ts index 2e21f74ed08..92d612f3b41 100644 --- a/src/compiler/docs/test/generate-doc-data.spec.ts +++ b/packages/core/src/compiler/docs/_test_/generate-doc-data.spec.ts @@ -1,8 +1,14 @@ -import { mockBuildCtx, mockCompilerCtx, mockModule, mockValidatedConfig } from '@stencil/core/testing'; -import { DEFAULT_STYLE_MODE, getComponentsFromModules } from '@utils'; - -import type * as d from '../../../declarations'; -import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub'; +import { + mockBuildCtx, + mockCompilerCtx, + mockModule, + mockValidatedConfig, +} from '@stencil/core/testing'; +import { describe, expect, it, beforeEach } from 'vitest'; +import type * as d from '@stencil/core'; + +import { DEFAULT_STYLE_MODE, getComponentsFromModules } from '../../../utils'; +import { stubComponentCompilerMeta } from '../../types/_tests_/ComponentCompilerMeta.stub'; import { AUTO_GENERATE_COMMENT } from '../constants'; import { generateDocData, getDocsStyles } from '../generate-doc-data'; @@ -337,28 +343,31 @@ auto-generated content }, ); - it.each(['', null, undefined])("defaults the docs to an empty string if '%s' is provided", (docsValue) => { - const compilerStyleDoc: d.CompilerStyleDoc = { - annotation: 'prop', - docs: 'these are the docs for this prop', - name: 'my-style-one', - mode: DEFAULT_STYLE_MODE, - }; - // @ts-ignore the intent of this test to verify the fallback of this field if it's falsy - compilerStyleDoc.docs = docsValue; + it.each(['', null, undefined])( + "defaults the docs to an empty string if '%s' is provided", + (docsValue) => { + const compilerStyleDoc: d.CompilerStyleDoc = { + annotation: 'prop', + docs: 'these are the docs for this prop', + name: 'my-style-one', + mode: DEFAULT_STYLE_MODE, + }; + // @ts-ignore the intent of this test to verify the fallback of this field if it's falsy + compilerStyleDoc.docs = docsValue; - const compilerMeta = stubComponentCompilerMeta({ styleDocs: [compilerStyleDoc] }); + const compilerMeta = stubComponentCompilerMeta({ styleDocs: [compilerStyleDoc] }); - const actual = getDocsStyles(compilerMeta); + const actual = getDocsStyles(compilerMeta); - expect(actual).toEqual([ - { - annotation: 'prop', - docs: '', - name: 'my-style-one', - }, - ]); - }); + expect(actual).toEqual([ + { + annotation: 'prop', + docs: '', + name: 'my-style-one', + }, + ]); + }, + ); it.each(['', undefined, null, DEFAULT_STYLE_MODE])( "uses 'undefined' for the mode value when '%s' is provided", diff --git a/src/compiler/docs/test/markdown-dependencies.spec.ts b/packages/core/src/compiler/docs/_test_/markdown-dependencies.spec.ts similarity index 97% rename from src/compiler/docs/test/markdown-dependencies.spec.ts rename to packages/core/src/compiler/docs/_test_/markdown-dependencies.spec.ts index 4403fa9cd5a..6eecd7ca0bf 100644 --- a/src/compiler/docs/test/markdown-dependencies.spec.ts +++ b/packages/core/src/compiler/docs/_test_/markdown-dependencies.spec.ts @@ -1,4 +1,6 @@ -import type * as d from '../../../declarations'; +import { describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + import { DEFAULT_TARGET_COMPONENT_STYLES } from '../../config/constants'; import { depsToMarkdown } from '../readme/markdown-dependencies'; diff --git a/src/compiler/docs/test/markdown-overview.spec.ts b/packages/core/src/compiler/docs/_test_/markdown-overview.spec.ts similarity index 96% rename from src/compiler/docs/test/markdown-overview.spec.ts rename to packages/core/src/compiler/docs/_test_/markdown-overview.spec.ts index c487968c387..9501b1755ef 100644 --- a/src/compiler/docs/test/markdown-overview.spec.ts +++ b/packages/core/src/compiler/docs/_test_/markdown-overview.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { overviewToMarkdown } from '../readme/markdown-overview'; describe('markdown-overview', () => { diff --git a/src/compiler/docs/test/markdown-props.spec.ts b/packages/core/src/compiler/docs/_test_/markdown-props.spec.ts similarity index 96% rename from src/compiler/docs/test/markdown-props.spec.ts rename to packages/core/src/compiler/docs/_test_/markdown-props.spec.ts index ba1e543538c..52c0554c906 100644 --- a/src/compiler/docs/test/markdown-props.spec.ts +++ b/packages/core/src/compiler/docs/_test_/markdown-props.spec.ts @@ -1,4 +1,6 @@ -import { propsToMarkdown } from '../../docs/readme/markdown-props'; +import { describe, expect, it } from 'vitest'; + +import { propsToMarkdown } from '../readme/markdown-props'; describe('markdown props', () => { it('advanced union types', () => { diff --git a/packages/core/src/compiler/docs/_test_/output-docs.spec.ts b/packages/core/src/compiler/docs/_test_/output-docs.spec.ts new file mode 100644 index 00000000000..a54b42429b8 --- /dev/null +++ b/packages/core/src/compiler/docs/_test_/output-docs.spec.ts @@ -0,0 +1,139 @@ +import { describe, expect, it } from 'vitest'; +import type * as d from '@stencil/core'; + +import { generateMarkdown } from '../readme/output-docs'; + +describe('css-props to markdown', () => { + describe('generateMarkdown', () => { + const mockReadmeOutput: d.OutputTargetDocsReadme = { + type: 'docs-readme', + footer: '*Built with StencilJS*', + }; + + const mockComponent: d.JsonDocsComponent = { + tag: 'my-component', + filePath: 'src/components/my-component/my-component.tsx', + fileName: 'my-component.tsx', + dirPath: 'src/components/my-component', + readmePath: 'src/components/my-component/readme.md', + usagesDir: 'src/components/my-component/usage', + encapsulation: 'shadow', + docs: '', + docsTags: [], + usage: {}, + props: [], + methods: [], + events: [], + listeners: [], + styles: [], + slots: [], + parts: [], + dependents: [], + dependencies: [], + dependencyGraph: {}, + customStates: [], + readme: '', + }; + + it.each([ + { + name: 'component styles when available', + componentStyles: [ + { + name: '--background', + docs: 'Background color', + annotation: 'prop' as const, + mode: undefined, + }, + { name: '--color', docs: 'Text color', annotation: 'prop' as const, mode: undefined }, + ], + shouldContain: [ + '## CSS Custom Properties', + '`--background`', + 'Background color', + '`--color`', + 'Text color', + ], + shouldNotContain: [], + }, + { + name: 'preserved CSS props (already in component.styles)', + componentStyles: [ + { + name: '--bg', + docs: 'Defaults to var(--nano-color-blue-cerulean-1000);', + annotation: 'prop' as const, + mode: undefined, + }, + { + name: '--text-color', + docs: 'Text color of the component', + annotation: 'prop' as const, + mode: undefined, + }, + ], + shouldContain: [ + '## CSS Custom Properties', + '`--bg`', + 'Defaults to var(--nano-color-blue-cerulean-1000);', + ], + shouldNotContain: [], + }, + { + name: 'no CSS section when styles are empty', + componentStyles: [], + shouldContain: [], + shouldNotContain: ['## CSS Custom Properties'], + }, + { + name: 'updated component styles', + componentStyles: [ + { + name: '--new-prop', + docs: 'New property from build', + annotation: 'prop' as const, + mode: undefined, + }, + ], + shouldContain: ['`--new-prop`', 'New property from build'], + shouldNotContain: [], + }, + ])('should use $name', ({ componentStyles, shouldContain, shouldNotContain }) => { + const component: d.JsonDocsComponent = { + ...mockComponent, + styles: componentStyles, + }; + + const markdown = generateMarkdown('# my-component', component, [], mockReadmeOutput); + + shouldContain.forEach((expected) => { + expect(markdown).toContain(expected); + }); + + shouldNotContain.forEach((unexpected) => { + expect(markdown).not.toContain(unexpected); + }); + }); + + it('should escape special characters in CSS prop descriptions', () => { + const component: d.JsonDocsComponent = { + ...mockComponent, + styles: [ + { + name: '--bg', + docs: 'Defaults to var(--nano-color-blue-cerulean-1000); with | pipes', + annotation: 'prop', + mode: undefined, + }, + ], + }; + + const markdown = generateMarkdown('# my-component', component, [], mockReadmeOutput); + + // Pipe characters are escaped in markdown tables + expect(markdown).toContain( + 'Defaults to var(--nano-color-blue-cerulean-1000); with \\| pipes', + ); + }); + }); +}); diff --git a/src/compiler/docs/test/style-docs.spec.ts b/packages/core/src/compiler/docs/_test_/style-docs.spec.ts similarity index 87% rename from src/compiler/docs/test/style-docs.spec.ts rename to packages/core/src/compiler/docs/_test_/style-docs.spec.ts index fb2e3a02c90..46369597f40 100644 --- a/src/compiler/docs/test/style-docs.spec.ts +++ b/packages/core/src/compiler/docs/_test_/style-docs.spec.ts @@ -1,6 +1,7 @@ -import type * as d from '@stencil/core/declarations'; -import { DEFAULT_STYLE_MODE } from '@utils'; +import { describe, expect, it, beforeEach } from 'vitest'; +import type * as d from '@stencil/core'; +import { DEFAULT_STYLE_MODE } from '../../../utils'; import { parseStyleDocs } from '../style-docs'; describe('style-docs', () => { @@ -146,7 +147,9 @@ describe('style-docs', () => { } `; parseStyleDocs(styleDocs, styleText); - expect(styleDocs).toEqual([{ name: `--max-width`, docs: `Max width of the alert`, annotation: 'prop' }]); + expect(styleDocs).toEqual([ + { name: `--max-width`, docs: `Max width of the alert`, annotation: 'prop' }, + ]); }); it('works with multiple, mixed comment types', () => { @@ -168,8 +171,10 @@ describe('style-docs', () => { ]); }); - it.each(['ios', 'md', undefined, '', DEFAULT_STYLE_MODE])("attaches mode metadata for a style mode '%s'", (mode) => { - const styleText = ` + it.each(['ios', 'md', undefined, '', DEFAULT_STYLE_MODE])( + "attaches mode metadata for a style mode '%s'", + (mode) => { + const styleText = ` /*! * @prop --max-width: Max width of the alert */ @@ -178,8 +183,11 @@ describe('style-docs', () => { } `; - parseStyleDocs(styleDocs, styleText, mode); + parseStyleDocs(styleDocs, styleText, mode); - expect(styleDocs).toEqual([{ name: `--max-width`, docs: `Max width of the alert`, annotation: 'prop', mode }]); - }); + expect(styleDocs).toEqual([ + { name: `--max-width`, docs: `Max width of the alert`, annotation: 'prop', mode }, + ]); + }, + ); }); diff --git a/packages/core/src/compiler/docs/cem/index.ts b/packages/core/src/compiler/docs/cem/index.ts new file mode 100644 index 00000000000..ebaba0ac396 --- /dev/null +++ b/packages/core/src/compiler/docs/cem/index.ts @@ -0,0 +1,382 @@ +import type * as d from '@stencil/core'; + +import { dashToPascalCase, isOutputTargetDocsCustomElementsManifest } from '../../../utils'; + +/** + * Generate Custom Elements Manifest (custom-elements.json) output + * conforming to the Custom Elements Manifest specification. + * @see https://github.com/webcomponents/custom-elements-manifest + * + * @param compilerCtx the current compiler context + * @param docsData the generated docs data from Stencil components + * @param outputTargets the output targets configured for the build + */ +export const generateCustomElementsManifestDocs = async ( + compilerCtx: d.CompilerCtx, + docsData: d.JsonDocs, + outputTargets: d.OutputTarget[], +): Promise => { + const cemOutputTargets = outputTargets.filter(isOutputTargetDocsCustomElementsManifest); + if (cemOutputTargets.length === 0) { + return; + } + + const manifest = generateManifest(docsData); + const jsonContent = JSON.stringify(manifest, null, 2); + + await Promise.all( + cemOutputTargets.map((outputTarget) => + compilerCtx.fs.writeFile(outputTarget.file!, jsonContent), + ), + ); +}; + +/** + * Generate the Custom Elements Manifest from Stencil docs data + * @param docsData the generated docs data + * @returns the Custom Elements Manifest object + */ +const generateManifest = (docsData: d.JsonDocs): CustomElementsManifest => { + // Group components by their source file path + const componentsByFile = new Map(); + + for (const component of docsData.components) { + const filePath = component.filePath; + if (!componentsByFile.has(filePath)) { + componentsByFile.set(filePath, []); + } + componentsByFile.get(filePath)!.push(component); + } + + const modules: JavaScriptModule[] = []; + + for (const [filePath, components] of componentsByFile) { + const declarations: CustomElementDeclaration[] = components.map((component) => + componentToDeclaration(component), + ); + + const exports: (JavaScriptExport | CustomElementExport)[] = components.flatMap((component) => { + const className = dashToPascalCase(component.tag); + return [ + { + kind: 'js' as const, + name: className, + declaration: { + name: className, + }, + }, + { + kind: 'custom-element-definition' as const, + name: component.tag, + declaration: { + name: className, + }, + }, + ]; + }); + + modules.push({ + kind: 'javascript-module', + path: filePath, + declarations, + exports, + }); + } + + return { + schemaVersion: '2.1.0', + modules, + }; +}; + +/** + * Convert Stencil's ComponentCompilerTypeReferences to CEM TypeReference array + * @param references Stencil's type references map + * @returns CEM TypeReference array + */ +const convertTypeReferences = ( + references?: d.ComponentCompilerTypeReferences, +): TypeReference[] | undefined => { + if (!references || Object.keys(references).length === 0) { + return undefined; + } + + return Object.entries(references).map(([name, ref]) => ({ + name, + // Global types (like HTMLElement, Array) get 'global:' package + ...(ref.location === 'global' && { package: 'global:' }), + // Imported types get their module path + ...(ref.location === 'import' && ref.path && { module: ref.path }), + // Local types don't need package or module (they're in the same module) + })); +}; + +/** + * Create a CEM Type object from a type string and optional references + * @param text the type string + * @param references Stencil's type references map + * @returns CEM Type object + */ +const createType = (text: string, references?: d.ComponentCompilerTypeReferences): Type => { + const typeRefs = convertTypeReferences(references); + return { + text, + ...(typeRefs && { references: typeRefs }), + }; +}; + +/** + * Convert a Stencil component to a Custom Element Declaration + * @param component the Stencil component docs data + * @returns the Custom Element Declaration + */ +const componentToDeclaration = (component: d.JsonDocsComponent): CustomElementDeclaration => { + const className = dashToPascalCase(component.tag); + + const attributes: Attribute[] = component.props + .filter((prop) => prop.attr !== undefined) + .map((prop) => ({ + name: prop.attr!, + ...(prop.docs && { description: prop.docs }), + ...(prop.type && { type: createType(prop.type, prop.complexType?.references) }), + ...(prop.default !== undefined && { default: prop.default }), + fieldName: prop.name, + ...(prop.deprecation !== undefined && { deprecated: prop.deprecation || true }), + })); + + const members: (CustomElementField | ClassMethod)[] = [ + // Fields (properties) + ...component.props.map( + (prop): CustomElementField => ({ + kind: 'field', + name: prop.name, + ...(prop.docs && { description: prop.docs }), + ...(prop.type && { type: createType(prop.type, prop.complexType?.references) }), + ...(prop.default !== undefined && { default: prop.default }), + ...(prop.deprecation !== undefined && { deprecated: prop.deprecation || true }), + ...(!prop.mutable && { readonly: true }), + ...(prop.attr && { attribute: prop.attr }), + ...(prop.reflectToAttr && { reflects: true }), + }), + ), + // Methods + ...component.methods.map( + (method): ClassMethod => ({ + kind: 'method', + name: method.name, + ...(method.docs && { description: method.docs }), + ...(method.deprecation !== undefined && { deprecated: method.deprecation || true }), + ...(method.parameters && + method.parameters.length > 0 && { + parameters: method.parameters.map((param) => ({ + name: param.name, + ...(param.docs && { description: param.docs }), + ...(param.type && { type: createType(param.type, method.complexType?.references) }), + })), + }), + ...(method.returns && { + return: { + ...(method.returns.type && { + type: createType(method.returns.type, method.complexType?.references), + }), + ...(method.returns.docs && { description: method.returns.docs }), + }, + }), + }), + ), + ]; + + const events: Event[] = component.events.map((event) => ({ + name: event.event, + ...(event.docs && { description: event.docs }), + type: createType( + event.detail ? `CustomEvent<${event.detail}>` : 'CustomEvent', + event.complexType?.references, + ), + ...(event.deprecation !== undefined && { deprecated: event.deprecation || true }), + })); + + const slots: Slot[] = component.slots.map((slot) => ({ + name: slot.name, + ...(slot.docs && { description: slot.docs }), + })); + + const cssParts: CssPart[] = component.parts.map((part) => ({ + name: part.name, + ...(part.docs && { description: part.docs }), + })); + + const cssProperties: CssCustomProperty[] = component.styles + .filter((style) => style.annotation === 'prop') + .map((style) => ({ + name: style.name, + ...(style.docs && { description: style.docs }), + })); + + // Generate demos from usage examples + const demos: Demo[] = Object.entries(component.usage || {}).map(([name, content]) => ({ + // Create relative URL from usagesDir + filename + url: component.usagesDir ? `${component.usagesDir}/${name}.md` : `${name}.md`, + ...(content && { description: content }), + })); + + return { + kind: 'class', + customElement: true, + tagName: component.tag, + name: className, + ...(component.docs && { description: component.docs }), + ...(component.deprecation !== undefined && { deprecated: component.deprecation || true }), + ...(attributes.length > 0 && { attributes }), + ...(members.length > 0 && { members }), + ...(events.length > 0 && { events }), + ...(slots.length > 0 && { slots }), + ...(cssParts.length > 0 && { cssParts }), + ...(cssProperties.length > 0 && { cssProperties }), + ...(component.customStates.length > 0 && { + customStates: component.customStates.map((state) => ({ + name: state.name, + initialValue: state.initialValue, + ...(state.docs && { description: state.docs }), + })), + }), + ...(demos.length > 0 && { demos }), + }; +}; + +// Custom Elements Manifest Types +// Based on https://github.com/webcomponents/custom-elements-manifest/blob/main/schema.d.ts + +interface CustomElementsManifest { + schemaVersion: string; + modules: JavaScriptModule[]; +} + +interface JavaScriptModule { + kind: 'javascript-module'; + path: string; + declarations?: CustomElementDeclaration[]; + exports?: (JavaScriptExport | CustomElementExport)[]; +} + +interface JavaScriptExport { + kind: 'js'; + name: string; + declaration: Reference; +} + +interface CustomElementExport { + kind: 'custom-element-definition'; + name: string; + declaration: Reference; +} + +interface Reference { + name: string; + package?: string; + module?: string; +} + +interface CustomElementDeclaration { + kind: 'class'; + customElement: true; + tagName: string; + name: string; + description?: string; + deprecated?: boolean | string; + attributes?: Attribute[]; + members?: (CustomElementField | ClassMethod)[]; + events?: Event[]; + slots?: Slot[]; + cssParts?: CssPart[]; + cssProperties?: CssCustomProperty[]; + customStates?: CustomState[]; + demos?: Demo[]; +} + +interface Demo { + url: string; + description?: string; +} + +interface Attribute { + name: string; + description?: string; + type?: Type; + default?: string; + fieldName?: string; + deprecated?: boolean | string; +} + +interface Type { + text: string; + references?: TypeReference[]; +} + +interface TypeReference { + name: string; + package?: string; + module?: string; +} + +interface CustomElementField { + kind: 'field'; + name: string; + description?: string; + type?: Type; + default?: string; + deprecated?: boolean | string; + readonly?: boolean; + attribute?: string; + reflects?: boolean; +} + +interface ClassMethod { + kind: 'method'; + name: string; + description?: string; + deprecated?: boolean | string; + parameters?: Parameter[]; + return?: { + type?: Type; + description?: string; + }; +} + +interface Parameter { + name: string; + description?: string; + type?: Type; +} + +interface Event { + name: string; + description?: string; + type: Type; + deprecated?: boolean | string; +} + +interface Slot { + name: string; + description?: string; +} + +interface CssPart { + name: string; + description?: string; +} + +/** + * Custom state that can be targeted with the CSS :state() pseudo-class. + * This is a custom extension to the CEM spec. + */ +interface CustomState { + name: string; + initialValue: boolean; + description?: string; +} + +interface CssCustomProperty { + name: string; + description?: string; +} diff --git a/src/compiler/docs/constants.ts b/packages/core/src/compiler/docs/constants.ts similarity index 100% rename from src/compiler/docs/constants.ts rename to packages/core/src/compiler/docs/constants.ts diff --git a/packages/core/src/compiler/docs/custom/index.ts b/packages/core/src/compiler/docs/custom/index.ts new file mode 100644 index 00000000000..3e800e404bd --- /dev/null +++ b/packages/core/src/compiler/docs/custom/index.ts @@ -0,0 +1,23 @@ +import type * as d from '@stencil/core'; + +import { isOutputTargetDocsCustom } from '../../../utils'; + +export const generateCustomDocs = async ( + config: d.ValidatedConfig, + docsData: d.JsonDocs, + outputTargets: d.OutputTarget[], +) => { + const customOutputTargets = outputTargets.filter(isOutputTargetDocsCustom); + if (customOutputTargets.length === 0) { + return; + } + await Promise.all( + customOutputTargets.map(async (customOutput) => { + try { + await customOutput.generator(docsData, config); + } catch (e) { + config.logger.error(`uncaught custom docs error: ${e}`); + } + }), + ); +}; diff --git a/src/compiler/docs/generate-doc-data.ts b/packages/core/src/compiler/docs/generate-doc-data.ts similarity index 95% rename from src/compiler/docs/generate-doc-data.ts rename to packages/core/src/compiler/docs/generate-doc-data.ts index c3e24aead63..7806d46a4f6 100644 --- a/src/compiler/docs/generate-doc-data.ts +++ b/packages/core/src/compiler/docs/generate-doc-data.ts @@ -1,3 +1,7 @@ +import { basename, dirname } from 'path'; +import { JsonDocsValue } from '@stencil/core'; +import type * as d from '@stencil/core'; + import { DEFAULT_STYLE_MODE, flatOne, @@ -7,12 +11,8 @@ import { relative, sortBy, unique, -} from '@utils'; -import { basename, dirname } from 'path'; - -import type * as d from '../../declarations'; -import { JsonDocsValue } from '../../declarations'; -import { typescriptVersion, version } from '../../version'; +} from '../../utils'; +import { version, versions } from '../../version'; import { getBuildTimestamp } from '../build/build-ctx'; import { addFileToLibrary, getTypeLibrary } from '../transformers/type-library'; import { AUTO_GENERATE_COMMENT } from './constants'; @@ -48,7 +48,7 @@ export const generateDocData = async ( compiler: { name: '@stencil/core', version, - typescriptVersion, + typescriptVersion: versions.typescript, }, components: await getDocsComponents(config, compilerCtx, buildCtx), typeLibrary, @@ -134,8 +134,8 @@ const buildDocsDepGraph = ( const dependencies: d.JsonDocsDependencyGraph = {}; function walk(tagName: string): void { if (!dependencies[tagName]) { - const cmp = cmps.find((c) => c.tagName === tagName); - const deps = cmp?.directDependencies; + const foundCmp = cmps.find((c) => c.tagName === tagName); + const deps = foundCmp?.directDependencies; if (deps?.length > 0) { dependencies[tagName] = deps; deps.forEach(walk); @@ -216,7 +216,9 @@ const getRealProperties = (properties: d.ComponentCompilerProperty[]): d.JsonDoc * @param virtualProps the component's virtual property metadata to derive JSDoc metadata from * @returns the derived metadata */ -const getVirtualProperties = (virtualProps: d.ComponentCompilerVirtualProperty[]): d.JsonDocsProp[] => { +const getVirtualProperties = ( + virtualProps: d.ComponentCompilerVirtualProperty[], +): d.JsonDocsProp[] => { return virtualProps.map( (member): d.JsonDocsProp => ({ name: member.name, @@ -336,13 +338,17 @@ export const getDocsStyles = (cmpMeta: d.ComponentCompilerMeta): d.JsonDocsStyle return sortBy( cmpMeta.styleDocs, - (compilerStyleDoc) => `${compilerStyleDoc.name.toLowerCase()},${compilerStyleDoc.mode.toLowerCase()}}`, + (compilerStyleDoc) => + `${compilerStyleDoc.name.toLowerCase()},${compilerStyleDoc.mode.toLowerCase()}}`, ).map((compilerStyleDoc) => { return { name: compilerStyleDoc.name, annotation: compilerStyleDoc.annotation || '', docs: compilerStyleDoc.docs || '', - mode: compilerStyleDoc.mode && compilerStyleDoc.mode !== DEFAULT_STYLE_MODE ? compilerStyleDoc.mode : undefined, + mode: + compilerStyleDoc.mode && compilerStyleDoc.mode !== DEFAULT_STYLE_MODE + ? compilerStyleDoc.mode + : undefined, }; }); }; @@ -437,7 +443,7 @@ export const getUserReadmeContent = async ( if (userContentIndex >= 0) { return existingContent.substring(0, userContentIndex); } - } catch (e) {} + } catch {} return undefined; }; @@ -497,7 +503,10 @@ const generateDocs = (readme: string | undefined, jsdoc: d.CompilerJsDoc): strin * @returns an object that maps the filename containing the usage example, to the file's contents. If an error occurs, * an empty object is returned. */ -const generateUsages = async (compilerCtx: d.CompilerCtx, usagesDir: string): Promise => { +const generateUsages = async ( + compilerCtx: d.CompilerCtx, + usagesDir: string, +): Promise => { const rtn: d.JsonDocsUsage = {}; try { @@ -529,7 +538,7 @@ const generateUsages = async (compilerCtx: d.CompilerCtx, usagesDir: string): Pr .forEach((key) => { rtn[key] = usages[key]; }); - } catch (e) {} + } catch {} return rtn; }; diff --git a/packages/core/src/compiler/docs/json/index.ts b/packages/core/src/compiler/docs/json/index.ts new file mode 100644 index 00000000000..af037a8bf87 --- /dev/null +++ b/packages/core/src/compiler/docs/json/index.ts @@ -0,0 +1,85 @@ +import type * as d from '@stencil/core'; + +import { isOutputTargetDocsJson, join } from '../../../utils'; + +export const generateJsonDocs = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + docsData: d.JsonDocs, + outputTargets: d.OutputTarget[], +) => { + const jsonOutputTargets = outputTargets.filter(isOutputTargetDocsJson); + if (jsonOutputTargets.length === 0) { + return; + } + const docsDtsPath = join( + config.sys.getCompilerExecutingPath(), + '..', + '..', + 'declarations', + 'stencil-public-docs.d.ts', + ); + let docsDts = await compilerCtx.fs.readFile(docsDtsPath); + // this file was written by dts-bundle-generator, which uses tabs for + // indentation. Instead, let's replace those with spaces! + docsDts = docsDts + .split('\n') + .map((line) => line.replace(/\t/g, ' ')) + .join('\n'); + + const typesContent = ` +/** + * This is an autogenerated file created by the Stencil compiler. + * DO NOT MODIFY IT MANUALLY + */ +${docsDts} +declare const _default: JsonDocs; +export default _default; +`; + + const json = { + ...docsData, + components: docsData.components.map((cmp) => ({ + filePath: cmp.filePath, + + encapsulation: cmp.encapsulation, + tag: cmp.tag, + readme: cmp.readme, + docs: cmp.docs, + docsTags: cmp.docsTags, + usage: cmp.usage, + props: cmp.props, + methods: cmp.methods, + events: cmp.events, + listeners: cmp.listeners, + styles: cmp.styles, + slots: cmp.slots, + parts: cmp.parts, + states: cmp.customStates, + dependents: cmp.dependents, + dependencies: cmp.dependencies, + dependencyGraph: cmp.dependencyGraph, + deprecation: cmp.deprecation, + })), + }; + const jsonContent = JSON.stringify(json, null, 2); + await Promise.all( + jsonOutputTargets.map((jsonOutput) => { + return writeDocsOutput(compilerCtx, jsonOutput, jsonContent, typesContent); + }), + ); +}; + +const writeDocsOutput = async ( + compilerCtx: d.CompilerCtx, + jsonOutput: d.OutputTargetDocsJson, + jsonContent: string, + typesContent: string, +) => { + return Promise.all([ + compilerCtx.fs.writeFile(jsonOutput.file, jsonContent), + jsonOutput.typesFile + ? compilerCtx.fs.writeFile(jsonOutput.typesFile, typesContent) + : (Promise.resolve() as any), + ]); +}; diff --git a/src/compiler/docs/readme/docs-util.ts b/packages/core/src/compiler/docs/readme/docs-util.ts similarity index 100% rename from src/compiler/docs/readme/docs-util.ts rename to packages/core/src/compiler/docs/readme/docs-util.ts diff --git a/packages/core/src/compiler/docs/readme/index.ts b/packages/core/src/compiler/docs/readme/index.ts new file mode 100644 index 00000000000..dc59b834174 --- /dev/null +++ b/packages/core/src/compiler/docs/readme/index.ts @@ -0,0 +1,59 @@ +import type * as d from '@stencil/core'; + +import { isOutputTargetDocsReadme } from '../../../utils'; +import { generateReadme } from './output-docs'; + +export const generateReadmeDocs = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + docsData: d.JsonDocs, + outputTargets: d.OutputTarget[], +) => { + const readmeOutputTargets = outputTargets.filter(isOutputTargetDocsReadme); + if (readmeOutputTargets.length === 0) { + return; + } + const strictCheck = readmeOutputTargets.some((o) => o.strict); + if (strictCheck) { + strictCheckDocs(config, docsData); + } + + await Promise.all( + docsData.components.map((cmpData) => { + return generateReadme(config, compilerCtx, readmeOutputTargets, cmpData, docsData.components); + }), + ); +}; + +const strictCheckDocs = (config: d.ValidatedConfig, docsData: d.JsonDocs) => { + docsData.components.forEach((component) => { + component.props.forEach((prop) => { + if (!prop.docs && prop.deprecation === undefined) { + config.logger.warn( + `Property "${prop.name}" of "${component.tag}" is not documented. ${component.filePath}`, + ); + } + }); + component.methods.forEach((method) => { + if (!method.docs && method.deprecation === undefined) { + config.logger.warn( + `Method "${method.name}" of "${component.tag}" is not documented. ${component.filePath}`, + ); + } + }); + component.events.forEach((ev) => { + if (!ev.docs && ev.deprecation === undefined) { + config.logger.warn( + `Event "${ev.event}" of "${component.tag}" is not documented. ${component.filePath}`, + ); + } + }); + component.parts.forEach((ev) => { + if (ev.docs === '') { + config.logger.warn( + `Part "${ev.name}" of "${component.tag}" is not documented. ${component.filePath}`, + ); + } + }); + }); +}; diff --git a/src/compiler/docs/readme/markdown-css-props.ts b/packages/core/src/compiler/docs/readme/markdown-css-props.ts similarity index 91% rename from src/compiler/docs/readme/markdown-css-props.ts rename to packages/core/src/compiler/docs/readme/markdown-css-props.ts index 2784d34b12c..77220a7a21e 100644 --- a/src/compiler/docs/readme/markdown-css-props.ts +++ b/packages/core/src/compiler/docs/readme/markdown-css-props.ts @@ -1,4 +1,5 @@ -import type * as d from '../../../declarations'; +import type * as d from '@stencil/core'; + import { MarkdownTable } from './docs-util'; export const stylesToMarkdown = (styles: d.JsonDocsStyle[]) => { diff --git a/packages/core/src/compiler/docs/readme/markdown-custom-states.ts b/packages/core/src/compiler/docs/readme/markdown-custom-states.ts new file mode 100644 index 00000000000..eeb0c9363c7 --- /dev/null +++ b/packages/core/src/compiler/docs/readme/markdown-custom-states.ts @@ -0,0 +1,37 @@ +import type * as d from '@stencil/core'; + +import { MarkdownTable } from './docs-util'; + +/** + * Converts a list of Custom States metadata to a table written in Markdown + * @param customStates the Custom States metadata to convert + * @returns a list of strings that make up the Markdown table + */ +export const customStatesToMarkdown = ( + customStates: d.JsonDocsCustomState[], +): ReadonlyArray => { + const content: string[] = []; + if (customStates.length === 0) { + return content; + } + + content.push(`## Custom States`); + content.push(``); + + const table = new MarkdownTable(); + table.addHeader(['State', 'Initial Value', 'Description']); + + customStates.forEach((state) => { + table.addRow([ + `\`:state(${state.name})\``, + state.initialValue ? '`true`' : '`false`', + state.docs, + ]); + }); + + content.push(...table.toMarkdown()); + content.push(``); + content.push(``); + + return content; +}; diff --git a/src/compiler/docs/readme/markdown-dependencies.ts b/packages/core/src/compiler/docs/readme/markdown-dependencies.ts similarity index 82% rename from src/compiler/docs/readme/markdown-dependencies.ts rename to packages/core/src/compiler/docs/readme/markdown-dependencies.ts index ab38410ea95..ed9310bca93 100644 --- a/src/compiler/docs/readme/markdown-dependencies.ts +++ b/packages/core/src/compiler/docs/readme/markdown-dependencies.ts @@ -1,8 +1,12 @@ -import { normalizePath, relative } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../../declarations'; +import { normalizePath, relative } from '../../../utils'; -export const depsToMarkdown = (cmp: d.JsonDocsComponent, cmps: d.JsonDocsComponent[], config: d.ValidatedConfig) => { +export const depsToMarkdown = ( + cmp: d.JsonDocsComponent, + cmps: d.JsonDocsComponent[], + config: d.ValidatedConfig, +) => { const content: string[] = []; const deps = Object.entries(cmp.dependencyGraph); @@ -35,8 +39,8 @@ export const depsToMarkdown = (cmp: d.JsonDocsComponent, cmps: d.JsonDocsCompone content.push(`### Graph`); content.push('```mermaid'); content.push('graph TD;'); - deps.forEach(([key, deps]) => { - deps.forEach((dep) => { + deps.forEach(([key, depList]) => { + depList.forEach((dep) => { content.push(` ${key} --> ${dep}`); }); }); diff --git a/src/compiler/docs/readme/markdown-events.ts b/packages/core/src/compiler/docs/readme/markdown-events.ts similarity index 94% rename from src/compiler/docs/readme/markdown-events.ts rename to packages/core/src/compiler/docs/readme/markdown-events.ts index cd754a63623..e3dfc1706bb 100644 --- a/src/compiler/docs/readme/markdown-events.ts +++ b/packages/core/src/compiler/docs/readme/markdown-events.ts @@ -1,4 +1,5 @@ -import type * as d from '../../../declarations'; +import type * as d from '@stencil/core'; + import { MarkdownTable } from './docs-util'; export const eventsToMarkdown = (events: d.JsonDocsEvent[]) => { diff --git a/src/compiler/docs/readme/markdown-methods.ts b/packages/core/src/compiler/docs/readme/markdown-methods.ts similarity index 96% rename from src/compiler/docs/readme/markdown-methods.ts rename to packages/core/src/compiler/docs/readme/markdown-methods.ts index 45f25fae862..dad1c4a2da8 100644 --- a/src/compiler/docs/readme/markdown-methods.ts +++ b/packages/core/src/compiler/docs/readme/markdown-methods.ts @@ -1,4 +1,5 @@ -import type * as d from '../../../declarations'; +import type * as d from '@stencil/core'; + import { MarkdownTable } from './docs-util'; export const methodsToMarkdown = (methods: d.JsonDocsMethod[]) => { diff --git a/src/compiler/docs/readme/markdown-overview.ts b/packages/core/src/compiler/docs/readme/markdown-overview.ts similarity index 100% rename from src/compiler/docs/readme/markdown-overview.ts rename to packages/core/src/compiler/docs/readme/markdown-overview.ts diff --git a/src/compiler/docs/readme/markdown-parts.ts b/packages/core/src/compiler/docs/readme/markdown-parts.ts similarity index 94% rename from src/compiler/docs/readme/markdown-parts.ts rename to packages/core/src/compiler/docs/readme/markdown-parts.ts index d105939191e..35507cddff2 100644 --- a/src/compiler/docs/readme/markdown-parts.ts +++ b/packages/core/src/compiler/docs/readme/markdown-parts.ts @@ -1,4 +1,5 @@ -import type * as d from '../../../declarations'; +import type * as d from '@stencil/core'; + import { MarkdownTable } from './docs-util'; /** diff --git a/src/compiler/docs/readme/markdown-props.ts b/packages/core/src/compiler/docs/readme/markdown-props.ts similarity index 96% rename from src/compiler/docs/readme/markdown-props.ts rename to packages/core/src/compiler/docs/readme/markdown-props.ts index 2da4ddea309..d21cdeed9a0 100644 --- a/src/compiler/docs/readme/markdown-props.ts +++ b/packages/core/src/compiler/docs/readme/markdown-props.ts @@ -1,4 +1,5 @@ -import type * as d from '../../../declarations'; +import type * as d from '@stencil/core'; + import { MarkdownTable } from './docs-util'; export const propsToMarkdown = (props: d.JsonDocsProp[]) => { diff --git a/src/compiler/docs/readme/markdown-slots.ts b/packages/core/src/compiler/docs/readme/markdown-slots.ts similarity index 93% rename from src/compiler/docs/readme/markdown-slots.ts rename to packages/core/src/compiler/docs/readme/markdown-slots.ts index 4c76c0ad212..b49f62ae886 100644 --- a/src/compiler/docs/readme/markdown-slots.ts +++ b/packages/core/src/compiler/docs/readme/markdown-slots.ts @@ -1,4 +1,5 @@ -import type * as d from '../../../declarations'; +import type * as d from '@stencil/core'; + import { MarkdownTable } from './docs-util'; /** diff --git a/src/compiler/docs/readme/markdown-usage.ts b/packages/core/src/compiler/docs/readme/markdown-usage.ts similarity index 83% rename from src/compiler/docs/readme/markdown-usage.ts rename to packages/core/src/compiler/docs/readme/markdown-usage.ts index fcfce66bfd1..baeec125d17 100644 --- a/src/compiler/docs/readme/markdown-usage.ts +++ b/packages/core/src/compiler/docs/readme/markdown-usage.ts @@ -1,6 +1,6 @@ -import { toTitleCase } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../../declarations'; +import { toTitleCase } from '../../../utils'; export const usageToMarkdown = (usages: d.JsonDocsUsage) => { const content: string[] = []; @@ -17,14 +17,14 @@ export const usageToMarkdown = (usages: d.JsonDocsUsage) => { content.push(''); content.push(text); content.push(''); - }), - content.push(''); + }); + content.push(''); content.push(''); return content; }; -export const mergeUsages = (usages: d.JsonDocsUsage) => { +const mergeUsages = (usages: d.JsonDocsUsage) => { const keys = Object.keys(usages); const map = new Map(); keys.forEach((key) => { diff --git a/packages/core/src/compiler/docs/readme/output-docs.ts b/packages/core/src/compiler/docs/readme/output-docs.ts new file mode 100644 index 00000000000..d20e949d9ef --- /dev/null +++ b/packages/core/src/compiler/docs/readme/output-docs.ts @@ -0,0 +1,212 @@ +import type * as d from '@stencil/core'; + +import { join, normalizePath, relative } from '../../../utils'; +import { AUTO_GENERATE_COMMENT } from '../constants'; +import { getUserReadmeContent } from '../generate-doc-data'; +import { stylesToMarkdown } from './markdown-css-props'; +import { customStatesToMarkdown } from './markdown-custom-states'; +import { depsToMarkdown } from './markdown-dependencies'; +import { eventsToMarkdown } from './markdown-events'; +import { methodsToMarkdown } from './markdown-methods'; +import { overviewToMarkdown } from './markdown-overview'; +import { partsToMarkdown } from './markdown-parts'; +import { propsToMarkdown } from './markdown-props'; +import { slotsToMarkdown } from './markdown-slots'; +import { usageToMarkdown } from './markdown-usage'; + +/** + * Generate a README for a given component and write it to disk. + * + * Typically the README is going to be a 'sibling' to the component's source + * code (i.e. written to the same directory) but the user may also configure a + * custom output directory by setting {@link d.OutputTargetDocsReadme.dir}. + * + * Output readme files also include {@link AUTO_GENERATE_COMMENT}, and any + * text located _above_ that comment is preserved when the new readme is written + * to disk. + * + * @param config a validated Stencil config + * @param compilerCtx the current compiler context + * @param readmeOutputs docs-readme output targets + * @param docsData documentation data for the component of interest + * @param cmps metadata for all the components in the project + */ +export const generateReadme = async ( + config: d.ValidatedConfig, + compilerCtx: d.CompilerCtx, + readmeOutputs: d.OutputTargetDocsReadme[], + docsData: d.JsonDocsComponent, + cmps: d.JsonDocsComponent[], +) => { + const isUpdate = !!docsData.readme; + const userContent = isUpdate ? docsData.readme : getDefaultReadme(docsData); + + await Promise.all( + readmeOutputs.map(async (readmeOutput) => { + if (readmeOutput.dir) { + const relativeReadmePath = relative(config.srcDir, docsData.readmePath); + const readmeOutputPath = join(readmeOutput.dir, relativeReadmePath); + + const currentReadmeContent = + readmeOutput.overwriteExisting === true + ? // Overwrite explicitly requested: always use the provided user content. + userContent + : normalizePath(readmeOutput.dir) !== normalizePath(config.srcDir) + ? (readmeOutput.overwriteExisting === 'if-missing' && + // Validate a file exists at the output path + (await compilerCtx.fs.access(readmeOutputPath))) || + // False and undefined case: follow the changes made in #5648 + (readmeOutput.overwriteExisting ?? false) === false + ? // Existing file found: The user set a custom `.dir` property, which is + // where we're going to write the updated README. We need to read the + // non-automatically generated content from that file and preserve that. + await getUserReadmeContent(compilerCtx, readmeOutputPath) + : // No existing file found: use the provided user content. + userContent + : // Default case: writing to srcDir, so use the provided user content. + userContent; + + // CSS Custom Properties preservation is now handled centrally in outputDocs + const readmeContent = generateMarkdown( + currentReadmeContent, + docsData, + cmps, + readmeOutput, + config, + ); + + const results = await compilerCtx.fs.writeFile(readmeOutputPath, readmeContent); + if (results.changedContent) { + if (isUpdate) { + config.logger.info(`updated readme docs: ${docsData.tag}`); + } else { + config.logger.info(`created readme docs: ${docsData.tag}`); + } + } + } + }), + ); +}; + +export const generateMarkdown = ( + userContent: string | undefined, + cmp: d.JsonDocsComponent, + cmps: d.JsonDocsComponent[], + readmeOutput: d.OutputTargetDocsReadme, + config?: d.ValidatedConfig, +) => { + //If the readmeOutput.dependencies is true or undefined the dependencies will be generated. + const dependencies = readmeOutput.dependencies !== false ? depsToMarkdown(cmp, cmps, config) : []; + + return [ + userContent || '', + AUTO_GENERATE_COMMENT, + '', + '', + ...getDocsDeprecation(cmp), + ...overviewToMarkdown(cmp.overview), + ...usageToMarkdown(cmp.usage), + ...propsToMarkdown(cmp.props), + ...eventsToMarkdown(cmp.events), + ...methodsToMarkdown(cmp.methods), + ...slotsToMarkdown(cmp.slots), + ...partsToMarkdown(cmp.parts), + ...customStatesToMarkdown(cmp.customStates), + ...stylesToMarkdown(cmp.styles), + ...dependencies, + `----------------------------------------------`, + '', + readmeOutput.footer, + '', + ].join('\n'); +}; + +const getDocsDeprecation = (cmp: d.JsonDocsComponent) => { + if (cmp.deprecation !== undefined) { + return [`> **[DEPRECATED]** ${cmp.deprecation}`, '']; + } + return []; +}; + +/** + * Get a minimal default README for a Stencil component + * + * @param docsData documentation data for the component of interest + * @returns a minimal README template for that component + */ +const getDefaultReadme = (docsData: d.JsonDocsComponent) => { + return [`# ${docsData.tag}`, '', '', ''].join('\n'); +}; + +/** + * Extract the existing CSS Custom Properties section from a README file. + * This is used to preserve CSS props documentation when running `stencil docs` + * without building styles. + * + * @param compilerCtx the current compiler context + * @param readmePath the path to the README file to read + * @returns array of CSS custom properties styles, or undefined if none found + */ +export const extractExistingCssProps = async ( + compilerCtx: d.CompilerCtx, + readmePath: string, +): Promise => { + try { + const existingContent = await compilerCtx.fs.readFile(readmePath); + + // Find the CSS Custom Properties section + const cssPropsSectionMatch = existingContent.match( + /## CSS Custom Properties\s*\n\s*\n([\s\S]*?)(?=\n##|\n-{4,}|$)/, + ); + if (!cssPropsSectionMatch) { + return undefined; + } + + const cssPropsSection = cssPropsSectionMatch[1]; + const styles: d.JsonDocsStyle[] = []; + + // Parse the markdown table to extract CSS custom properties + // Table format: + // | Name | Description | + // | ---- | ----------- | + // | `--prop-name` | Description text | + const lines = cssPropsSection.split('\n'); + let inTable = false; + + for (const line of lines) { + const trimmedLine = line.trim(); + + // Skip header and separator rows + if (trimmedLine.startsWith('| Name') || trimmedLine.startsWith('| ---')) { + inTable = true; + continue; + } + + // Parse table rows + if (inTable && trimmedLine.startsWith('|')) { + const parts = trimmedLine + .split('|') + .map((p) => p.trim()) + .filter((p) => p); + if (parts.length >= 2) { + // Extract the CSS variable name (remove backticks) + const name = parts[0].replace(/`/g, '').trim(); + const docs = parts[1].trim(); + + if (name.startsWith('--')) { + styles.push({ + name, + docs, + annotation: 'prop', + mode: undefined, + }); + } + } + } + } + + return styles.length > 0 ? styles : undefined; + } catch { + return undefined; + } +}; diff --git a/src/compiler/docs/style-docs.ts b/packages/core/src/compiler/docs/style-docs.ts similarity index 90% rename from src/compiler/docs/style-docs.ts rename to packages/core/src/compiler/docs/style-docs.ts index c037fd64ea5..c38c89135e1 100644 --- a/src/compiler/docs/style-docs.ts +++ b/packages/core/src/compiler/docs/style-docs.ts @@ -1,4 +1,4 @@ -import type * as d from '../../declarations'; +import type * as d from '@stencil/core'; /** * Parse CSS docstrings that Stencil supports, as documented here: @@ -11,7 +11,11 @@ import type * as d from '../../declarations'; * @param styleText the CSS text we're working with * @param mode a mode associated with the parsed style, if applicable (e.g. this is not applicable for global styles) */ -export function parseStyleDocs(styleDocs: d.StyleDoc[], styleText: string | null, mode?: string | undefined) { +export function parseStyleDocs( + styleDocs: d.StyleDoc[], + styleText: string | null, + mode?: string | undefined, +) { if (typeof styleText !== 'string') { return; } @@ -44,9 +48,8 @@ export function parseStyleDocs(styleDocs: d.StyleDoc[], styleText: string | null * @param mode a mode associated with the parsed style, if applicable (e.g. this is not applicable for global styles) */ function parseCssComment(styleDocs: d.StyleDoc[], comment: string, mode: string | undefined): void { - /** - * @prop --max-width: Max width of the alert - */ + // Example of what these comments might look like: + // @property --max-width: Max width of the alert // (the above is an example of what these comments might look like) const lines = comment.split(/\r?\n/).map((line) => { @@ -92,7 +95,7 @@ function parseCssComment(styleDocs: d.StyleDoc[], comment: string, mode: string * Opening syntax for a CSS docstring. * This will match a traditional docstring or a "loud" comment in sass */ -const CSS_DOC_START = /\/\*(\*|\!)/; +const CSS_DOC_START = /\/\*(\*|!)/; /** * Closing syntax for a CSS docstring */ diff --git a/packages/core/src/compiler/docs/vscode/index.ts b/packages/core/src/compiler/docs/vscode/index.ts new file mode 100644 index 00000000000..5f082c6868d --- /dev/null +++ b/packages/core/src/compiler/docs/vscode/index.ts @@ -0,0 +1,145 @@ +import type * as d from '@stencil/core'; + +import { isOutputTargetDocsVscode, join } from '../../../utils'; +import { getNameText } from '../generate-doc-data'; + +/** + * Generate [custom data](https://github.com/microsoft/vscode-custom-data) to augment existing HTML types in VS Code. + * This function writes the custom data as a JSON file to disk, which can be used in VS Code to inform the IDE about + * custom elements generated by Stencil. + * + * The JSON generated by this function must conform to the + * [HTML custom data schema](https://github.com/microsoft/vscode-html-languageservice/blob/e7ae8a7170df5e721a13cee1b86e293b24eb3b20/docs/customData.schema.json). + * + * This function generates custom data for HTML only at this time (it does not generate custom data for CSS). + * + * @param compilerCtx the current compiler context + * @param docsData an intermediate representation documentation derived from compiled Stencil components + * @param outputTargets the output target(s) the associated with the current build + */ +export const generateVscodeDocs = async ( + compilerCtx: d.CompilerCtx, + docsData: d.JsonDocs, + outputTargets: d.OutputTarget[], +): Promise => { + const vsCodeOutputTargets = outputTargets.filter(isOutputTargetDocsVscode); + if (vsCodeOutputTargets.length === 0) { + return; + } + + await Promise.all( + vsCodeOutputTargets.map(async (outputTarget: d.OutputTargetDocsVscode): Promise => { + const json = { + /** + * the 'version' top-level field is required by the schema. changes to the JSON generated by Stencil must: + * - comply with v1.X of the schema _OR_ + * - increment this field as a part of updating the JSON generation. This should be considered a breaking change + * + * {@link https://github.com/microsoft/vscode-html-languageservice/blob/e7ae8a7170df5e721a13cee1b86e293b24eb3b20/src/htmlLanguageTypes.ts#L184} + */ + version: 1.1, + tags: docsData.components.map((cmp: d.JsonDocsComponent) => ({ + name: cmp.tag, + description: { + kind: 'markdown', + value: cmp.docs, + }, + attributes: cmp.props + .filter( + (p: d.JsonDocsProp): p is DocPropWithAttribute => + p.attr !== undefined && p.attr.length > 0, + ) + .map(serializeAttribute), + references: getReferences(cmp, outputTarget.sourceCodeBaseUrl), + })), + }; + + // fields in the custom data may have a value of `undefined`. calling `stringify` will remove such fields. + const jsonContent = JSON.stringify(json, null, 2); + await compilerCtx.fs.writeFile(outputTarget.file, jsonContent); + }), + ); +}; + +/** + * This type describes external references for a custom element. + * + * An internal representation of Microsoft/VS Code's [`IReference` type](https://github.com/microsoft/vscode-html-languageservice/blob/e7ae8a7170df5e721a13cee1b86e293b24eb3b20/src/htmlLanguageTypes.ts#L153). + */ +type TagReference = { + name: string; + url: string; +}; + +/** + * Generate a 'references' section for a component's documentation. + * @param cmp the Stencil component to generate a references section for + * @param repoBaseUrl an optional URL, that when provided, will add a reference to the source code for the component + * @returns the generated references section, or undefined if no references could be generated + */ +const getReferences = ( + cmp: d.JsonDocsComponent, + repoBaseUrl: string | undefined, +): TagReference[] | undefined => { + // collect any `@reference` JSDoc tags on the component + const references = getNameText('reference', cmp.docsTags).map(([name, url]) => ({ name, url })); + + if (repoBaseUrl) { + references.push({ + name: 'Source code', + url: join(repoBaseUrl, cmp.filePath ?? ''), + }); + } + if (references.length > 0) { + return references; + } + return undefined; +}; + +/** + * A type that describes the attributes that can be used with a custom element. + * + * An internal representation of Microsoft/VS Code's [`IAttributeData` type](https://github.com/microsoft/vscode-html-languageservice/blob/e7ae8a7170df5e721a13cee1b86e293b24eb3b20/src/htmlLanguageTypes.ts#L165). + */ +type AttributeData = { + name: string; + description: string; + values?: { name: string }[]; +}; + +/** + * Utility that provides a type-safe way of making a key K on a type T required. + * + * This is preferable than using an intersection of `T & {K: someType}` as it ensures that: + * - the type of K will always match the type T[K] + * - it should error should K not exist in `keyof T` + */ +type WithRequired = T & { [P in K]-?: T[P] }; + +/** + * A `@Prop` documentation type with a required 'attr' field + */ +type DocPropWithAttribute = WithRequired; + +/** + * Serialize a component's class member decorated with `@Prop` to be written to disk + * @param prop the intermediate representation of the documentation to serialize + * @returns the serialized data + */ +const serializeAttribute = (prop: DocPropWithAttribute): AttributeData => { + const attribute: AttributeData = { + name: prop.attr, + description: prop.docs, + }; + const values = prop.values + .filter( + (jsonDocValue: d.JsonDocsValue): jsonDocValue is Required => + jsonDocValue.type === 'string' && jsonDocValue.value !== undefined, + ) + .map((jsonDocValue: Required) => ({ name: jsonDocValue.value })); + + if (values.length > 0) { + attribute.values = values; + } + return attribute; +}; diff --git a/src/compiler/entries/component-bundles.ts b/packages/core/src/compiler/entries/component-bundles.ts similarity index 95% rename from src/compiler/entries/component-bundles.ts rename to packages/core/src/compiler/entries/component-bundles.ts index cc39a63c845..c87d3542540 100644 --- a/src/compiler/entries/component-bundles.ts +++ b/packages/core/src/compiler/entries/component-bundles.ts @@ -1,6 +1,6 @@ -import { sortBy } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { sortBy } from '../../utils'; import { getDefaultBundles } from './default-bundles'; /** @@ -65,7 +65,10 @@ export function generateComponentBundles( config: d.ValidatedConfig, buildCtx: d.BuildCtx, ): readonly d.ComponentCompilerMeta[][] { - const components = sortBy(buildCtx.components, (cmp: d.ComponentCompilerMeta) => cmp.dependents.length); + const components = sortBy( + buildCtx.components, + (cmp: d.ComponentCompilerMeta) => cmp.dependents.length, + ); const defaultBundles = getDefaultBundles(config, buildCtx, components); // this is most likely all the components @@ -85,7 +88,9 @@ export function generateComponentBundles( }); const bundlers: readonly d.ComponentCompilerMeta[][] = components - .filter((cmp: d.ComponentCompilerMeta) => usedComponents.has(cmp.tagName) && !alreadyBundled.has(cmp)) + .filter( + (cmp: d.ComponentCompilerMeta) => usedComponents.has(cmp.tagName) && !alreadyBundled.has(cmp), + ) .map((c: d.ComponentCompilerMeta) => [c]); return [...defaultBundles, ...optimizeBundlers(bundlers, 0.6)].filter( diff --git a/packages/core/src/compiler/entries/component-graph.ts b/packages/core/src/compiler/entries/component-graph.ts new file mode 100644 index 00000000000..084366c2709 --- /dev/null +++ b/packages/core/src/compiler/entries/component-graph.ts @@ -0,0 +1,19 @@ +import type * as d from '@stencil/core'; + +import { getScopeId } from '../style/scope-css'; + +export const generateModuleGraph = ( + cmps: d.ComponentCompilerMeta[], + bundleModules: ReadonlyArray, +) => { + const cmpMap = new Map(); + cmps.forEach((cmp) => { + const bundle = bundleModules.find((b) => b.cmps.includes(cmp)); + if (bundle) { + // add default case for no mode + cmpMap.set(getScopeId(cmp.tagName), bundle.rolldownResult.imports); + } + }); + + return cmpMap; +}; diff --git a/src/compiler/entries/default-bundles.ts b/packages/core/src/compiler/entries/default-bundles.ts similarity index 96% rename from src/compiler/entries/default-bundles.ts rename to packages/core/src/compiler/entries/default-bundles.ts index f9b88322268..c617747b32b 100644 --- a/src/compiler/entries/default-bundles.ts +++ b/packages/core/src/compiler/entries/default-bundles.ts @@ -1,6 +1,6 @@ -import { buildError, buildWarn, flatOne, unique, validateComponentTag } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { buildError, buildWarn, flatOne, unique, validateComponentTag } from '../../utils'; import { getUsedComponents } from '../html/used-components'; /** @@ -50,7 +50,7 @@ export function getDefaultBundles( * @param cmps the components that have been registered & defined for the current build * @returns a three dimensional array with the compiler metadata for each component used */ -export function getUserConfigBundles( +function getUserConfigBundles( config: d.ValidatedConfig, buildCtx: d.BuildCtx, cmps: d.ComponentCompilerMeta[], diff --git a/src/compiler/entries/resolve-component-dependencies.ts b/packages/core/src/compiler/entries/resolve-component-dependencies.ts similarity index 93% rename from src/compiler/entries/resolve-component-dependencies.ts rename to packages/core/src/compiler/entries/resolve-component-dependencies.ts index a054fa29143..e6f657e552e 100644 --- a/src/compiler/entries/resolve-component-dependencies.ts +++ b/packages/core/src/compiler/entries/resolve-component-dependencies.ts @@ -1,6 +1,6 @@ -import { flatOne, unique } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { flatOne, unique } from '../../utils'; /** * For each entry in the provided collection of compiler metadata, generate several lists: @@ -84,7 +84,9 @@ function resolveTransitiveDependencies( visited.add(cmp); // create a collection of dependencies of web components that the build knows about - const dependencies = unique(cmp.potentialCmpRefs.filter((tagName) => cmps.some((c) => c.tagName === tagName))); + const dependencies = unique( + cmp.potentialCmpRefs.filter((tagName) => cmps.some((c) => c.tagName === tagName)), + ); cmp.dependencies = cmp.directDependencies = dependencies; @@ -111,7 +113,10 @@ function resolveTransitiveDependencies( * @param cmp the metadata for the component whose dependents are being calculated * @param cmps the metadata for all components that participate in the current build */ -function resolveTransitiveDependents(cmp: d.ComponentCompilerMeta, cmps: d.ComponentCompilerMeta[]): void { +function resolveTransitiveDependents( + cmp: d.ComponentCompilerMeta, + cmps: d.ComponentCompilerMeta[], +): void { // the dependents of a component are any other components that list it as a direct or transitive dependency cmp.dependents = cmps .filter((c) => c.dependencies.includes(cmp.tagName)) diff --git a/packages/core/src/compiler/events.ts b/packages/core/src/compiler/events.ts new file mode 100644 index 00000000000..f8e850de5a2 --- /dev/null +++ b/packages/core/src/compiler/events.ts @@ -0,0 +1,73 @@ +import type * as d from '@stencil/core'; + +export const buildEvents = (): d.BuildEvents => { + const evCallbacks: EventCallback[] = []; + + const off = (callback: any) => { + const index = evCallbacks.findIndex((ev) => ev.callback === callback); + if (index > -1) { + evCallbacks.splice(index, 1); + return true; + } + return false; + }; + + const on = (arg0: any, arg1?: any): d.BuildOnEventRemove => { + if (typeof arg0 === 'function') { + const eventName: null = null; + const callback = arg0; + evCallbacks.push({ + eventName, + callback, + }); + return () => off(callback); + } else if (typeof arg0 === 'string' && typeof arg1 === 'function') { + const eventName = arg0.toLowerCase().trim(); + const callback = arg1; + + evCallbacks.push({ + eventName, + callback, + }); + + return () => off(callback); + } + return () => false; + }; + + const emit = (eventName: d.CompilerEventName, data: any) => { + const normalizedEventName = eventName.toLowerCase().trim(); + const callbacks = evCallbacks.slice(); + + for (const ev of callbacks) { + if (ev.eventName == null) { + try { + ev.callback(eventName, data); + } catch (e) { + console.error(e); + } + } else if (ev.eventName === normalizedEventName) { + try { + ev.callback(data); + } catch (e) { + console.error(e); + } + } + } + }; + + const unsubscribeAll = () => { + evCallbacks.length = 0; + }; + + return { + emit, + on, + unsubscribeAll, + }; +}; + +interface EventCallback { + eventName: string | null; + callback: Function; +} diff --git a/packages/core/src/compiler/fs-watch/fs-watch-rebuild.ts b/packages/core/src/compiler/fs-watch/fs-watch-rebuild.ts new file mode 100644 index 00000000000..ea372c5ed70 --- /dev/null +++ b/packages/core/src/compiler/fs-watch/fs-watch-rebuild.ts @@ -0,0 +1,156 @@ +import { basename } from 'path'; +import type * as d from '@stencil/core'; + +import { + getComponentsDtsSrcFilePath, + isOutputTargetDocsJson, + isOutputTargetDocsVscode, + isOutputTargetStats, + isString, + unique, +} from '../../utils'; + +export const filesChanged = (buildCtx: d.BuildCtx) => { + // files changed include updated, added and deleted + return unique([ + ...buildCtx.filesUpdated, + ...buildCtx.filesAdded, + ...buildCtx.filesDeleted, + ]).sort(); +}; + +/** + * Unary helper function mapping string to string and wrapping `basename`, + * which normally takes two string arguments. This means it cannot be passed + * to `Array.prototype.map`, but this little helper can! + * + * @param filePath a filepath to check out + * @returns the basename for that filepath + */ +const unaryBasename = (filePath: string): string => basename(filePath); + +/** + * Get the file extension for a path + * + * @param filePath a path + * @returns the file extension (well, characters after the last `'.'`) or + * `null` if no extension exists. + */ +const getExt = (filePath: string): string | null => { + const fileParts = filePath.split('.'); + + return fileParts.length > 1 ? fileParts.pop()!.toLowerCase() : null; +}; + +/** + * Script extensions which we want to be able to recognize + */ +const SCRIPT_EXT = ['ts', 'tsx', 'js', 'jsx']; + +/** + * Helper to check if a filepath has a script extension + * + * @param filePath a file extension + * @returns whether the filepath has a script extension or not + */ +const hasScriptExt = (filePath: string): boolean => { + const ext = getExt(filePath); + + return ext ? SCRIPT_EXT.includes(ext) : false; +}; + +const STYLE_EXT = ['css', 'scss', 'sass', 'pcss', 'styl', 'stylus', 'less']; + +/** + * Helper to check if a filepath has a style extension + * + * @param filePath a file extension to check + * @returns whether the filepath has a style extension or not + */ +const hasStyleExt = (filePath: string): boolean => { + const ext = getExt(filePath); + + return ext ? STYLE_EXT.includes(ext) : false; +}; + +/** + * Get all scripts from a build context that were added + * + * @param buildCtx the build context + * @returns an array of filepaths that were added + */ +export const scriptsAdded = (buildCtx: d.BuildCtx): string[] => + buildCtx.filesAdded.filter(hasScriptExt).map(unaryBasename); + +/** + * Get all scripts from a build context that were deleted + * + * @param buildCtx the build context + * @returns an array of deleted filepaths + */ +export const scriptsDeleted = (buildCtx: d.BuildCtx): string[] => + buildCtx.filesDeleted.filter(hasScriptExt).map(unaryBasename); + +/** + * Check whether a build has script changes + * + * @param buildCtx the build context + * @returns whether or not there are script changes + */ +export const hasScriptChanges = (buildCtx: d.BuildCtx): boolean => + buildCtx.filesChanged.some(hasScriptExt); + +/** + * Check whether a build has style changes + * + * @param buildCtx the build context + * @returns whether or not there are style changes + */ +export const hasStyleChanges = (buildCtx: d.BuildCtx): boolean => + buildCtx.filesChanged.some(hasStyleExt); + +/** + * Returns true if any HTML file under `srcDir` changed. + * HTML outside srcDir (e.g. test fixtures) has no effect on compiled output. + * @param config the Stencil configuration + * @param buildCtx the current build context + * @returns whether or not there are HTML changes under srcDir + */ +export const hasHtmlChanges = (config: d.ValidatedConfig, buildCtx: d.BuildCtx): boolean => { + const srcDirPrefix = config.srcDir + '/'; + return buildCtx.filesChanged.some( + (f) => f.toLowerCase().endsWith('.html') && f.startsWith(srcDirPrefix), + ); +}; + +/** + * Checks if a path is ignored by the watch configuration + * + * @param config The validated config for the Stencil project + * @param path The path to check + * @returns Whether the path is ignored by the watch configuration + */ +export const isWatchIgnorePath = (config: d.ValidatedConfig, path: string) => { + if (!isString(path)) { + return false; + } + + const isWatchIgnore = (config.watchIgnoredRegex as RegExp[]).some((reg) => reg.test(path)); + if (isWatchIgnore) { + return true; + } + const outputTargets = config.outputTargets; + const ignoreFiles = [ + // Ignore components.d.ts — its disk write would cascade-rebuild all components. + getComponentsDtsSrcFilePath(config), + ...outputTargets.filter(isOutputTargetDocsJson).map((o) => o.file), + ...outputTargets.filter(isOutputTargetDocsJson).map((o) => o.typesFile), + ...outputTargets.filter(isOutputTargetStats).map((o) => o.file), + ...outputTargets.filter(isOutputTargetDocsVscode).map((o) => o.file), + ]; + if (ignoreFiles.includes(path)) { + return true; + } + + return false; +}; diff --git a/src/compiler/html/test/remove-unused-styles.spec.ts b/packages/core/src/compiler/html/_test_/remove-unused-styles.spec.ts similarity index 95% rename from src/compiler/html/test/remove-unused-styles.spec.ts rename to packages/core/src/compiler/html/_test_/remove-unused-styles.spec.ts index 9bd9fa0aec0..9797f674a51 100644 --- a/src/compiler/html/test/remove-unused-styles.spec.ts +++ b/packages/core/src/compiler/html/_test_/remove-unused-styles.spec.ts @@ -1,10 +1,11 @@ -import type * as d from '@stencil/core/declarations'; import { mockDocument } from '@stencil/core/testing'; +import { describe, expect, it, beforeEach } from 'vitest'; +import type * as d from '@stencil/core'; import { removeUnusedStyles } from '../remove-unused-styles'; describe('removeUnusedStyles', () => { - let results: d.HydrateResults; + let results: d.SsrResults; beforeEach(() => { results = { @@ -281,12 +282,20 @@ describe('removeUnusedStyles', () => { }); function expectSelector(css: string, selector: string) { - selector = selector.replace(/ \{ /g, '{').replace(/ \} /g, '}').replace(/\: /g, ':').replace(/\; /g, ';'); + selector = selector + .replace(/ \{ /g, '{') + .replace(/ \} /g, '}') + .replace(/: /g, ':') + .replace(/; /g, ';'); expect(css).toContain(selector); } function expectNoSelector(css: string, selector: string) { - selector = selector.replace(/ \{ /g, '{').replace(/ \} /g, '}').replace(/\: /g, ':').replace(/\; /g, ';'); + selector = selector + .replace(/ \{ /g, '{') + .replace(/ \} /g, '}') + .replace(/: /g, ':') + .replace(/; /g, ';'); expect(css).not.toContain(selector); } }); diff --git a/src/compiler/html/test/update-esm-import-paths.spec.ts b/packages/core/src/compiler/html/_test_/update-esm-import-paths.spec.ts similarity index 98% rename from src/compiler/html/test/update-esm-import-paths.spec.ts rename to packages/core/src/compiler/html/_test_/update-esm-import-paths.spec.ts index 013e14c8749..0ebc562f1b0 100644 --- a/src/compiler/html/test/update-esm-import-paths.spec.ts +++ b/packages/core/src/compiler/html/_test_/update-esm-import-paths.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { updateImportPaths } from '../inline-esm-import'; describe('updateImportPaths', () => { diff --git a/packages/core/src/compiler/html/add-script-attr.ts b/packages/core/src/compiler/html/add-script-attr.ts new file mode 100644 index 00000000000..7fd8c271f6c --- /dev/null +++ b/packages/core/src/compiler/html/add-script-attr.ts @@ -0,0 +1,27 @@ +import type * as d from '@stencil/core'; + +import { join } from '../../utils'; +import { getAbsoluteBuildDir } from './html-utils'; + +export const addScriptDataAttribute = ( + config: d.ValidatedConfig, + doc: Document, + outputTarget: d.OutputTargetWww, +) => { + const resourcesUrl = getAbsoluteBuildDir(outputTarget); + const entryEsmFilename = `${config.fsNamespace}.js`; + const entryNoModuleFilename = `${config.fsNamespace}.js`; + const expectedEsmSrc = join(resourcesUrl, entryEsmFilename); + const expectedNoModuleSrc = join(resourcesUrl, entryNoModuleFilename); + + const scripts = Array.from(doc.querySelectorAll('script')); + const scriptEsm = scripts.find((s) => s.getAttribute('src') === expectedEsmSrc); + const scriptNomodule = scripts.find((s) => s.getAttribute('src') === expectedNoModuleSrc); + + if (scriptEsm) { + scriptEsm.setAttribute('data-stencil', ''); + } + if (scriptNomodule) { + scriptNomodule.setAttribute('data-stencil', ''); + } +}; diff --git a/src/compiler/html/canonical-link.ts b/packages/core/src/compiler/html/canonical-link.ts similarity index 100% rename from src/compiler/html/canonical-link.ts rename to packages/core/src/compiler/html/canonical-link.ts diff --git a/src/compiler/html/html-utils.ts b/packages/core/src/compiler/html/html-utils.ts similarity index 83% rename from src/compiler/html/html-utils.ts rename to packages/core/src/compiler/html/html-utils.ts index 9953030f6cd..5724fa8b7f0 100644 --- a/src/compiler/html/html-utils.ts +++ b/packages/core/src/compiler/html/html-utils.ts @@ -1,6 +1,6 @@ -import { join, relative } from '@utils'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { join, relative } from '../../utils'; /** * Get the path to the build directory where files written for the `www` output diff --git a/packages/core/src/compiler/html/inject-module-preloads.ts b/packages/core/src/compiler/html/inject-module-preloads.ts new file mode 100644 index 00000000000..5653da4933a --- /dev/null +++ b/packages/core/src/compiler/html/inject-module-preloads.ts @@ -0,0 +1,43 @@ +import type * as d from '@stencil/core'; + +import { join } from '../../utils'; +import { getAbsoluteBuildDir } from './html-utils'; + +export const optimizeCriticalPath = ( + doc: Document, + criticalBundlers: string[], + outputTarget: d.OutputTargetWww, +) => { + const buildDir = getAbsoluteBuildDir(outputTarget); + const paths = criticalBundlers.map((path) => join(buildDir, path)); + injectModulePreloads(doc, paths); +}; + +export const injectModulePreloads = (doc: Document, paths: string[]) => { + const existingLinks = ( + Array.from(doc.querySelectorAll('link[rel=modulepreload]')) as HTMLLinkElement[] + ).map((link) => link.getAttribute('href')); + + const addLinks = paths + .filter((path) => !existingLinks.includes(path)) + .map((path) => createModulePreload(doc, path)); + + const head = doc.head; + const firstScript = head.querySelector('script'); + if (firstScript) { + for (const link of addLinks) { + head.insertBefore(link, firstScript); + } + } else { + for (const link of addLinks) { + head.appendChild(link); + } + } +}; + +const createModulePreload = (doc: Document, href: string) => { + const link = doc.createElement('link'); + link.setAttribute('rel', 'modulepreload'); + link.setAttribute('href', href); + return link; +}; diff --git a/src/compiler/html/inject-sw-script.ts b/packages/core/src/compiler/html/inject-sw-script.ts similarity index 80% rename from src/compiler/html/inject-sw-script.ts rename to packages/core/src/compiler/html/inject-sw-script.ts index 1f892d8027f..dbc126adf2c 100644 --- a/src/compiler/html/inject-sw-script.ts +++ b/packages/core/src/compiler/html/inject-sw-script.ts @@ -1,4 +1,5 @@ -import type * as d from '../../declarations'; +import type * as d from '@stencil/core'; + import { getRegisterSW, UNREGISTER_SW } from '../service-worker/generate-sw'; import { generateServiceWorkerUrl } from '../service-worker/service-worker-util'; @@ -19,8 +20,15 @@ export const updateIndexHtmlServiceWorker = async ( } }; -const injectRegisterServiceWorker = async (buildCtx: d.BuildCtx, outputTarget: d.OutputTargetWww, doc: Document) => { - const swUrl = generateServiceWorkerUrl(outputTarget, outputTarget.serviceWorker as d.ServiceWorkerConfig); +const injectRegisterServiceWorker = async ( + buildCtx: d.BuildCtx, + outputTarget: d.OutputTargetWww, + doc: Document, +) => { + const swUrl = generateServiceWorkerUrl( + outputTarget, + outputTarget.serviceWorker as d.ServiceWorkerConfig, + ); const serviceWorker = getRegisterSwScript(doc, buildCtx, swUrl); doc.body.appendChild(serviceWorker); }; diff --git a/src/compiler/html/inline-esm-import.ts b/packages/core/src/compiler/html/inline-esm-import.ts similarity index 88% rename from src/compiler/html/inline-esm-import.ts rename to packages/core/src/compiler/html/inline-esm-import.ts index 3f417164790..2385f20fdd2 100644 --- a/src/compiler/html/inline-esm-import.ts +++ b/packages/core/src/compiler/html/inline-esm-import.ts @@ -1,7 +1,7 @@ -import { isString, join } from '@utils'; import ts from 'typescript'; +import type * as d from '@stencil/core'; -import type * as d from '../../declarations'; +import { isString, join } from '../../utils'; import { generateHashedCopy } from '../output-targets/copy/hashed-copy'; import { getAbsoluteBuildDir } from './html-utils'; import { injectModulePreloads } from './inject-module-preloads'; @@ -18,6 +18,9 @@ import { injectModulePreloads } from './inject-module-preloads'; * the file referenced by the ` + `); }); @@ -32,7 +35,7 @@ describe('hydrate no encapsulation', () => { render() { return ( -

Hello

+

Hello

); } @@ -45,7 +48,7 @@ describe('hydrate no encapsulation', () => { expect(serverHydrated.root).toEqualHtml(` -

+

Hello

@@ -164,8 +167,8 @@ describe('hydrate no encapsulation', () => { class CmpA { render() { return ( - - light-dom + + light-dom ); } @@ -175,8 +178,8 @@ describe('hydrate no encapsulation', () => { render() { return ( - -
+ +
); } @@ -410,8 +413,8 @@ describe('hydrate no encapsulation', () => { return ( -
bottom light-dom
-
top light-dom
+
bottom light-dom
+
top light-dom
middle light-dom
@@ -424,9 +427,9 @@ describe('hydrate no encapsulation', () => { return (
- + - +
); @@ -447,7 +450,7 @@ describe('hydrate no encapsulation', () => {
-
+
top light-dom
@@ -455,7 +458,7 @@ describe('hydrate no encapsulation', () => { middle light-dom -
+
bottom light-dom
diff --git a/src/runtime/test/hydrate-prop.spec.tsx b/packages/core/src/runtime/_test_/hydrate-prop.spec.tsx similarity index 91% rename from src/runtime/test/hydrate-prop.spec.tsx rename to packages/core/src/runtime/_test_/hydrate-prop.spec.tsx index 868acc372cd..72032e71fcc 100644 --- a/src/runtime/test/hydrate-prop.spec.tsx +++ b/packages/core/src/runtime/_test_/hydrate-prop.spec.tsx @@ -1,5 +1,6 @@ import { Component, h, Prop } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; +import { expect, describe, it } from '@stencil/vitest'; import { MEMBER_FLAGS } from '../../utils'; @@ -63,7 +64,7 @@ describe('hydrate prop types', () => { }); expect(serverHydrated.root).toEqualHtml(` - + true-hello world-101-101-10 @@ -80,7 +81,7 @@ describe('hydrate prop types', () => { expect(clientHydrated.root['s-cr']['s-cn']).toBe(true); expect(clientHydrated.root).toEqualHtml(` - + true-hello world-101-101-10 diff --git a/src/runtime/test/hydrate-scoped.spec.tsx b/packages/core/src/runtime/_test_/hydrate-scoped.spec.tsx similarity index 79% rename from src/runtime/test/hydrate-scoped.spec.tsx rename to packages/core/src/runtime/_test_/hydrate-scoped.spec.tsx index d7b0150cdd0..58b859ab239 100644 --- a/src/runtime/test/hydrate-scoped.spec.tsx +++ b/packages/core/src/runtime/_test_/hydrate-scoped.spec.tsx @@ -1,9 +1,10 @@ import { Component, h, Host } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; +import { expect, describe, it } from '@stencil/vitest'; describe('hydrate scoped', () => { - it('does not support shadow, slot, light dom', async () => { - @Component({ tag: 'cmp-a', shadow: true }) + it('converts shadow and slots to light dom via serializeShadowRoot', async () => { + @Component({ tag: 'cmp-a', encapsulation: { type: 'shadow' } }) class CmpA { render() { return ( @@ -15,11 +16,12 @@ describe('hydrate scoped', () => { ); } } - // @ts-ignore + const serverHydrated = await newSpecPage({ components: [CmpA], html: `88mph`, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -38,22 +40,22 @@ describe('hydrate scoped', () => { components: [CmpA], html: serverHydrated.root.outerHTML, hydrateClientSide: true, - supportsShadowDom: false, }); expect(clientHydrated.root).toEqualHtml(` - -
- - 88mph -
+ +
+ +
+
+ 88mph
`); }); it('scoped, slot, light dom', async () => { - @Component({ tag: 'cmp-a', scoped: true }) + @Component({ tag: 'cmp-a', encapsulation: { type: 'scoped' } }) class CmpA { render() { return ( @@ -96,8 +98,8 @@ describe('hydrate scoped', () => { expect(clientHydrated.root).toEqualHtml(` -
- +
+ 88mph
@@ -105,12 +107,12 @@ describe('hydrate scoped', () => { }); it('root element, no slot', async () => { - @Component({ tag: 'cmp-a', scoped: true }) + @Component({ tag: 'cmp-a', encapsulation: { type: 'scoped' } }) class CmpA { render() { return ( -

Hello

+

Hello

); } @@ -120,11 +122,12 @@ describe('hydrate scoped', () => { components: [CmpA], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` -

+

Hello

@@ -151,13 +154,13 @@ describe('hydrate scoped', () => { }); it('adds a scoped-slot class to the slot parent element', async () => { - @Component({ tag: 'cmp-a', scoped: true }) + @Component({ tag: 'cmp-a', encapsulation: { type: 'scoped' } }) class CmpA { render() { return ( -
-

+

+

@@ -172,10 +175,10 @@ describe('hydrate scoped', () => { hydrateServerSide: true, }); expect(serverHydrated.root).toEqualHtml(` - + -
-

+

+

@@ -189,10 +192,10 @@ describe('hydrate scoped', () => { expect(clientHydrated.root.querySelector('p').className).toBe('hi sc-cmp-a-s sc-cmp-a'); expect(clientHydrated.root).toEqualHtml(` - + -
-

+

+

diff --git a/src/runtime/test/hydrate-shadow-child.spec.tsx b/packages/core/src/runtime/_test_/hydrate-shadow-child.spec.tsx similarity index 90% rename from src/runtime/test/hydrate-shadow-child.spec.tsx rename to packages/core/src/runtime/_test_/hydrate-shadow-child.spec.tsx index 3d9e191f7fa..bcd8ca2f534 100644 --- a/src/runtime/test/hydrate-shadow-child.spec.tsx +++ b/packages/core/src/runtime/_test_/hydrate-shadow-child.spec.tsx @@ -1,9 +1,10 @@ import { Component, h, Host } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; +import { expect, describe, it } from '@stencil/vitest'; describe('hydrate, shadow child', () => { it('no slot', async () => { - @Component({ tag: 'cmp-a', shadow: true }) + @Component({ tag: 'cmp-a', encapsulation: { type: 'shadow' } }) class CmpA { render() { return ( @@ -18,6 +19,7 @@ describe('hydrate, shadow child', () => { components: [CmpA], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -59,7 +61,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -74,6 +76,7 @@ describe('hydrate, shadow child', () => { components: [CmpA, CmpB], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -119,7 +122,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -135,6 +138,7 @@ describe('hydrate, shadow child', () => { components: [CmpA, CmpB], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -178,7 +182,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -194,6 +198,7 @@ describe('hydrate, shadow child', () => { components: [CmpA, CmpB], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -238,7 +243,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -254,6 +259,7 @@ describe('hydrate, shadow child', () => { components: [CmpA, CmpB], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -301,7 +307,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -317,6 +323,7 @@ describe('hydrate, shadow child', () => { components: [CmpA, CmpB], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -366,7 +373,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -383,6 +390,7 @@ describe('hydrate, shadow child', () => { components: [CmpA, CmpB], html: ``, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -432,7 +440,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-b', shadow: true }) + @Component({ tag: 'cmp-b', encapsulation: { type: 'shadow' } }) class CmpB { render() { return ( @@ -444,7 +452,7 @@ describe('hydrate, shadow child', () => { ); } } - @Component({ tag: 'cmp-c', shadow: true }) + @Component({ tag: 'cmp-c', encapsulation: { type: 'shadow' } }) class CmpC { render() { return ( @@ -466,6 +474,7 @@ describe('hydrate, shadow child', () => { `, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` @@ -473,7 +482,7 @@ describe('hydrate, shadow child', () => { - + @@ -481,7 +490,7 @@ describe('hydrate, shadow child', () => { cmp-b-top-text - +
@@ -501,17 +510,17 @@ describe('hydrate, shadow child', () => { }); expect(clientHydrated.root).toEqualHtml(` - + - +
cmp-b-top-text - +
cmp-c @@ -526,7 +535,7 @@ describe('hydrate, shadow child', () => { it('preserves all nodes', async () => { @Component({ tag: 'cmp-a', - shadow: true, + encapsulation: { type: 'shadow' }, }) class CmpA { render() { @@ -546,32 +555,33 @@ describe('hydrate, shadow child', () => { `, hydrateServerSide: true, + serializeShadowRoot: 'scoped', }); expect(serverHydrated.root).toEqualHtml(` - + -