diff --git a/.bazelrc b/.bazelrc index 872b401b8af9..e3fb14bdabf7 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,5 +1,5 @@ # Disable NG CLI TTY mode -test --action_env=NG_FORCE_TTY=false +build --action_env=NG_FORCE_TTY=false # Make TypeScript compilation fast, by keeping a few copies of the compiler # running as daemons, and cache SourceFile AST's to reduce parse time. @@ -8,6 +8,10 @@ build --strategy=TypeScriptCompile=worker # Enable debugging tests with --config=debug test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results +# Enable debugging tests with --config=no-sharding +# The below is useful to while using `fit` and `fdescribe` to avoid sharing and re-runs of failed flaky tests. +test:no-sharding --flaky_test_attempts=1 --test_sharding_strategy=disabled + ############################### # Filesystem interactions # ############################### @@ -30,8 +34,7 @@ build --symlink_prefix=dist/ build --nowatchfs # Turn off legacy external runfiles -run --nolegacy_external_runfiles -test --nolegacy_external_runfiles +build --nolegacy_external_runfiles # Turn on --incompatible_strict_action_env which was on by default # in Bazel 0.21.0 but turned off again in 0.22.0. Follow @@ -43,6 +46,18 @@ build --incompatible_strict_action_env run --incompatible_strict_action_env test --incompatible_strict_action_env +# Enable remote caching of build/action tree +build --experimental_remote_merkle_tree_cache + +# Ensure that tags applied in BUILDs propagate to actions +build --experimental_allow_tags_propagation + +# Don't check if output files have been modified +build --noexperimental_check_output_files + +# Ensure sandboxing is enabled even for exclusive tests +test --incompatible_exclusive_test_sandboxed + ############################### # Saucelabs support # # Turn on these settings with # @@ -68,7 +83,14 @@ test:saucelabs --define=KARMA_WEB_TEST_MODE=SL_REQUIRED # Releases should always be stamped with version control info # This command assumes node on the path and is a workaround for # https://github.com/bazelbuild/bazel/issues/4802 -build:release --workspace_status_command="yarn -s ng-dev release build-env-stamp" +build:release --workspace_status_command="yarn -s ng-dev release build-env-stamp --mode=release" +build:release --stamp + +build:snapshot --workspace_status_command="yarn -s ng-dev release build-env-stamp --mode=snapshot" +build:snapshot --stamp +build:snapshot --//:enable_snapshot_repo_deps + +build:local --//:enable_package_json_tar_deps ############################### # Output # @@ -92,8 +114,8 @@ test --test_output=errors ################################ # Use the Angular team internal GCP instance for remote execution. -build:remote --remote_instance_name=projects/internal-200822/instances/default_instance -build:remote --project_id=internal-200822 +build:remote --remote_instance_name=projects/internal-200822/instances/primary_instance +build:remote --bes_instance_name=internal-200822 # Starting with Bazel 0.27.0 strategies do not need to be explicitly # defined. See https://github.com/bazelbuild/bazel/issues/7480 @@ -105,20 +127,15 @@ build:remote --remote_executor=remotebuildexecution.googleapis.com build:remote --remote_timeout=600 build:remote --jobs=150 - # Setup the toolchain and platform for the remote build execution. The platform -# is automatically configured by the "rbe_autoconfig" rule in the project workpsace. -build:remote --host_javabase=@rbe_ubuntu1604_angular//java:jdk -build:remote --javabase=@rbe_ubuntu1604_angular//java:jdk -build:remote --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8 -build:remote --java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8 -build:remote --crosstool_top=@rbe_ubuntu1604_angular//cc:toolchain -build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -build:remote --extra_toolchains=@rbe_ubuntu1604_angular//config:cc-toolchain -build:remote --extra_execution_platforms=//tools:rbe_ubuntu1604-angular -build:remote --host_platform=//tools:rbe_ubuntu1604-angular -build:remote --platforms=//tools:rbe_ubuntu1604-angular - - # Set remote caching settings +# Setup the toolchain and platform for the remote build execution. The platform +# is provided by the shared dev-infra package and targets k8 remote containers. +build:remote --crosstool_top=@npm//@angular/build-tooling/bazel/remote-execution/cpp:cc_toolchain_suite +build:remote --extra_toolchains=@npm//@angular/build-tooling/bazel/remote-execution/cpp:cc_toolchain +build:remote --extra_execution_platforms=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network +build:remote --host_platform=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network +build:remote --platforms=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network + +# Set remote caching settings build:remote --remote_accept_cached=true # Force remote executions to consider the entire run as linux. @@ -134,6 +151,9 @@ build:remote --google_default_credentials # These settings are required for rules_nodejs ############################### +# Fixes use of npm paths with spaces such as some within the puppeteer module +build --experimental_inprocess_symlink_creation + #################################################### # User bazel configuration # NOTE: This needs to be the *last* entry in the config. @@ -145,4 +165,4 @@ try-import .bazelrc.user # Enable runfiles even on Windows. # Architect resolves output files from data files, and this isn't possible without runfile support. -test --enable_runfiles +build --enable_runfiles diff --git a/.bazelversion b/.bazelversion index 54f6b9be19dc..03f488b076ae 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1,3 +1 @@ -4.0.0 -# [NB: this comment has to be after the first line, see https://github.com/bazelbuild/bazelisk/issues/117] -# When updating the Bazel version you also need to update the RBE toolchains version in WORKSPACE +5.3.0 diff --git a/.circleci/bazel.rc b/.circleci/bazel.rc index 1b89d0bd6424..f4c1163eb7bb 100644 --- a/.circleci/bazel.rc +++ b/.circleci/bazel.rc @@ -7,9 +7,6 @@ build --announce_rc # Don't be spammy in the logs build --noshow_progress -# Don't run manual tests -test --test_tag_filters=-manual - # Workaround https://github.com/bazelbuild/bazel/issues/3645 # Bazel doesn't calculate the memory ceiling correctly when running under Docker. # Limit Bazel to consuming resources that fit in CircleCI "xlarge" class diff --git a/.circleci/config.yml b/.circleci/config.yml index bcab988200a5..5f1aebbeb5c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,354 +1,17 @@ -# Configuration file for https://circleci.com/gh/angular/angular-cli - -# Note: YAML anchors allow an object to be re-used, reducing duplication. -# The ampersand declares an alias for an object, then later the `<<: *name` -# syntax dereferences it. -# See http://blog.daemonl.com/2016/02/yaml.html -# To validate changes, use an online parser, eg. -# http://yaml-online-parser.appspot.com/ - version: 2.1 - orbs: - browser-tools: circleci/browser-tools@1.0.1 - -# Variables - -## IMPORTANT -# Windows needs its own cache key because binaries in node_modules are different. -# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI. -var_1: &cache_key v1-angular_devkit-14.15-{{ checksum "yarn.lock" }} -var_1_win: &cache_key_win v1-angular_devkit-win-12.22-{{ checksum "yarn.lock" }} -var_3: &default_nodeversion '14.15' -# Workspace initially persisted by the `setup` job, and then enhanced by `setup-and-build-win`. -# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs -# https://circleci.com/blog/deep-diving-into-circleci-workspaces/ -var_4: &workspace_location . -# Filter to only release branches on a given job. -var_5: &only_release_branches - filters: - branches: - only: - - master - - /\d+\.\d+\.x/ - -# Executor Definitions -# https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-executors -executors: - action-executor: - parameters: - nodeversion: - type: string - default: *default_nodeversion - docker: - - image: cimg/node:<< parameters.nodeversion >> - working_directory: ~/ng - resource_class: small - - test-executor: - parameters: - nodeversion: - type: string - default: *default_nodeversion - docker: - - image: cimg/node:<< parameters.nodeversion >> - working_directory: ~/ng - resource_class: large - - windows-executor: - # Same as https://circleci.com/orbs/registry/orb/circleci/windows, but named. - working_directory: ~/ng - resource_class: windows.medium - shell: powershell.exe -ExecutionPolicy Bypass - machine: - # Contents of this image: - # https://circleci.com/docs/2.0/hello-world-windows/#software-pre-installed-in-the-windows-image - image: windows-server-2019-vs2019:stable - -# Command Definitions -# https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-commands -commands: - custom_attach_workspace: - description: Attach workspace at a predefined location - steps: - - attach_workspace: - at: *workspace_location - setup_windows: - steps: - - run: nvm install 12.22.1 - - run: nvm use 12.22.1 - - run: npm install -g yarn@1.22.10 - - run: node --version - - run: yarn --version - - setup_bazel_rbe: - parameters: - key: - type: env_var_name - default: CIRCLE_PROJECT_REPONAME - steps: - - run: - name: 'Setup bazel RBE remote execution' - command: | - touch .bazelrc.user; - # We need ensure that the same default digest is used for encoding and decoding - # with openssl. Openssl versions might have different default digests which can - # cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734 - openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "${<< parameters.key >>}" -out /home/circleci/.gcp_credentials; - sudo bash -c "echo -e 'build --google_credentials=/home/circleci/.gcp_credentials' >> .bazelrc.user"; - # Upload/don't upload local results to cache based on environment - if [[ -n "{$CIRCLE_PR_NUMBER}" ]]; then - sudo bash -c "echo -e 'build:remote --remote_upload_local_results=false\n' >> .bazelrc.user"; - echo "Not uploading local build results to remote cache."; - else - sudo bash -c "echo -e 'build:remote --remote_upload_local_results=true\n' >> .bazelrc.user"; - echo "Uploading local build results to remote cache."; - fi - # Enable remote builds - sudo bash -c "echo -e 'build --config=remote' >> .bazelrc.user"; - echo "Reading from remote cache for bazel remote jobs."; - - install_python: - steps: - - run: - name: 'Install Python 2' - command: | - sudo apt-get update > /dev/null 2>&1 - sudo apt-get install -y python - python --version + path-filtering: circleci/path-filtering@0.1.3 -# Job definitions -jobs: - setup: - executor: action-executor - resource_class: medium - steps: - - checkout - - run: - name: Rebase PR on target branch - command: > - if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then - # User is required for rebase. - git config user.name "angular-ci" - git config user.email "angular-ci" - # Rebase PR on top of target branch. - node tools/rebase-pr.js angular/angular-cli ${CIRCLE_PR_NUMBER} - else - echo "This build is not over a PR, nothing to do." - fi - - restore_cache: - keys: - - *cache_key - - run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - - persist_to_workspace: - root: *workspace_location - paths: - - ./* - - save_cache: - key: *cache_key - paths: - - ~/.cache/yarn - - lint: - executor: action-executor - steps: - - custom_attach_workspace - - run: yarn lint - - run: 'yarn bazel:format -mode=check || - (echo "BUILD files not formatted. Please run ''yarn bazel:format''" ; exit 1)' - # Run the skylark linter to check our Bazel rules - - run: 'yarn bazel:lint || - (echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)' - - validate: - executor: action-executor - steps: - - custom_attach_workspace - - run: - name: Validate Commit Messages - command: > - if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then - yarn ng-dev commit-message validate-range <> <> - else - echo "This build is not over a PR, nothing to do." - fi - - run: - command: yarn -s admin validate - - e2e-cli: - parameters: - nodeversion: - type: string - default: *default_nodeversion - snapshots: - type: boolean - default: false - executor: - name: test-executor - nodeversion: << parameters.nodeversion >> - parallelism: 6 - steps: - - custom_attach_workspace - - browser-tools/install-chrome - - run: - name: Initialize Environment - command: ./.circleci/env.sh - - run: - name: Execute CLI E2E Tests - command: | - mkdir /mnt/ramdisk/e2e-main - node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<> --tmpdir=/mnt/ramdisk/e2e-main - - run: - name: Execute CLI E2E Tests Subset with Yarn - command: | - mkdir /mnt/ramdisk/e2e-yarn - node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<> --yarn --tmpdir=/mnt/ramdisk/e2e-yarn --glob="{tests/basic/**,tests/update/**,tests/commands/add/**}" - - test-browsers: - executor: - name: test-executor - environment: - E2E_BROWSERS: true - resource_class: medium - steps: - - custom_attach_workspace - - run: - name: Initialize Environment - command: ./.circleci/env.sh - - run: - name: Initialize Saucelabs - command: setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev) - - run: - name: Start Saucelabs Tunnel - command: ./scripts/saucelabs/start-tunnel.sh - background: true - # Waits for the Saucelabs tunnel to be ready. This ensures that we don't run tests - # too early without Saucelabs not being ready. - - run: ./scripts/saucelabs/wait-for-tunnel.sh - - run: node ./tests/legacy-cli/run_e2e ./tests/legacy-cli/e2e/tests/misc/browsers.ts --ve - - run: node ./tests/legacy-cli/run_e2e ./tests/legacy-cli/e2e/tests/misc/browsers.ts - - run: ./scripts/saucelabs/stop-tunnel.sh - - build: - executor: action-executor - steps: - - custom_attach_workspace - - run: yarn build - - test: - executor: test-executor - resource_class: xlarge - steps: - - custom_attach_workspace - - browser-tools/install-chrome - - setup_bazel_rbe - - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc - - run: - command: yarn bazel:test - no_output_timeout: 20m - - snapshot_publish: - executor: action-executor - resource_class: medium - steps: - - custom_attach_workspace - - run: - name: Decrypt Credentials - # Note: when changing the image, you might have to re-encrypt the credentials with a - # matching version of openssl. - # See https://stackoverflow.com/a/43847627/2116927 for more info. - command: | - openssl aes-256-cbc -d -in .circleci/github_token -k "${KEY}" -out ~/github_token -md md5 - - run: - name: Deployment to Snapshot - command: | - yarn admin snapshots --verbose --githubTokenFile=${HOME}/github_token - - # Windows jobs - e2e-cli-win: - executor: windows-executor - parallelism: 8 - steps: - - custom_attach_workspace - - setup_windows - - restore_cache: - keys: - - *cache_key_win - - run: yarn install --frozen-lockfile --cache-folder ../.cache/yarn - - save_cache: - key: *cache_key_win - paths: - - ~/.cache/yarn - # Run partial e2e suite on PRs only. Release branches will run the full e2e suite. - - run: - name: Execute E2E Tests - command: | - if (Test-Path env:CIRCLE_PR_NUMBER) { - node tests\legacy-cli\run_e2e.js "--glob={tests/basic/**,tests/i18n/extract-ivy*.ts,tests/build/profile.ts}" --nb-shards=$env:CIRCLE_NODE_TOTAL --shard=$env:CIRCLE_NODE_INDEX - } else { - node tests\legacy-cli\run_e2e.js --nb-shards=$env:CIRCLE_NODE_TOTAL --shard=$env:CIRCLE_NODE_INDEX - } +# This allows you to use CircleCI's dynamic configuration feature +setup: true workflows: - version: 2 - default_workflow: + run-filter: jobs: - # Linux jobs - - setup - - lint: - requires: - - setup - - validate: - requires: - - setup - - build: - requires: - - setup - - e2e-cli: - name: e2e-cli - post-steps: - - store_artifacts: - path: /tmp/dist - destination: cli/new-production - requires: - - build - - e2e-cli: - name: e2e-cli-ng-snapshots - snapshots: true - requires: - - e2e-cli - filters: - branches: - only: - - renovate/angular - - master - - e2e-cli: - name: e2e-cli-node-12 - nodeversion: '12.18' - <<: *only_release_branches - requires: - - e2e-cli - - test-browsers: - requires: - - build - - # Bazel jobs - # These jobs only really depend on Setup, but the build job is very quick to run (~35s) and - # will catch any build errors before proceeding to the more lengthy and resource intensive - # Bazel jobs. - - test: - requires: - - build - - # Windows jobs - - e2e-cli-win: - requires: - - test - - # Publish jobs - - snapshot_publish: - <<: *only_release_branches - requires: - - build - - test - - e2e-cli + - path-filtering/filter: + # Compare files on main + base-revision: main + # 3-column space-separated table for mapping; `path-to-test parameter-to-set value-for-parameter` for each row + mapping: | + tests/legacy-cli/e2e/ng-snapshot/package.json snapshot_changed true + config-path: '.circleci/dynamic_config.yml' diff --git a/.circleci/dynamic_config.yml b/.circleci/dynamic_config.yml new file mode 100644 index 000000000000..5dbea5ebedca --- /dev/null +++ b/.circleci/dynamic_config.yml @@ -0,0 +1,487 @@ +# Configuration file for https://circleci.com/gh/angular/angular-cli + +# Note: YAML anchors allow an object to be re-used, reducing duplication. +# The ampersand declares an alias for an object, then later the `<<: *name` +# syntax dereferences it. +# See http://blog.daemonl.com/2016/02/yaml.html +# To validate changes, use an online parser, eg. +# http://yaml-online-parser.appspot.com/ + +version: 2.1 + +orbs: + browser-tools: circleci/browser-tools@1.4.0 + devinfra: angular/dev-infra@1.0.7 + +parameters: + snapshot_changed: + type: boolean + default: false + +# Variables + +## IMPORTANT +# Windows needs its own cache key because binaries in node_modules are different. +# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI. +var_1: &cache_key v1-angular_devkit-14.20-{{ checksum "yarn.lock" }} +var_1_win: &cache_key_win v1-angular_devkit-win-16.13-{{ checksum "yarn.lock" }} +var_3: &default_nodeversion '14.20' +var_3_major: &default_nodeversion_major '14' +# The major version of node toolchains. See tools/toolchain_info.bzl +# NOTE: entries in this array may be repeated elsewhere in the file, find them before adding more +var_3_all_major: &all_nodeversion_major ['14', '16'] +# Workspace initially persisted by the `setup` job, and then enhanced by `setup-and-build-win`. +# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs +# https://circleci.com/blog/deep-diving-into-circleci-workspaces/ +var_4: &workspace_location . +# Filter to only release branches on a given job. +var_5: &only_release_branches + filters: + branches: + only: + - main + - /\d+\.\d+\.x/ + +var_6: &only_pull_requests + filters: + branches: + only: + - /pull\/\d+/ + +var_7: &all_e2e_subsets ['npm', 'esbuild', 'yarn'] + +# Executor Definitions +# https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-executors +executors: + action-executor: + parameters: + nodeversion: + type: string + default: *default_nodeversion + docker: + - image: cimg/node:<< parameters.nodeversion >> + working_directory: ~/ng + resource_class: small + + windows-executor: + # Same as https://circleci.com/orbs/registry/orb/circleci/windows, but named. + working_directory: ~/ng + resource_class: windows.medium + shell: powershell.exe -ExecutionPolicy Bypass + machine: + # Contents of this image: + # https://circleci.com/docs/2.0/hello-world-windows/#software-pre-installed-in-the-windows-image + image: windows-server-2019-vs2019:stable + +# Command Definitions +# https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-commands +commands: + fail_fast: + steps: + - run: + name: 'Cancel workflow on fail' + shell: bash + when: on_fail + command: | + curl -X POST --header "Content-Type: application/json" "https://circleci.com/api/v2/workflow/$%7BCIRCLE_WORKFLOW_ID%7D/cancel?circle-token=${CIRCLE_TOKEN}" + + initialize_env: + steps: + - run: + name: Initialize Environment + command: ./.circleci/env.sh + + rebase_pr: + steps: + - devinfra/rebase-pr-on-target-branch: + base_revision: << pipeline.git.base_revision >> + head_revision: << pipeline.git.revision >> + + rebase_pr_win: + steps: + - devinfra/rebase-pr-on-target-branch: + base_revision: << pipeline.git.base_revision >> + head_revision: << pipeline.git.revision >> + # Use `bash.exe` as Shell because the CircleCI-orb command is an + # included Bash script and expects Bash as shell. + shell: bash.exe + + custom_attach_workspace: + description: Attach workspace at a predefined location + steps: + - attach_workspace: + at: *workspace_location + setup_windows: + steps: + - initialize_env + - run: nvm install 16.13 + - run: nvm use 16.13 + - run: npm install -g yarn@1.22.10 + - run: node --version + - run: yarn --version + + setup_bazel_rbe: + parameters: + key: + type: env_var_name + default: CIRCLE_PROJECT_REPONAME + steps: + - devinfra/setup-bazel-remote-exec: + bazelrc: ./.bazelrc.user + + install_python: + steps: + - run: + name: 'Install Python 2' + command: | + sudo apt-get update > /dev/null 2>&1 + sudo apt-get install -y python + python --version + +# Job definitions +jobs: + setup: + executor: action-executor + resource_class: medium + steps: + - checkout + - rebase_pr + - initialize_env + - restore_cache: + keys: + - *cache_key + - run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - persist_to_workspace: + root: *workspace_location + paths: + - ./* + - save_cache: + key: *cache_key + paths: + - ~/.cache/yarn + + lint: + executor: action-executor + steps: + - custom_attach_workspace + - run: yarn lint + + validate: + executor: action-executor + steps: + - custom_attach_workspace + - run: + name: Validate Commit Messages + command: > + if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then + yarn ng-dev commit-message validate-range <> <> + else + echo "This build is not over a PR, nothing to do." + fi + - run: + name: Validate Code Formatting + command: yarn -s ng-dev format changed <> --check + - run: + name: Validate NgBot Configuration + command: yarn ng-dev ngbot verify + - run: + name: Validate Circular Dependencies + command: yarn ts-circular-deps:check + - run: yarn -s admin validate + - run: yarn -s check-tooling-setup + + e2e-tests: + parameters: + nodeversion: + type: string + default: *default_nodeversion + snapshots: + type: boolean + default: false + subset: + type: enum + enum: *all_e2e_subsets + default: 'npm' + executor: + name: action-executor + nodeversion: << parameters.nodeversion >> + parallelism: 8 + resource_class: large + steps: + - custom_attach_workspace + - browser-tools/install-chrome + - initialize_env + - run: mkdir /mnt/ramdisk/e2e + - when: + condition: + equal: ['npm', << parameters.subset >>] + steps: + - run: + name: Execute CLI E2E Tests with NPM + command: | + node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<> --tmpdir=/mnt/ramdisk/e2e --ignore="tests/misc/browsers.ts" + - when: + condition: + equal: ['esbuild', << parameters.subset >>] + steps: + - run: + name: Execute CLI E2E Tests Subset with Esbuild + command: | + node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<> --esbuild --tmpdir=/mnt/ramdisk/e2e --glob="{tests/basic/**,tests/build/prod-build.ts,tests/build/relative-sourcemap.ts,tests/build/styles/scss.ts,tests/build/styles/include-paths.ts,tests/commands/add/add-pwa.ts}" --ignore="tests/basic/{environment,rebuild,serve,scripts-array}.ts" + - when: + condition: + equal: ['yarn', << parameters.subset >>] + steps: + - run: + name: Execute CLI E2E Tests Subset with Yarn + command: | + node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<> --yarn --tmpdir=/mnt/ramdisk/e2e --glob="{tests/basic/**,tests/update/**,tests/commands/add/**}" + - fail_fast + + test-browsers: + executor: + name: action-executor + resource_class: medium + steps: + - custom_attach_workspace + - initialize_env + - run: + name: Initialize Saucelabs + command: setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev) + - run: + name: Start Saucelabs Tunnel + command: ./scripts/saucelabs/start-tunnel.sh + background: true + # Waits for the Saucelabs tunnel to be ready. This ensures that we don't run tests + # too early without Saucelabs not being ready. + - run: ./scripts/saucelabs/wait-for-tunnel.sh + - run: node ./tests/legacy-cli/run_e2e --glob="tests/misc/browsers.ts" + - run: ./scripts/saucelabs/stop-tunnel.sh + - fail_fast + + build: + executor: action-executor + steps: + - custom_attach_workspace + - run: yarn build + - persist_to_workspace: + root: *workspace_location + paths: + - dist/_*.tgz + + build-bazel-e2e: + executor: action-executor + resource_class: medium + steps: + - custom_attach_workspace + - run: yarn bazel build //tests/legacy-cli/... + + unit-test: + executor: action-executor + resource_class: xlarge + parameters: + nodeversion: + type: string + default: *default_nodeversion_major + steps: + - custom_attach_workspace + - browser-tools/install-chrome + - setup_bazel_rbe + - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc + - when: + # The default nodeversion runs all *excluding* other versions + condition: + equal: [*default_nodeversion_major, << parameters.nodeversion >>] + steps: + - run: + command: yarn bazel test --test_tag_filters=-node16,-node<< parameters.nodeversion >>-broken //packages/... + # This timeout provides time for the actual tests to timeout and report status + # instead of CircleCI stopping the job without test failure information. + no_output_timeout: 40m + - when: + # Non-default nodeversion runs only that specific nodeversion + condition: + not: + equal: [*default_nodeversion_major, << parameters.nodeversion >>] + steps: + - run: + command: yarn bazel test --test_tag_filters=node<< parameters.nodeversion >>,-node<< parameters.nodeversion >>-broken //packages/... + # This timeout provides time for the actual tests to timeout and report status + # instead of CircleCI stopping the job without test failure information. + no_output_timeout: 40m + - fail_fast + + snapshot_publish: + executor: action-executor + resource_class: medium + steps: + - custom_attach_workspace + - install_python + - run: + name: Decrypt Credentials + # Note: when changing the image, you might have to re-encrypt the credentials with a + # matching version of openssl. + # See https://stackoverflow.com/a/43847627/2116927 for more info. + command: | + openssl aes-256-cbc -d -in .circleci/github_token -k "${KEY}" -out ~/github_token -md md5 + - run: + name: Deployment to Snapshot + command: | + yarn admin snapshots --verbose --githubTokenFile=${HOME}/github_token + - fail_fast + + publish_artifacts: + executor: action-executor + environment: + steps: + - custom_attach_workspace + - run: + name: Create artifacts for packages + command: yarn ng-dev release build + - run: + name: Copy tarballs to folder + command: | + mkdir -p dist/artifacts/ + cp dist/*.tgz dist/artifacts/ + - store_artifacts: + path: dist/artifacts/ + destination: angular + + # Windows jobs + e2e-cli-win: + executor: windows-executor + parallelism: 16 + steps: + - checkout + - rebase_pr_win + - setup_windows + - restore_cache: + keys: + - *cache_key_win + - run: + # We use Arsenal Image Mounter (AIM) instead of ImDisk because of: https://github.com/nodejs/node/issues/6861 + # Useful resources for AIM: http://reboot.pro/index.php?showtopic=22068 + name: 'Arsenal Image Mounter (RAM Disk)' + command: | + pwsh ./.circleci/win-ram-disk.ps1 + - run: yarn install --frozen-lockfile --cache-folder ../.cache/yarn + - save_cache: + key: *cache_key_win + paths: + - ~/.cache/yarn + # Path where Arsenal Image Mounter files are downloaded. + # Must match path in .circleci/win-ram-disk.ps1 + - ./aim + # Build the npm packages for the e2e tests + - run: yarn build + # Run partial e2e suite on PRs only. Release branches will run the full e2e suite. + - run: + name: Execute E2E Tests + command: | + mkdir X:/ramdisk/e2e-main + node tests\legacy-cli\run_e2e.js --nb-shards=$env:CIRCLE_NODE_TOTAL --shard=$env:CIRCLE_NODE_INDEX --tmpdir=X:/ramdisk/e2e-main --ignore="tests/misc/browsers.ts" + - fail_fast + +workflows: + version: 2 + default_workflow: + jobs: + # Linux jobs + - setup + - lint: + requires: + - setup + - validate: + requires: + - setup + - build: + requires: + - setup + + - test-browsers: + requires: + - build + + - e2e-tests: + name: e2e-cli-<< matrix.subset >> + nodeversion: '14.20' + matrix: + parameters: + subset: *all_e2e_subsets + filters: + branches: + ignore: + - main + - /\d+\.\d+\.x/ + requires: + - build + + - e2e-tests: + name: e2e-cli-node-<>-<< matrix.subset >> + matrix: + alias: e2e-cli + parameters: + nodeversion: ['14.20', '16.13', '18.10'] + subset: *all_e2e_subsets + requires: + - build + <<: *only_release_branches + + - e2e-tests: + name: e2e-snapshots-<< matrix.subset >> + nodeversion: '16.13' + matrix: + parameters: + subset: *all_e2e_subsets + snapshots: true + pre-steps: + - when: + condition: + and: + - not: + equal: [main, << pipeline.git.branch >>] + - not: << pipeline.parameters.snapshot_changed >> + steps: + # Don't run snapshot E2E's unless it's on the main branch or the snapshots file has been updated. + - run: circleci-agent step halt + requires: + - build + filters: + branches: + only: + - main + # This is needed to run this steps on Renovate PRs that amend the snapshots package.json + - /^pull\/.*/ + + # Bazel jobs + # These jobs only really depend on Setup, but the build job is very quick to run (~35s) and + # will catch any build errors before proceeding to the more lengthy and resource intensive + # Bazel jobs. + - unit-test: + name: test-node<< matrix.nodeversion >> + matrix: + parameters: + nodeversion: *all_nodeversion_major + requires: + - build + + # Compile the e2e tests with bazel to ensure the non-runtime typescript + # compilation completes succesfully. + - build-bazel-e2e: + requires: + - build + + # Windows jobs + - e2e-cli-win + + # Publish jobs + - snapshot_publish: + <<: *only_release_branches + requires: + - setup + - e2e-cli + + - publish_artifacts: + <<: *only_pull_requests + requires: + - build diff --git a/.circleci/env.sh b/.circleci/env.sh index d24334473255..6ec09ef85153 100755 --- a/.circleci/env.sh +++ b/.circleci/env.sh @@ -22,7 +22,7 @@ setPublicVar PATH "${HOME}/.npm-global/bin:${PATH}"; # Define SauceLabs environment variables for CircleCI. #################################################################################################### setPublicVar SAUCE_USERNAME "angular-tooling"; -setSecretVar SAUCE_ACCESS_KEY "8c4ffce86ae6-c419-8ef4-0513-54267305"; +setSecretVar SAUCE_ACCESS_KEY "e05dabf6fe0e-2c18-abf4-496d-1d010490"; setPublicVar SAUCE_LOG_FILE /tmp/angular/sauce-connect.log setPublicVar SAUCE_READY_FILE /tmp/angular/sauce-connect-ready-file.lock setPublicVar SAUCE_PID_FILE /tmp/angular/sauce-connect-pid-file.lock @@ -33,3 +33,6 @@ setPublicVar SAUCE_READY_FILE_TIMEOUT 120 # Source `$BASH_ENV` to make the variables available immediately. source $BASH_ENV; + +# Disable husky. +setPublicVar HUSKY 0 diff --git a/.circleci/gcp_token b/.circleci/gcp_token deleted file mode 100644 index 06773903e8d8..000000000000 Binary files a/.circleci/gcp_token and /dev/null differ diff --git a/.circleci/win-ram-disk.ps1 b/.circleci/win-ram-disk.ps1 new file mode 100644 index 000000000000..5d16d8b8a11d --- /dev/null +++ b/.circleci/win-ram-disk.ps1 @@ -0,0 +1,30 @@ +$aimContents = "./aim"; + +if (-not (Test-Path -Path $aimContents)) { + echo "Arsenal Image Mounter files not found in cache. Downloading..." + + # Download AIM Drivers and validate hash + Invoke-WebRequest "https://github.com/ArsenalRecon/Arsenal-Image-Mounter/raw/988930e4b3180ec34661504e6f9906f98943a022/DriverSetup/DriverFiles.zip" -OutFile "aim_drivers.zip" -UseBasicParsing + $aimDriversDownloadHash = (Get-FileHash aim_drivers.zip -a sha256).Hash + If ($aimDriversDownloadHash -ne "1F5AA5DD892C2D5E8A0083752B67C6E5A2163CD83B6436EA545508D84D616E02") { + throw "aim_drivers.zip hash is ${aimDriversDownloadHash} which didn't match the known version." + } + Expand-Archive -Path "aim_drivers.zip" -DestinationPath $aimContents/drivers + + # Download AIM CLI and validate hash + Invoke-WebRequest "https://github.com/ArsenalRecon/Arsenal-Image-Mounter/raw/988930e4b3180ec34661504e6f9906f98943a022/Command%20line%20applications/aim_ll.zip" -OutFile "aim_ll.zip" -UseBasicParsing + $aimCliDownloadHash = (Get-FileHash aim_ll.zip -a sha256).Hash + If ($aimCliDownloadHash -ne "9AD3058F14595AC4A5E5765A9746737D31C219383766B624FCBA4C5ED96B20F3") { + throw "aim_ll.zip hash is ${aimCliDownloadHash} which didn't match the known version." + } + Expand-Archive -Path "aim_ll.zip" -DestinationPath $aimContents/cli +} else { + echo "Arsenal Image Mounter files found in cache. Skipping download." +} + +# Install AIM drivers +./aim/cli/x64/aim_ll.exe --install ./aim/drivers + +# Setup RAM disk mount. Same parameters as ImDisk +# See: https://support.circleci.com/hc/en-us/articles/4411520952091-Create-a-windows-RAM-disk +./aim/cli/x64/aim_ll.exe -a -s 5G -m X: -p "/fs:ntfs /q /y" diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000000..c3cc98e062af --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +/bazel-out/ +/dist-schema/ +/goldens/public-api +/packages/angular_devkit/build_angular/test/ +/packages/angular_devkit/build_webpack/test/ +/packages/angular_devkit/schematics_cli/blank/project-files/ +/packages/angular_devkit/schematics_cli/blank/schematic-files/ +/packages/angular_devkit/schematics_cli/schematic/files/ +/tests/ +.yarn/ +dist/ +node_modules/ +third_party/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000000..954eb0855a7b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,118 @@ +{ + "root": true, + "env": { + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:import/typescript", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": ["eslint-plugin-import", "header", "@typescript-eslint"], + "rules": { + "@typescript-eslint/consistent-type-assertions": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-unnecessary-qualifier": "error", + "@typescript-eslint/no-unused-expressions": "error", + "curly": "error", + "header/header": [ + "error", + "block", + [ + "*", + " * @license", + " * Copyright Google LLC All Rights Reserved.", + " *", + " * Use of this source code is governed by an MIT-style license that can be", + " * found in the LICENSE file at https://angular.io/license", + " " + ], + 2 + ], + "import/first": "error", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-duplicates": "error", + "import/no-extraneous-dependencies": ["off", { "devDependencies": false }], + "import/no-unassigned-import": ["error", { "allow": ["symbol-observable"] }], + "import/order": [ + "error", + { + "alphabetize": { "order": "asc" }, + "groups": [["builtin", "external"], "parent", "sibling", "index"] + } + ], + "max-len": [ + "error", + { + "code": 140, + "ignoreUrls": true + } + ], + "max-lines-per-function": ["error", { "max": 200 }], + "no-caller": "error", + "no-console": "error", + "no-empty": ["error", { "allowEmptyCatch": true }], + "no-eval": "error", + "no-multiple-empty-lines": ["error"], + "no-throw-literal": "error", + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "*", + "next": "return" + } + ], + "sort-imports": ["error", { "ignoreDeclarationSort": true }], + "spaced-comment": [ + "error", + "always", + { + "markers": ["/"] + } + ], + + /* TODO: evaluate usage of these rules and fix issues as needed */ + "no-case-declarations": "off", + "no-fallthrough": "off", + "no-underscore-dangle": "off", + "@typescript-eslint/await-thenable": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-implied-eval": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-unnecessary-type-assertion": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/prefer-regexp-exec": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/restrict-plus-operands": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "@typescript-eslint/unbound-method": "off" + }, + "overrides": [ + { + "files": ["!packages/**", "**/*_spec.ts"], + "rules": { + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], + "max-lines-per-function": "off", + "no-console": "off" + } + } + ] +} diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index b5135def5125..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,10 +0,0 @@ -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 - -Please help us process issues more efficiently by filing an -issue using one of the following templates: - -https://github.com/angular/angular-cli/issues/new/choose - -Thank you! - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md deleted file mode 100644 index c24ab5b7ef34..000000000000 --- a/.github/ISSUE_TEMPLATE/1-bug-report.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -name: "\U0001F41E Bug report" -about: Report a bug in Angular CLI ---- - - - -# 🐞 Bug report - -### Command (mark with an `x`) - - - -- [ ] new -- [ ] build -- [ ] serve -- [ ] test -- [ ] e2e -- [ ] generate -- [ ] add -- [ ] update -- [ ] lint -- [ ] extract-i18n -- [ ] run -- [ ] config -- [ ] help -- [ ] version -- [ ] doc - - -### Is this a regression? - - - Yes, the previous version in which this bug was not present was: .... - - -### Description - - A clear and concise description of the problem... - - -## 🔬 Minimal Reproduction - - -## 🔥 Exception or Error -

-
-
-
-
- - -## 🌍 Your Environment -

-
-
-
-
- -**Anything else relevant?** - - - diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.yml b/.github/ISSUE_TEMPLATE/1-bug-report.yml new file mode 100644 index 000000000000..5c4ea7d2cbdb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug-report.yml @@ -0,0 +1,102 @@ +name: Bug report +description: Report a bug in Angular CLI +body: + - type: markdown + attributes: + value: | + Oh hi there! + + To expedite issue processing please search open and closed issues before submitting a new one. + Existing issues often contain information about workarounds, resolution, or progress updates. + - type: dropdown + id: command + attributes: + label: Command + description: Can you pin-point the command or commands that are effected by this bug? + options: + - add + - build + - config + - doc + - e2e + - extract-i18n + - generate + - help + - lint + - new + - other + - run + - serve + - test + - update + - version + multiple: true + validations: + required: true + - type: checkboxes + id: is-regression + attributes: + label: Is this a regression? + description: Did this behavior use to work in the previous version? + options: + - label: Yes, this behavior used to work in the previous version + - type: input + id: version-bug-was-not-present + attributes: + label: The previous version in which this bug was not present was + validations: + required: false + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of the problem. + validations: + required: true + - type: textarea + id: minimal-reproduction + attributes: + label: Minimal Reproduction + description: | + Simple steps to reproduce this bug. + + **Please include:** + * commands run (including args) + * packages added + * related code changes + + + If reproduction steps are not enough for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue. + A good way to make a minimal reproduction is to create a new app via `ng new repro-app` and add the minimum possible code to show the problem. + Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior. + + Issues that don't have enough info and can't be reproduced will be closed. + + You can read more about issue submission guidelines [here](https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#-submitting-an-issue). + validations: + required: true + - type: textarea + id: exception-or-error + attributes: + label: Exception or Error + description: If the issue is accompanied by an exception or an error, please share it below. + render: text + validations: + required: false + - type: textarea + id: environment + attributes: + label: Your Environment + description: Run `ng version` and paste output below. + render: text + validations: + required: true + - type: textarea + id: other + attributes: + label: Anything else relevant? + description: | + Is this a browser specific issue? If so, please specify the browser and version. + Do any of these matter: operating system, IDE, package manager, HTTP server, ...? If so, please mention it below. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md deleted file mode 100644 index 9210f5135779..000000000000 --- a/.github/ISSUE_TEMPLATE/2-feature-request.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: "\U0001F680 Feature request" -about: Suggest a feature for Angular CLI - ---- - - - -# 🚀 Feature request - - -### Command (mark with an `x`) - - -- [ ] new -- [ ] build -- [ ] serve -- [ ] test -- [ ] e2e -- [ ] generate -- [ ] add -- [ ] update -- [ ] lint -- [ ] extract-i18n -- [ ] run -- [ ] config -- [ ] help -- [ ] version -- [ ] doc - -### Description - A clear and concise description of the problem or missing capability... - - -### Describe the solution you'd like - If you have a solution in mind, please describe it. - - -### Describe alternatives you've considered - Have you considered any alternative solutions or workarounds? diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yml b/.github/ISSUE_TEMPLATE/2-feature-request.yml new file mode 100644 index 000000000000..4a01698e0f37 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature-request.yml @@ -0,0 +1,55 @@ +name: Feature request +description: Suggest a feature for Angular CLI +body: + - type: markdown + attributes: + value: | + Oh hi there! + + To expedite issue processing please search open and closed issues before submitting a new one. + Existing issues often contain information about workarounds, resolution, or progress updates. + - type: dropdown + id: command + attributes: + label: Command + description: Can you pin-point the command or commands that are relevant for this feature request? + options: + - add + - build + - config + - doc + - e2e + - extract-i18n + - generate + - help + - lint + - new + - run + - serve + - test + - update + - version + multiple: true + validations: + required: true + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of the problem or missing capability. + validations: + required: true + - type: textarea + id: desired-solution + attributes: + label: Describe the solution you'd like + description: If you have a solution in mind, please describe it. + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: Have you considered any alternative solutions or workarounds? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/3-docs-bug.md b/.github/ISSUE_TEMPLATE/3-docs-bug.md deleted file mode 100644 index 7cd9ec28753a..000000000000 --- a/.github/ISSUE_TEMPLATE/3-docs-bug.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "📚 Docs or angular.io issue report" -about: Report an issue in Angular's documentation or angular.io application - ---- - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 - -Please file any Docs or angular.io issues at: https://github.com/angular/angular/issues/new/choose - -For the time being, we keep Angular AIO issues in a separate repository. - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 diff --git a/.github/ISSUE_TEMPLATE/4-security-issue-disclosure.md b/.github/ISSUE_TEMPLATE/4-security-issue-disclosure.md deleted file mode 100644 index 70736318d2a3..000000000000 --- a/.github/ISSUE_TEMPLATE/4-security-issue-disclosure.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: ⚠️ Security issue disclosure -about: Report a security issue in Angular Framework, Material, or CLI - ---- - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 - -Please read https://angular.io/guide/security#report-issues on how to disclose security related issues. - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 diff --git a/.github/ISSUE_TEMPLATE/5-support-request.md b/.github/ISSUE_TEMPLATE/5-support-request.md deleted file mode 100644 index cdbd2e887db7..000000000000 --- a/.github/ISSUE_TEMPLATE/5-support-request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: "❓ Support request" -about: Questions and requests for support - ---- - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 - -Please do not file questions or support requests on the GitHub issues tracker. - -You can get your questions answered using other communication channels. Please see: -https://github.com/angular/angular-cli/blob/master/CONTRIBUTING.md#question - -Thank you! - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 diff --git a/.github/ISSUE_TEMPLATE/6-angular-framework.md b/.github/ISSUE_TEMPLATE/6-angular-framework.md deleted file mode 100644 index 8a689c55de35..000000000000 --- a/.github/ISSUE_TEMPLATE/6-angular-framework.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "⚡Angular Framework" -about: Issues and feature requests for Angular Framework - ---- - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 - -Please file any Angular Framework issues at: https://github.com/angular/angular/issues/new/choose - -For the time being, we keep Angular issues in a separate repository. - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 diff --git a/.github/ISSUE_TEMPLATE/7-angular-material.md b/.github/ISSUE_TEMPLATE/7-angular-material.md deleted file mode 100644 index fab3fe5b67c8..000000000000 --- a/.github/ISSUE_TEMPLATE/7-angular-material.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "\U0001F48E Angular Material" -about: Issues and feature requests for Angular Material - ---- - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 - -Please file any Angular Material issues at: https://github.com/angular/material2/issues/new - -For the time being, we keep Angular Material issues in a separate repository. - -🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..5764ed46e6a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +blank_issues_enabled: false +contact_links: + - name: Docs or angular.io issue report + url: https://github.com/angular/angular/issues/new + about: Report an issue in Angular's documentation or angular.io application + - name: Security issue disclosure + url: https://angular.io/guide/security#report-issues + about: Report a security issue in Angular Framework, Material, or CLI + - name: Support request + url: https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#question + about: Questions and requests for support. + - name: Angular Framework + url: https://github.com/angular/angular/issues/new/choose + about: Issues and feature requests for Angular Framework + - name: Angular Material + url: https://github.com/angular/components/issues/new/choose + about: Issues and feature requests for Angular Material diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000000..3214dde0a4f4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,43 @@ +## PR Checklist + +Please check to confirm your PR fulfills the following requirements: + + + +- [ ] The commit message follows our guidelines: https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#-commit-message-guidelines +- [ ] Tests for the changes have been added (for bug fixes / features) +- [ ] Docs have been added / updated (for bug fixes / features) + +## PR Type + +What kind of change does this PR introduce? + + + +- [ ] Bugfix +- [ ] Feature +- [ ] Code style update (formatting, local variables) +- [ ] Refactoring (no functional changes, no api changes) +- [ ] Build related changes +- [ ] CI related changes +- [ ] Documentation content changes +- [ ] Other... Please describe: + +## What is the current behavior? + + + +Issue Number: N/A + +## What is the new behavior? + + + +## Does this PR introduce a breaking change? + +- [ ] Yes +- [ ] No + + + +## Other information diff --git a/.github/SAVED_REPLIES.md b/.github/SAVED_REPLIES.md index 29e19832903c..06fb24cd1cd6 100644 --- a/.github/SAVED_REPLIES.md +++ b/.github/SAVED_REPLIES.md @@ -4,80 +4,80 @@ The following are canned responses that the Angular CLI team should use to close Since GitHub currently doesn't allow us to have a repository-wide or organization-wide list of [saved replies](https://help.github.com/articles/working-with-saved-replies/), these replies need to be maintained by individual team members. Since the responses can be modified in the future, all responses are versioned to simplify the process of keeping the responses up to date. - ## Angular CLI: Already Fixed (v1) + ``` Thanks for reporting this issue. Luckily, it has already been fixed in one of the recent releases. Please update to the most recent version to resolve the problem. If the problem persists in your application after upgrading, please open a new issue, provide a simple repository reproducing the problem, and describe the difference between the expected and current behavior. You can use `ng new repro-app` to create a new project where you reproduce the problem. ``` - ## Angular CLI: Don't Understand (v1) + ``` I'm sorry, but we don't understand the problem you are reporting. If the problem persists, please open a new issue, provide a simple repository reproducing the problem, and describe the difference between the expected and current behavior. You can use `ng new repro-app` to create a new project where you reproduce the problem. ``` - ## Angular CLI: Duplicate (v1.1) + ``` Thanks for reporting this issue. However, this issue is a duplicate of #. Please subscribe to that issue for future updates. ``` - ## Angular CLI: Insufficient Information Provided (v1) + ``` -Thanks for reporting this issue. However, you didn't provide sufficient information for us to understand and reproduce the problem. Please check out [our submission guidelines](https://github.com/angular/angular-cli/blob/master/CONTRIBUTING.md#-submitting-an-issue) to understand why we can't act on issues that are lacking important information. +Thanks for reporting this issue. However, you didn't provide sufficient information for us to understand and reproduce the problem. Please check out [our submission guidelines](https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#-submitting-an-issue) to understand why we can't act on issues that are lacking important information. If the problem persists, please file a new issue and ensure you provide all of the required information when filling out the issue template. ``` - ## Angular CLI: NPM install issue (v1) + ``` This seems like a problem with your node/npm and not with Angular CLI. Please have a look at the [fixing npm permissions page](https://docs.npmjs.com/getting-started/fixing-npm-permissions), [common errors page](https://docs.npmjs.com/troubleshooting/common-errors), [npm issue tracker](https://github.com/npm/npm/issues), or open a new issue if the problem you are experiencing isn't known. ``` - ## Angular CLI: Issue Outside of Angular CLI (v1.1) + ``` I'm sorry, but this issue is not caused by Angular CLI. Please contact the author(s) of the project or file an issue on their issue tracker. ``` - ## Angular CLI: Non-reproducible (v1) + ``` I'm sorry, but we can't reproduce the problem following the instructions you provided. Remember that we have a large number of issues to resolve, and have only a limited amount of time to reproduce your issue. Short, explicit instructions make it much more likely we'll be able to reproduce the problem so we can fix it. -If the problem persists, please open a new issue following [our submission guidelines](https://github.com/angular/angular-cli/blob/master/CONTRIBUTING.md#-submitting-an-issue). +If the problem persists, please open a new issue following [our submission guidelines](https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#-submitting-an-issue). A good way to make a minimal repro is to create a new app via `ng new repro-app` and adding the minimum possible code to show the problem. Then you can push this repository to github and link it here. ``` - ## Angular CLI: Obsolete (v1) + ``` Thanks for reporting this issue. This issue is now obsolete due to changes in the recent releases. Please update to the most recent Angular CLI version. If the problem persists after upgrading, please open a new issue, provide a simple repository reproducing the problem, and describe the difference between the expected and current behavior. ``` - ## Angular CLI: Support Request (v1) + ``` Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on [StackOverflow](http://stackoverflow.com/) using tag `angular-cli`. -If you are wondering why we don't resolve support issues via the issue tracker, please [check out this explanation](https://github.com/angular/angular-cli/blob/master/CONTRIBUTING.md#-got-a-question-or-problem). +If you are wondering why we don't resolve support issues via the issue tracker, please [check out this explanation](https://github.com/angular/angular-cli/blob/main/CONTRIBUTING.md#-got-a-question-or-problem). ``` - ## Angular CLI: Static Analysis errors (v1) + ``` Hello, errors like `Error encountered resolving symbol values statically` mean that there has been some problem in statically analyzing your app. @@ -93,6 +93,7 @@ In that case, please open an issue in https://github.com/angular/angular. ``` ## Angular CLI: Lockfiles (v1) + ``` I'd like to remind everyone that **you only have reproducible installs if you use a lockfile**. Both [NPM v5+](https://docs.npmjs.com/files/package-locks) and [Yarn](https://yarnpkg.com/lang/en/docs/yarn-lock/) support lockfiles. If your CI works one day but not the next and you did not change your code or `package.json`, it is likely because one of your dependencies had a bad release and you did not have a lockfile. diff --git a/.github/angular-robot.yml b/.github/angular-robot.yml index 03cda7b51d33..d5105974613c 100644 --- a/.github/angular-robot.yml +++ b/.github/angular-robot.yml @@ -5,20 +5,22 @@ merge: # the status will be added to your pull requests status: # set to true to disable - disabled: false + disabled: true # the name of the status - context: "ci/angular: merge status" + context: 'ci/angular: merge status' # text to show when all checks pass - successText: "All checks passed!" + successText: 'All checks passed!' # text to show when some checks are failing - failureText: "The following checks are failing:" + failureText: 'The following checks are failing:' # comment that will be added to a PR when there is a conflict, leave empty or set to false to disable - mergeConflictComment: "Hi @{{PRAuthor}}! This PR has merge conflicts due to recent upstream merges. -\nPlease help to unblock it by resolving these conflicts. Thanks!" + mergeConflictComment: >- + Hi @{{PRAuthor}}! This PR has merge conflicts due to recent upstream merges. + + Please help to unblock it by resolving these conflicts. Thanks! # label to monitor - mergeLabel: "action: merge" + mergeLabel: 'action: merge' # list of checks that will determine if the merge label can be added checks: @@ -28,73 +30,63 @@ merge: requireReviews: true # list of labels that a PR needs to have, checked with a regexp (e.g. "target:" will work for the label "target: major") requiredLabels: - - "target: *" - - "cla: yes" + - 'target: *' # list of labels that a PR shouldn't have, checked after the required labels with a regexp forbiddenLabels: - - "action: cleanup" - - "action: review" - - "PR state: blocked" - - "cla: no" + - 'action: cleanup' + - 'action: review' + - 'PR state: blocked' # list of PR statuses that need to be successful - requiredStatuses: - - "ci/circleci: build" - - "ci/circleci: setup" - - "ci/circleci: lint" - - "ci/circleci: validate" - - "ci/circleci: test" - - "ci/circleci: e2e-cli-win" - - "ci/circleci: e2e-cli" - - "ci/circleci: test-browsers" - - "ci/angular: size" - - "cla/google" + # NOTE: Required PR statuses are now exclusively handled via Github configuration + requiredStatuses: [] # the comment that will be added when the merge label is added despite failing checks, leave empty or set to false to disable # {{MERGE_LABEL}} will be replaced by the value of the mergeLabel option # {{PLACEHOLDER}} will be replaced by the list of failing checks - mergeRemovedComment: "I see that you just added the `{{MERGE_LABEL}}` label, but the following checks are still failing: -\n{{PLACEHOLDER}} -\n -\n**If you want your PR to be merged, it has to pass all the CI checks.** -\n -\nIf you can't get the PR to a green state due to flakes or broken master, please try rebasing to master and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help." + mergeRemovedComment: >- + I see that you just added the `{{MERGE_LABEL}}` label, but the following + checks are still failing: + + {{PLACEHOLDER}} + + **If you want your PR to be merged, it has to pass all the CI checks.** + If you can't get the PR to a green state due to flakes or broken `main`, + please try rebasing to `main` and/or restarting the CI job. If that fails + and you believe that the issue is not due to your change, please contact the + caretaker and ask for help. # options for the triage plugin triage: # set to true to disable - disabled: false + disabled: true # number of the milestone to apply when the issue has not been triaged yet needsTriageMilestone: 11, # number of the milestone to apply when the issue is triaged defaultMilestone: 12, # arrays of labels that determine if an issue has been triaged by the caretaker l1TriageLabels: - - - - "comp: *" + - - 'comp: *' # arrays of labels that determine if an issue has been fully triaged l2TriageLabels: - - - - "type: bug/fix" - - "severity*" - - "freq*" - - "comp: *" - - - - "type: feature" - - "comp: *" - - - - "type: refactor" - - "comp: *" - - - - "type: RFC / Discussion / question" - - "comp: *" - - - - "type: docs" - - "comp: *" + - - 'type: bug/fix' + - 'severity*' + - 'freq*' + - 'comp: *' + - - 'type: feature' + - 'comp: *' + - - 'type: refactor' + - 'comp: *' + - - 'type: RFC / Discussion / question' + - 'comp: *' + - - 'type: docs' + - 'comp: *' # Size checking size: - circleCiStatusName: "ci/circleci: e2e-cli" + # Size checking for production build is performed via the E2E test `build/prod-build` + disabled: true + circleCiStatusName: 'ci/circleci: e2e-cli' maxSizeIncrease: 10000 comment: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6499bb344192..cc8aa2b2c506 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,16 +1,16 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/" + - package-ecosystem: 'npm' + directory: '/' schedule: - interval: "daily" + interval: 'daily' commit-message: - prefix: "build" + prefix: 'build' labels: - - "comp: build & ci" - - "target: patch" - - "action: merge" + - 'comp: build & ci' + - 'target: patch' + - 'action: merge' # Disable version updates # This does not affect security updates open-pull-requests-limit: 0 diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml new file mode 100644 index 000000000000..592e79f60eac --- /dev/null +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -0,0 +1,21 @@ +name: DevInfra + +on: + push: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review, labeled] + +# Declare default permissions as read only. +permissions: + contents: read + +jobs: + assistant_to_the_branch_manager: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + persist-credentials: false + - uses: angular/dev-infra/github-actions/branch-manager@0c06b3d1a58ab12f4f9933efc78e33083d008d17 + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml new file mode 100644 index 000000000000..c45db3aa5d10 --- /dev/null +++ b/.github/workflows/dev-infra.yml @@ -0,0 +1,25 @@ +name: DevInfra + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +# Declare default permissions as read only. +permissions: + contents: read + +jobs: + labels: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + - uses: angular/dev-infra/github-actions/commit-message-based-labels@329aaf4c0225ab82d4914552e4a6ffa8db82faa3 + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} + post_approval_changes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + - uses: angular/dev-infra/github-actions/post-approval-changes@329aaf4c0225ab82d4914552e4a6ffa8db82faa3 + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/feature-requests.yml b/.github/workflows/feature-requests.yml new file mode 100644 index 000000000000..bdde6c145afd --- /dev/null +++ b/.github/workflows/feature-requests.yml @@ -0,0 +1,21 @@ +name: Feature request triage bot + +# Declare default permissions as read only. +permissions: + contents: read + +on: + schedule: + # Run at 13:00 every day + - cron: '0 13 * * *' + +jobs: + feature_triage: + # To prevent this action from running in forks, we only run it if the repository is exactly the + # angular/angular-cli repository. + if: github.repository == 'angular/angular-cli' + runs-on: ubuntu-latest + steps: + - uses: angular/dev-infra/github-actions/feature-request@329aaf4c0225ab82d4914552e4a6ffa8db82faa3 + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/lock-closed.yml b/.github/workflows/lock-closed.yml index b43e8601204e..9bf0b45c9fdb 100644 --- a/.github/workflows/lock-closed.yml +++ b/.github/workflows/lock-closed.yml @@ -1,5 +1,9 @@ name: Lock Inactive Issues +# Declare default permissions as read only. +permissions: + contents: read + on: schedule: # Run at 08:00 every day @@ -9,6 +13,6 @@ jobs: lock_closed: runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/lock-closed@0fc6f4d839e93312ed0dd9a2be88d4c11e947a0b + - uses: angular/dev-infra/github-actions/lock-closed@329aaf4c0225ab82d4914552e4a6ffa8db82faa3 with: lock-bot-key: ${{ secrets.LOCK_BOT_PRIVATE_KEY }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..ffc9e7bbaf1a --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,51 @@ +name: OpenSSF Scorecard +on: + branch_protection_rule: + schedule: + - cron: '0 2 * * 0' + push: + branches: [main] + workflow_dispatch: + +# Declare default permissions as read only. +permissions: + contents: read + +jobs: + analysis: + name: Scorecards analysis + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results + id-token: write + + steps: + - name: 'Checkout code' + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + with: + persist-credentials: false + + - name: 'Run analysis' + uses: ossf/scorecard-action@e363bfca00e752f91de7b7d2a77340e2e523cb18 # tag=v2.0.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + # Upload the results as artifacts. + - name: 'Upload artifact' + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: 'Upload to code-scanning' + uses: github/codeql-action/upload-sarif@18fe527fa8b29f134bb91f32f1a5dc5abb15ed7f # tag=v2.1.30 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 357df0969694..91652321da0e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,9 +41,11 @@ npm-debug.log* yarn-error.log* .ng_pkg_build/ .ng-dev.log +.ng-dev.err*.log .ng-dev.user* .husky/_ .bazelrc.user +.eslintcache # Mac OSX Finder files. **/.DS_Store diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec13899..000000000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000000..84611a58eec9 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname $0)/_/husky.sh" + +yarn -s ng-dev format staged; \ No newline at end of file diff --git a/.monorepo.json b/.monorepo.json index cab101ad56e0..d2c6803b9103 100644 --- a/.monorepo.json +++ b/.monorepo.json @@ -1,52 +1,8 @@ { - "badges": [ - [ - { - "image": "https://img.shields.io/circleci/project/github/angular/angular-cli/master.svg?label=circleci", - "label": "CircleCI branch", - "url": "https://circleci.com/gh/angular/angular-cli" - }, - { - "title": "![Dependency Status](https://david-dm.org/angular/angular-cli.svg)", - "url": "https://david-dm.org/angular/angular-cli" - }, - { - "title": "![devDependency Status](https://david-dm.org/angular/angular-cli/dev-status.svg)", - "url": "https://david-dm.org/angular/angular-cli?type=dev" - } - ], - [ - { - "label": "License", - "image": "https://img.shields.io/npm/l/@angular/cli.svg", - "url": "/LICENSE" - } - ], - [ - { - "label": "GitHub forks", - "image": "https://img.shields.io/github/forks/angular/angular-cli.svg?style=social&label=Fork", - "url": "https://github.com/angular/angular-cli/fork" - }, - { - "label": "GitHub stars", - "image": "https://img.shields.io/github/stars/angular/angular-cli.svg?style=social&label=Star", - "url": "https://github.com/angular/angular-cli" - } - ] - ], - "links": { - "Gitter": "https://gitter.im/angular/angular-cli", - "Contributing": "/CONTRIBUTING.md", - "Angular CLI": "http://github.com/angular/angular-cli" - }, "packages": { - "@_/benchmark": { - }, - "@_/builders": { - }, - "devkit": { - }, + "@_/benchmark": {}, + "@_/builders": {}, + "devkit": {}, "@angular/cli": { "name": "Angular CLI", "section": "Tooling", @@ -58,6 +14,16 @@ ], "snapshotRepo": "angular/cli-builds" }, + "@angular/create": { + "name": "Angular Create", + "section": "Misc", + "links": [ + { + "label": "README", + "url": "/packages/angular/create/README.md" + } + ] + }, "@angular/pwa": { "name": "Angular PWA Schematics", "section": "Schematics", @@ -82,26 +48,6 @@ "name": "Benchmark", "section": "Tooling" }, - "@angular-devkit/build-optimizer": { - "name": "Build Optimizer", - "links": [ - { - "label": "README", - "url": "/packages/angular_devkit/build_optimizer/README.md" - } - ], - "snapshotRepo": "angular/angular-devkit-build-optimizer-builds" - }, - "@angular-devkit/build-ng-packagr": { - "name": "Build NgPackagr", - "links": [ - { - "label": "README", - "url": "/packages/angular_devkit/build_ng_packagr/README.md" - } - ], - "snapshotRepo": "angular/angular-devkit-build-ng-packagr-builds" - }, "@angular-devkit/build-angular": { "name": "Build Angular", "links": [ diff --git a/.ng-dev/caretaker.mts b/.ng-dev/caretaker.mts new file mode 100644 index 000000000000..aeea38ccf355 --- /dev/null +++ b/.ng-dev/caretaker.mts @@ -0,0 +1,16 @@ +import { CaretakerConfig } from '@angular/ng-dev'; + +/** The configuration for `ng-dev caretaker` commands. */ +export const caretaker: CaretakerConfig = { + githubQueries: [ + { + name: 'Merge Queue', + query: `is:pr is:open status:success label:"action: merge"`, + }, + { + name: 'Merge Assistance Queue', + query: `is:pr is:open label:"action: merge-assistance"`, + }, + ], + caretakerGroup: 'angular-cli-caretaker', +}; diff --git a/.ng-dev/commit-message.mts b/.ng-dev/commit-message.mts new file mode 100644 index 000000000000..2dd960387eac --- /dev/null +++ b/.ng-dev/commit-message.mts @@ -0,0 +1,13 @@ +import { CommitMessageConfig } from '@angular/ng-dev'; +import packages from '../lib/packages.js'; + +/** + * The configuration for `ng-dev commit-message` commands. + */ +export const commitMessage: CommitMessageConfig = { + maxLineLength: Infinity, + minBodyLength: 0, + minBodyLengthTypeExcludes: ['docs'], + // Note: When changing this logic, also change the `contributing.ejs` file. + scopes: [...Object.keys(packages.packages)], +}; diff --git a/.ng-dev/commit-message.ts b/.ng-dev/commit-message.ts deleted file mode 100644 index 06bd8eb244fc..000000000000 --- a/.ng-dev/commit-message.ts +++ /dev/null @@ -1,22 +0,0 @@ -// tslint:disable-next-line: no-implicit-dependencies -import { COMMIT_TYPES, CommitMessageConfig, ScopeRequirement } from '@angular/dev-infra-private/commit-message/config'; -import { packages } from '../lib/packages'; - -/** - * The details for valid commit types. - * This is exported so that other tooling can access both the types and scopes from one location. - * Currently used in the contributing documentation template (scripts/templates/contributing.ejs) - */ -export { COMMIT_TYPES, ScopeRequirement }; - -/** - * The configuration for `ng-dev commit-message` commands. - */ -export const commitMessage: CommitMessageConfig = { - maxLineLength: Infinity, - minBodyLength: 0, - minBodyLengthTypeExcludes: ['docs'], - scopes: [ - ...Object.keys(packages), - ], -}; diff --git a/.ng-dev/config.mts b/.ng-dev/config.mts new file mode 100644 index 000000000000..6add9773c06c --- /dev/null +++ b/.ng-dev/config.mts @@ -0,0 +1,6 @@ +export { commitMessage } from './commit-message.mjs'; +export { format } from './format.mjs'; +export { github } from './github.mjs'; +export { pullRequest } from './pull-request.mjs'; +export { release } from './release.mjs'; +export { caretaker } from './caretaker.mjs'; diff --git a/.ng-dev/config.ts b/.ng-dev/config.ts deleted file mode 100644 index a5d99ef26f13..000000000000 --- a/.ng-dev/config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { commitMessage } from './commit-message'; -export { github } from './github'; -export { merge } from './merge'; diff --git a/.ng-dev/format.mts b/.ng-dev/format.mts new file mode 100644 index 000000000000..3cba8e9830a9 --- /dev/null +++ b/.ng-dev/format.mts @@ -0,0 +1,11 @@ +import { FormatConfig } from '@angular/ng-dev'; + +/** + * Configuration for the `ng-dev format` command. + */ +export const format: FormatConfig = { + 'prettier': { + matchers: ['**/*.{ts,js,json,yml,yaml,md}'], + }, + 'buildifier': true, +}; diff --git a/.ng-dev/github.mts b/.ng-dev/github.mts new file mode 100644 index 000000000000..408c672bb8a4 --- /dev/null +++ b/.ng-dev/github.mts @@ -0,0 +1,12 @@ +import { GithubConfig } from '@angular/ng-dev'; + +/** + * Github configuration for the ng-dev command. This repository is + * uses as remote for the merge script. + */ +export const github: GithubConfig = { + owner: 'angular', + name: 'angular-cli', + mainBranchName: 'main', + useNgDevAuthService: true, +}; diff --git a/.ng-dev/github.ts b/.ng-dev/github.ts deleted file mode 100644 index 9d5a406aaa14..000000000000 --- a/.ng-dev/github.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { GithubConfig } from '@angular/dev-infra-private/utils/config'; - -/** - * Github configuration for the ng-dev command. This repository is - * uses as remote for the merge script. - */ -export const github: GithubConfig = { - owner: 'angular', - name: 'angular-cli', -}; diff --git a/.ng-dev/merge.ts b/.ng-dev/merge.ts deleted file mode 100644 index eb41a469caad..000000000000 --- a/.ng-dev/merge.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DevInfraMergeConfig } from '@angular/dev-infra-private/pr/merge/config'; -import { getDefaultTargetLabelConfiguration } from '@angular/dev-infra-private/pr/merge/defaults'; -import { github } from './github'; -import { release } from './release'; - -/** - * Configuration for the merge tool in `ng-dev`. This sets up the labels which - * are respected by the merge script (e.g. the target labels). - */ -export const merge: DevInfraMergeConfig['merge'] = async api => { - return { - githubApiMerge: { - default: 'rebase', - labels: [ - {pattern: 'squash commits', method: 'squash'}, - ], - }, - claSignedLabel: 'cla: yes', - mergeReadyLabel: /^action: merge(-assistance)?/, - caretakerNoteLabel: /(action: merge-assistance)/, - commitMessageFixupLabel: 'commit message fixup', - // We can pick any of the NPM packages as we are in a monorepo where all packages are - // published together with the same version and branching. - labels: await getDefaultTargetLabelConfiguration(api, github, release), - }; -}; diff --git a/.ng-dev/pull-request.mts b/.ng-dev/pull-request.mts new file mode 100644 index 000000000000..ec2ddc850398 --- /dev/null +++ b/.ng-dev/pull-request.mts @@ -0,0 +1,12 @@ +import { PullRequestConfig } from '@angular/ng-dev'; + +/** + * Configuration for the merge tool in `ng-dev`. This sets up the labels which + * are respected by the merge script (e.g. the target labels). + */ +export const pullRequest: PullRequestConfig = { + githubApiMerge: { + default: 'rebase', + labels: [{ pattern: 'squash commits', method: 'squash' }], + }, +}; diff --git a/.ng-dev/release.mts b/.ng-dev/release.mts new file mode 100644 index 000000000000..8e2e2333b141 --- /dev/null +++ b/.ng-dev/release.mts @@ -0,0 +1,27 @@ +import '../lib/bootstrap-local.js'; + +import { ReleaseConfig } from '@angular/ng-dev'; +import packages from '../lib/packages.js'; +import buildPackages from '../scripts/build.js'; + +const npmPackages = Object.entries(packages.releasePackages).map(([name, { experimental }]) => ({ + name, + experimental, +})); + +/** Configuration for the `ng-dev release` command. */ +export const release: ReleaseConfig = { + representativeNpmPackage: '@angular/cli', + npmPackages, + buildPackages: () => buildPackages.default(), + releaseNotes: { + groupOrder: [ + '@angular/cli', + '@schematics/angular', + '@angular-devkit/architect-cli', + '@angular-devkit/schematics-cli', + ], + }, + publishRegistry: 'https://wombat-dressing-room.appspot.com/', + releasePrLabels: ['action: merge'], +}; diff --git a/.ng-dev/release.ts b/.ng-dev/release.ts deleted file mode 100644 index 175b763add66..000000000000 --- a/.ng-dev/release.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ReleaseConfig } from '@angular/dev-infra-private/release/config'; -import { packages } from '../lib/packages'; - -/** Configuration for the `ng-dev release` command. */ -export const release: ReleaseConfig = { - npmPackages: Object.keys(packages), - // TODO: Set up package building. - buildPackages: async () => [], - // TODO: Set up generating changelogs - generateReleaseNotesForHead: async () => {}, -}; diff --git a/.ng-dev/tsconfig.json b/.ng-dev/tsconfig.json new file mode 100644 index 000000000000..12cf63f79e32 --- /dev/null +++ b/.ng-dev/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "noEmit": true + }, + "include": ["**/*.mts"], + "exclude": [] +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000000..c42da845b449 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/.nvmrc b/.nvmrc index 6b17d228d335..a3eb5a03fa6a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.16.1 +14.20.0 diff --git a/.prettierignore b/.prettierignore index fbcd212375a5..20472fa0fa4a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,16 @@ -/etc/api +/bazel-out/ +/docs/design/analytics.md +/dist-schema/ +/goldens/public-api +/packages/angular_devkit/build_angular/test/ +/packages/angular_devkit/core/src/workspace/json/test/ +/packages/angular_devkit/schematics_cli/blank/project-files/ +/packages/angular_devkit/schematics_cli/blank/schematic-files/ +/packages/angular_devkit/schematics_cli/schematic/files/ +/README.md +/CONTRIBUTING.md +.yarn/ +dist/ +third_party/ +/tests/legacy-cli/e2e/assets/ +/tools/test/*.json \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 5e2863a11f68..8ef20e3c1ea0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "printWidth": 100, + "quoteProps": "preserve", "singleQuote": true, "trailingComma": "all" } diff --git a/.yarn/releases/yarn-1.22.10.cjs b/.yarn/releases/yarn-1.22.10.cjs deleted file mode 100755 index 68b1990b1d92..000000000000 --- a/.yarn/releases/yarn-1.22.10.cjs +++ /dev/null @@ -1,147392 +0,0 @@ -#!/usr/bin/env node -module.exports = -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 549); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports) { - -module.exports = require("path"); - -/***/ }), -/* 1 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (immutable) */ __webpack_exports__["a"] = __extends; -/* unused harmony export __assign */ -/* unused harmony export __rest */ -/* unused harmony export __decorate */ -/* unused harmony export __param */ -/* unused harmony export __metadata */ -/* unused harmony export __awaiter */ -/* unused harmony export __generator */ -/* unused harmony export __exportStar */ -/* unused harmony export __values */ -/* unused harmony export __read */ -/* unused harmony export __spread */ -/* unused harmony export __await */ -/* unused harmony export __asyncGenerator */ -/* unused harmony export __asyncDelegator */ -/* unused harmony export __asyncValues */ -/* unused harmony export __makeTemplateObject */ -/* unused harmony export __importStar */ -/* unused harmony export __importDefault */ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ -/* global Reflect, Promise */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - } - return __assign.apply(this, arguments); -} - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) - t[p[i]] = s[p[i]]; - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -} - -function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); -} - -function __awaiter(thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -function __exportStar(m, exports) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -} - -function __values(o) { - var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; - if (m) return m.call(o); - return { - next: function () { - if (o && i >= o.length) o = void 0; - return { value: o && o[i++], done: !o }; - } - }; -} - -function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; -} - -function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; -} - -function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); -} - -function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } -} - -function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } -} - -function __asyncValues(o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -} - -function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; - -function __importStar(mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result.default = mod; - return result; -} - -function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; -} - - -/***/ }), -/* 2 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -exports.__esModule = true; - -var _promise = __webpack_require__(227); - -var _promise2 = _interopRequireDefault(_promise); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = function (fn) { - return function () { - var gen = fn.apply(this, arguments); - return new _promise2.default(function (resolve, reject) { - function step(key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - - if (info.done) { - resolve(value); - } else { - return _promise2.default.resolve(value).then(function (value) { - step("next", value); - }, function (err) { - step("throw", err); - }); - } - } - - return step("next"); - }); - }; -}; - -/***/ }), -/* 3 */ -/***/ (function(module, exports) { - -module.exports = require("util"); - -/***/ }), -/* 4 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getFirstSuitableFolder = exports.readFirstAvailableStream = exports.makeTempDir = exports.hardlinksWork = exports.writeFilePreservingEol = exports.getFileSizeOnDisk = exports.walk = exports.symlink = exports.find = exports.readJsonAndFile = exports.readJson = exports.readFileAny = exports.hardlinkBulk = exports.copyBulk = exports.unlink = exports.glob = exports.link = exports.chmod = exports.lstat = exports.exists = exports.mkdirp = exports.stat = exports.access = exports.rename = exports.readdir = exports.realpath = exports.readlink = exports.writeFile = exports.open = exports.readFileBuffer = exports.lockQueue = exports.constants = undefined; - -var _asyncToGenerator2; - -function _load_asyncToGenerator() { - return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); -} - -let buildActionsForCopy = (() => { - var _ref = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { - - // - let build = (() => { - var _ref5 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - const src = data.src, - dest = data.dest, - type = data.type; - - const onFresh = data.onFresh || noop; - const onDone = data.onDone || noop; - - // TODO https://github.com/yarnpkg/yarn/issues/3751 - // related to bundled dependencies handling - if (files.has(dest.toLowerCase())) { - reporter.verbose(`The case-insensitive file ${dest} shouldn't be copied twice in one bulk copy`); - } else { - files.add(dest.toLowerCase()); - } - - if (type === 'symlink') { - yield mkdirp((_path || _load_path()).default.dirname(dest)); - onFresh(); - actions.symlink.push({ - dest, - linkname: src - }); - onDone(); - return; - } - - if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { - // ignored file - return; - } - - const srcStat = yield lstat(src); - let srcFiles; - - if (srcStat.isDirectory()) { - srcFiles = yield readdir(src); - } - - let destStat; - try { - // try accessing the destination - destStat = yield lstat(dest); - } catch (e) { - // proceed if destination doesn't exist, otherwise error - if (e.code !== 'ENOENT') { - throw e; - } - } - - // if destination exists - if (destStat) { - const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); - const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); - const bothFiles = srcStat.isFile() && destStat.isFile(); - - // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving - // us modes that aren't valid. investigate this, it's generally safe to proceed. - - /* if (srcStat.mode !== destStat.mode) { - try { - await access(dest, srcStat.mode); - } catch (err) {} - } */ - - if (bothFiles && artifactFiles.has(dest)) { - // this file gets changed during build, likely by a custom install script. Don't bother checking it. - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); - return; - } - - if (bothFiles && srcStat.size === destStat.size && (0, (_fsNormalized || _load_fsNormalized()).fileDatesEqual)(srcStat.mtime, destStat.mtime)) { - // we can safely assume this is the same file - onDone(); - reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.size, +srcStat.mtime)); - return; - } - - if (bothSymlinks) { - const srcReallink = yield readlink(src); - if (srcReallink === (yield readlink(dest))) { - // if both symlinks are the same then we can continue on - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); - return; - } - } - - if (bothFolders) { - // mark files that aren't in this folder as possibly extraneous - const destFiles = yield readdir(dest); - invariant(srcFiles, 'src files not initialised'); - - for (var _iterator4 = destFiles, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { - var _ref6; - - if (_isArray4) { - if (_i4 >= _iterator4.length) break; - _ref6 = _iterator4[_i4++]; - } else { - _i4 = _iterator4.next(); - if (_i4.done) break; - _ref6 = _i4.value; - } - - const file = _ref6; - - if (srcFiles.indexOf(file) < 0) { - const loc = (_path || _load_path()).default.join(dest, file); - possibleExtraneous.add(loc); - - if ((yield lstat(loc)).isDirectory()) { - for (var _iterator5 = yield readdir(loc), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { - var _ref7; - - if (_isArray5) { - if (_i5 >= _iterator5.length) break; - _ref7 = _iterator5[_i5++]; - } else { - _i5 = _iterator5.next(); - if (_i5.done) break; - _ref7 = _i5.value; - } - - const file = _ref7; - - possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); - } - } - } - } - } - } - - if (destStat && destStat.isSymbolicLink()) { - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); - destStat = null; - } - - if (srcStat.isSymbolicLink()) { - onFresh(); - const linkname = yield readlink(src); - actions.symlink.push({ - dest, - linkname - }); - onDone(); - } else if (srcStat.isDirectory()) { - if (!destStat) { - reporter.verbose(reporter.lang('verboseFileFolder', dest)); - yield mkdirp(dest); - } - - const destParts = dest.split((_path || _load_path()).default.sep); - while (destParts.length) { - files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); - destParts.pop(); - } - - // push all files to queue - invariant(srcFiles, 'src files not initialised'); - let remaining = srcFiles.length; - if (!remaining) { - onDone(); - } - for (var _iterator6 = srcFiles, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { - var _ref8; - - if (_isArray6) { - if (_i6 >= _iterator6.length) break; - _ref8 = _iterator6[_i6++]; - } else { - _i6 = _iterator6.next(); - if (_i6.done) break; - _ref8 = _i6.value; - } - - const file = _ref8; - - queue.push({ - dest: (_path || _load_path()).default.join(dest, file), - onFresh, - onDone: function (_onDone) { - function onDone() { - return _onDone.apply(this, arguments); - } - - onDone.toString = function () { - return _onDone.toString(); - }; - - return onDone; - }(function () { - if (--remaining === 0) { - onDone(); - } - }), - src: (_path || _load_path()).default.join(src, file) - }); - } - } else if (srcStat.isFile()) { - onFresh(); - actions.file.push({ - src, - dest, - atime: srcStat.atime, - mtime: srcStat.mtime, - mode: srcStat.mode - }); - onDone(); - } else { - throw new Error(`unsure how to copy this: ${src}`); - } - }); - - return function build(_x5) { - return _ref5.apply(this, arguments); - }; - })(); - - const artifactFiles = new Set(events.artifactFiles || []); - const files = new Set(); - - // initialise events - for (var _iterator = queue, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref2; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref2 = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref2 = _i.value; - } - - const item = _ref2; - - const onDone = item.onDone; - item.onDone = function () { - events.onProgress(item.dest); - if (onDone) { - onDone(); - } - }; - } - events.onStart(queue.length); - - // start building actions - const actions = { - file: [], - symlink: [], - link: [] - }; - - // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items - // at a time due to the requirement to push items onto the queue - while (queue.length) { - const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); - yield Promise.all(items.map(build)); - } - - // simulate the existence of some files to prevent considering them extraneous - for (var _iterator2 = artifactFiles, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { - var _ref3; - - if (_isArray2) { - if (_i2 >= _iterator2.length) break; - _ref3 = _iterator2[_i2++]; - } else { - _i2 = _iterator2.next(); - if (_i2.done) break; - _ref3 = _i2.value; - } - - const file = _ref3; - - if (possibleExtraneous.has(file)) { - reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); - possibleExtraneous.delete(file); - } - } - - for (var _iterator3 = possibleExtraneous, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { - var _ref4; - - if (_isArray3) { - if (_i3 >= _iterator3.length) break; - _ref4 = _iterator3[_i3++]; - } else { - _i3 = _iterator3.next(); - if (_i3.done) break; - _ref4 = _i3.value; - } - - const loc = _ref4; - - if (files.has(loc.toLowerCase())) { - possibleExtraneous.delete(loc); - } - } - - return actions; - }); - - return function buildActionsForCopy(_x, _x2, _x3, _x4) { - return _ref.apply(this, arguments); - }; -})(); - -let buildActionsForHardlink = (() => { - var _ref9 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { - - // - let build = (() => { - var _ref13 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - const src = data.src, - dest = data.dest; - - const onFresh = data.onFresh || noop; - const onDone = data.onDone || noop; - if (files.has(dest.toLowerCase())) { - // Fixes issue https://github.com/yarnpkg/yarn/issues/2734 - // When bulk hardlinking we have A -> B structure that we want to hardlink to A1 -> B1, - // package-linker passes that modules A1 and B1 need to be hardlinked, - // the recursive linking algorithm of A1 ends up scheduling files in B1 to be linked twice which will case - // an exception. - onDone(); - return; - } - files.add(dest.toLowerCase()); - - if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { - // ignored file - return; - } - - const srcStat = yield lstat(src); - let srcFiles; - - if (srcStat.isDirectory()) { - srcFiles = yield readdir(src); - } - - const destExists = yield exists(dest); - if (destExists) { - const destStat = yield lstat(dest); - - const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); - const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); - const bothFiles = srcStat.isFile() && destStat.isFile(); - - if (srcStat.mode !== destStat.mode) { - try { - yield access(dest, srcStat.mode); - } catch (err) { - // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving - // us modes that aren't valid. investigate this, it's generally safe to proceed. - reporter.verbose(err); - } - } - - if (bothFiles && artifactFiles.has(dest)) { - // this file gets changed during build, likely by a custom install script. Don't bother checking it. - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); - return; - } - - // correct hardlink - if (bothFiles && srcStat.ino !== null && srcStat.ino === destStat.ino) { - onDone(); - reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.ino)); - return; - } - - if (bothSymlinks) { - const srcReallink = yield readlink(src); - if (srcReallink === (yield readlink(dest))) { - // if both symlinks are the same then we can continue on - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); - return; - } - } - - if (bothFolders) { - // mark files that aren't in this folder as possibly extraneous - const destFiles = yield readdir(dest); - invariant(srcFiles, 'src files not initialised'); - - for (var _iterator10 = destFiles, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { - var _ref14; - - if (_isArray10) { - if (_i10 >= _iterator10.length) break; - _ref14 = _iterator10[_i10++]; - } else { - _i10 = _iterator10.next(); - if (_i10.done) break; - _ref14 = _i10.value; - } - - const file = _ref14; - - if (srcFiles.indexOf(file) < 0) { - const loc = (_path || _load_path()).default.join(dest, file); - possibleExtraneous.add(loc); - - if ((yield lstat(loc)).isDirectory()) { - for (var _iterator11 = yield readdir(loc), _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { - var _ref15; - - if (_isArray11) { - if (_i11 >= _iterator11.length) break; - _ref15 = _iterator11[_i11++]; - } else { - _i11 = _iterator11.next(); - if (_i11.done) break; - _ref15 = _i11.value; - } - - const file = _ref15; - - possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); - } - } - } - } - } - } - - if (srcStat.isSymbolicLink()) { - onFresh(); - const linkname = yield readlink(src); - actions.symlink.push({ - dest, - linkname - }); - onDone(); - } else if (srcStat.isDirectory()) { - reporter.verbose(reporter.lang('verboseFileFolder', dest)); - yield mkdirp(dest); - - const destParts = dest.split((_path || _load_path()).default.sep); - while (destParts.length) { - files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); - destParts.pop(); - } - - // push all files to queue - invariant(srcFiles, 'src files not initialised'); - let remaining = srcFiles.length; - if (!remaining) { - onDone(); - } - for (var _iterator12 = srcFiles, _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { - var _ref16; - - if (_isArray12) { - if (_i12 >= _iterator12.length) break; - _ref16 = _iterator12[_i12++]; - } else { - _i12 = _iterator12.next(); - if (_i12.done) break; - _ref16 = _i12.value; - } - - const file = _ref16; - - queue.push({ - onFresh, - src: (_path || _load_path()).default.join(src, file), - dest: (_path || _load_path()).default.join(dest, file), - onDone: function (_onDone2) { - function onDone() { - return _onDone2.apply(this, arguments); - } - - onDone.toString = function () { - return _onDone2.toString(); - }; - - return onDone; - }(function () { - if (--remaining === 0) { - onDone(); - } - }) - }); - } - } else if (srcStat.isFile()) { - onFresh(); - actions.link.push({ - src, - dest, - removeDest: destExists - }); - onDone(); - } else { - throw new Error(`unsure how to copy this: ${src}`); - } - }); - - return function build(_x10) { - return _ref13.apply(this, arguments); - }; - })(); - - const artifactFiles = new Set(events.artifactFiles || []); - const files = new Set(); - - // initialise events - for (var _iterator7 = queue, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { - var _ref10; - - if (_isArray7) { - if (_i7 >= _iterator7.length) break; - _ref10 = _iterator7[_i7++]; - } else { - _i7 = _iterator7.next(); - if (_i7.done) break; - _ref10 = _i7.value; - } - - const item = _ref10; - - const onDone = item.onDone || noop; - item.onDone = function () { - events.onProgress(item.dest); - onDone(); - }; - } - events.onStart(queue.length); - - // start building actions - const actions = { - file: [], - symlink: [], - link: [] - }; - - // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items - // at a time due to the requirement to push items onto the queue - while (queue.length) { - const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); - yield Promise.all(items.map(build)); - } - - // simulate the existence of some files to prevent considering them extraneous - for (var _iterator8 = artifactFiles, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { - var _ref11; - - if (_isArray8) { - if (_i8 >= _iterator8.length) break; - _ref11 = _iterator8[_i8++]; - } else { - _i8 = _iterator8.next(); - if (_i8.done) break; - _ref11 = _i8.value; - } - - const file = _ref11; - - if (possibleExtraneous.has(file)) { - reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); - possibleExtraneous.delete(file); - } - } - - for (var _iterator9 = possibleExtraneous, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { - var _ref12; - - if (_isArray9) { - if (_i9 >= _iterator9.length) break; - _ref12 = _iterator9[_i9++]; - } else { - _i9 = _iterator9.next(); - if (_i9.done) break; - _ref12 = _i9.value; - } - - const loc = _ref12; - - if (files.has(loc.toLowerCase())) { - possibleExtraneous.delete(loc); - } - } - - return actions; - }); - - return function buildActionsForHardlink(_x6, _x7, _x8, _x9) { - return _ref9.apply(this, arguments); - }; -})(); - -let copyBulk = exports.copyBulk = (() => { - var _ref17 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { - const events = { - onStart: _events && _events.onStart || noop, - onProgress: _events && _events.onProgress || noop, - possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), - ignoreBasenames: _events && _events.ignoreBasenames || [], - artifactFiles: _events && _events.artifactFiles || [] - }; - - const actions = yield buildActionsForCopy(queue, events, events.possibleExtraneous, reporter); - events.onStart(actions.file.length + actions.symlink.length + actions.link.length); - - const fileActions = actions.file; - - const currentlyWriting = new Map(); - - yield (_promise || _load_promise()).queue(fileActions, (() => { - var _ref18 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - let writePromise; - while (writePromise = currentlyWriting.get(data.dest)) { - yield writePromise; - } - - reporter.verbose(reporter.lang('verboseFileCopy', data.src, data.dest)); - const copier = (0, (_fsNormalized || _load_fsNormalized()).copyFile)(data, function () { - return currentlyWriting.delete(data.dest); - }); - currentlyWriting.set(data.dest, copier); - events.onProgress(data.dest); - return copier; - }); - - return function (_x14) { - return _ref18.apply(this, arguments); - }; - })(), CONCURRENT_QUEUE_ITEMS); - - // we need to copy symlinks last as they could reference files we were copying - const symlinkActions = actions.symlink; - yield (_promise || _load_promise()).queue(symlinkActions, function (data) { - const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); - reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); - return symlink(linkname, data.dest); - }); - }); - - return function copyBulk(_x11, _x12, _x13) { - return _ref17.apply(this, arguments); - }; -})(); - -let hardlinkBulk = exports.hardlinkBulk = (() => { - var _ref19 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { - const events = { - onStart: _events && _events.onStart || noop, - onProgress: _events && _events.onProgress || noop, - possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), - artifactFiles: _events && _events.artifactFiles || [], - ignoreBasenames: [] - }; - - const actions = yield buildActionsForHardlink(queue, events, events.possibleExtraneous, reporter); - events.onStart(actions.file.length + actions.symlink.length + actions.link.length); - - const fileActions = actions.link; - - yield (_promise || _load_promise()).queue(fileActions, (() => { - var _ref20 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - reporter.verbose(reporter.lang('verboseFileLink', data.src, data.dest)); - if (data.removeDest) { - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(data.dest); - } - yield link(data.src, data.dest); - }); - - return function (_x18) { - return _ref20.apply(this, arguments); - }; - })(), CONCURRENT_QUEUE_ITEMS); - - // we need to copy symlinks last as they could reference files we were copying - const symlinkActions = actions.symlink; - yield (_promise || _load_promise()).queue(symlinkActions, function (data) { - const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); - reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); - return symlink(linkname, data.dest); - }); - }); - - return function hardlinkBulk(_x15, _x16, _x17) { - return _ref19.apply(this, arguments); - }; -})(); - -let readFileAny = exports.readFileAny = (() => { - var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (files) { - for (var _iterator13 = files, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { - var _ref22; - - if (_isArray13) { - if (_i13 >= _iterator13.length) break; - _ref22 = _iterator13[_i13++]; - } else { - _i13 = _iterator13.next(); - if (_i13.done) break; - _ref22 = _i13.value; - } - - const file = _ref22; - - if (yield exists(file)) { - return readFile(file); - } - } - return null; - }); - - return function readFileAny(_x19) { - return _ref21.apply(this, arguments); - }; -})(); - -let readJson = exports.readJson = (() => { - var _ref23 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { - return (yield readJsonAndFile(loc)).object; - }); - - return function readJson(_x20) { - return _ref23.apply(this, arguments); - }; -})(); - -let readJsonAndFile = exports.readJsonAndFile = (() => { - var _ref24 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { - const file = yield readFile(loc); - try { - return { - object: (0, (_map || _load_map()).default)(JSON.parse(stripBOM(file))), - content: file - }; - } catch (err) { - err.message = `${loc}: ${err.message}`; - throw err; - } - }); - - return function readJsonAndFile(_x21) { - return _ref24.apply(this, arguments); - }; -})(); - -let find = exports.find = (() => { - var _ref25 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (filename, dir) { - const parts = dir.split((_path || _load_path()).default.sep); - - while (parts.length) { - const loc = parts.concat(filename).join((_path || _load_path()).default.sep); - - if (yield exists(loc)) { - return loc; - } else { - parts.pop(); - } - } - - return false; - }); - - return function find(_x22, _x23) { - return _ref25.apply(this, arguments); - }; -})(); - -let symlink = exports.symlink = (() => { - var _ref26 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (src, dest) { - if (process.platform !== 'win32') { - // use relative paths otherwise which will be retained if the directory is moved - src = (_path || _load_path()).default.relative((_path || _load_path()).default.dirname(dest), src); - // When path.relative returns an empty string for the current directory, we should instead use - // '.', which is a valid fs.symlink target. - src = src || '.'; - } - - try { - const stats = yield lstat(dest); - if (stats.isSymbolicLink()) { - const resolved = dest; - if (resolved === src) { - return; - } - } - } catch (err) { - if (err.code !== 'ENOENT') { - throw err; - } - } - - // We use rimraf for unlink which never throws an ENOENT on missing target - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); - - if (process.platform === 'win32') { - // use directory junctions if possible on win32, this requires absolute paths - yield fsSymlink(src, dest, 'junction'); - } else { - yield fsSymlink(src, dest); - } - }); - - return function symlink(_x24, _x25) { - return _ref26.apply(this, arguments); - }; -})(); - -let walk = exports.walk = (() => { - var _ref27 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir, relativeDir, ignoreBasenames = new Set()) { - let files = []; - - let filenames = yield readdir(dir); - if (ignoreBasenames.size) { - filenames = filenames.filter(function (name) { - return !ignoreBasenames.has(name); - }); - } - - for (var _iterator14 = filenames, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { - var _ref28; - - if (_isArray14) { - if (_i14 >= _iterator14.length) break; - _ref28 = _iterator14[_i14++]; - } else { - _i14 = _iterator14.next(); - if (_i14.done) break; - _ref28 = _i14.value; - } - - const name = _ref28; - - const relative = relativeDir ? (_path || _load_path()).default.join(relativeDir, name) : name; - const loc = (_path || _load_path()).default.join(dir, name); - const stat = yield lstat(loc); - - files.push({ - relative, - basename: name, - absolute: loc, - mtime: +stat.mtime - }); - - if (stat.isDirectory()) { - files = files.concat((yield walk(loc, relative, ignoreBasenames))); - } - } - - return files; - }); - - return function walk(_x26, _x27) { - return _ref27.apply(this, arguments); - }; -})(); - -let getFileSizeOnDisk = exports.getFileSizeOnDisk = (() => { - var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { - const stat = yield lstat(loc); - const size = stat.size, - blockSize = stat.blksize; - - - return Math.ceil(size / blockSize) * blockSize; - }); - - return function getFileSizeOnDisk(_x28) { - return _ref29.apply(this, arguments); - }; -})(); - -let getEolFromFile = (() => { - var _ref30 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path) { - if (!(yield exists(path))) { - return undefined; - } - - const buffer = yield readFileBuffer(path); - - for (let i = 0; i < buffer.length; ++i) { - if (buffer[i] === cr) { - return '\r\n'; - } - if (buffer[i] === lf) { - return '\n'; - } - } - return undefined; - }); - - return function getEolFromFile(_x29) { - return _ref30.apply(this, arguments); - }; -})(); - -let writeFilePreservingEol = exports.writeFilePreservingEol = (() => { - var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path, data) { - const eol = (yield getEolFromFile(path)) || (_os || _load_os()).default.EOL; - if (eol !== '\n') { - data = data.replace(/\n/g, eol); - } - yield writeFile(path, data); - }); - - return function writeFilePreservingEol(_x30, _x31) { - return _ref31.apply(this, arguments); - }; -})(); - -let hardlinksWork = exports.hardlinksWork = (() => { - var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir) { - const filename = 'test-file' + Math.random(); - const file = (_path || _load_path()).default.join(dir, filename); - const fileLink = (_path || _load_path()).default.join(dir, filename + '-link'); - try { - yield writeFile(file, 'test'); - yield link(file, fileLink); - } catch (err) { - return false; - } finally { - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(file); - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(fileLink); - } - return true; - }); - - return function hardlinksWork(_x32) { - return _ref32.apply(this, arguments); - }; -})(); - -// not a strict polyfill for Node's fs.mkdtemp - - -let makeTempDir = exports.makeTempDir = (() => { - var _ref33 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (prefix) { - const dir = (_path || _load_path()).default.join((_os || _load_os()).default.tmpdir(), `yarn-${prefix || ''}-${Date.now()}-${Math.random()}`); - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dir); - yield mkdirp(dir); - return dir; - }); - - return function makeTempDir(_x33) { - return _ref33.apply(this, arguments); - }; -})(); - -let readFirstAvailableStream = exports.readFirstAvailableStream = (() => { - var _ref34 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths) { - for (var _iterator15 = paths, _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { - var _ref35; - - if (_isArray15) { - if (_i15 >= _iterator15.length) break; - _ref35 = _iterator15[_i15++]; - } else { - _i15 = _iterator15.next(); - if (_i15.done) break; - _ref35 = _i15.value; - } - - const path = _ref35; - - try { - const fd = yield open(path, 'r'); - return (_fs || _load_fs()).default.createReadStream(path, { fd }); - } catch (err) { - // Try the next one - } - } - return null; - }); - - return function readFirstAvailableStream(_x34) { - return _ref34.apply(this, arguments); - }; -})(); - -let getFirstSuitableFolder = exports.getFirstSuitableFolder = (() => { - var _ref36 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths, mode = constants.W_OK | constants.X_OK) { - const result = { - skipped: [], - folder: null - }; - - for (var _iterator16 = paths, _isArray16 = Array.isArray(_iterator16), _i16 = 0, _iterator16 = _isArray16 ? _iterator16 : _iterator16[Symbol.iterator]();;) { - var _ref37; - - if (_isArray16) { - if (_i16 >= _iterator16.length) break; - _ref37 = _iterator16[_i16++]; - } else { - _i16 = _iterator16.next(); - if (_i16.done) break; - _ref37 = _i16.value; - } - - const folder = _ref37; - - try { - yield mkdirp(folder); - yield access(folder, mode); - - result.folder = folder; - - return result; - } catch (error) { - result.skipped.push({ - error, - folder - }); - } - } - return result; - }); - - return function getFirstSuitableFolder(_x35) { - return _ref36.apply(this, arguments); - }; -})(); - -exports.copy = copy; -exports.readFile = readFile; -exports.readFileRaw = readFileRaw; -exports.normalizeOS = normalizeOS; - -var _fs; - -function _load_fs() { - return _fs = _interopRequireDefault(__webpack_require__(5)); -} - -var _glob; - -function _load_glob() { - return _glob = _interopRequireDefault(__webpack_require__(99)); -} - -var _os; - -function _load_os() { - return _os = _interopRequireDefault(__webpack_require__(46)); -} - -var _path; - -function _load_path() { - return _path = _interopRequireDefault(__webpack_require__(0)); -} - -var _blockingQueue; - -function _load_blockingQueue() { - return _blockingQueue = _interopRequireDefault(__webpack_require__(110)); -} - -var _promise; - -function _load_promise() { - return _promise = _interopRequireWildcard(__webpack_require__(50)); -} - -var _promise2; - -function _load_promise2() { - return _promise2 = __webpack_require__(50); -} - -var _map; - -function _load_map() { - return _map = _interopRequireDefault(__webpack_require__(29)); -} - -var _fsNormalized; - -function _load_fsNormalized() { - return _fsNormalized = __webpack_require__(218); -} - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const constants = exports.constants = typeof (_fs || _load_fs()).default.constants !== 'undefined' ? (_fs || _load_fs()).default.constants : { - R_OK: (_fs || _load_fs()).default.R_OK, - W_OK: (_fs || _load_fs()).default.W_OK, - X_OK: (_fs || _load_fs()).default.X_OK -}; - -const lockQueue = exports.lockQueue = new (_blockingQueue || _load_blockingQueue()).default('fs lock'); - -const readFileBuffer = exports.readFileBuffer = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readFile); -const open = exports.open = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.open); -const writeFile = exports.writeFile = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.writeFile); -const readlink = exports.readlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readlink); -const realpath = exports.realpath = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.realpath); -const readdir = exports.readdir = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readdir); -const rename = exports.rename = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.rename); -const access = exports.access = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.access); -const stat = exports.stat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.stat); -const mkdirp = exports.mkdirp = (0, (_promise2 || _load_promise2()).promisify)(__webpack_require__(145)); -const exists = exports.exists = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.exists, true); -const lstat = exports.lstat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.lstat); -const chmod = exports.chmod = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.chmod); -const link = exports.link = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.link); -const glob = exports.glob = (0, (_promise2 || _load_promise2()).promisify)((_glob || _load_glob()).default); -exports.unlink = (_fsNormalized || _load_fsNormalized()).unlink; - -// fs.copyFile uses the native file copying instructions on the system, performing much better -// than any JS-based solution and consumes fewer resources. Repeated testing to fine tune the -// concurrency level revealed 128 as the sweet spot on a quad-core, 16 CPU Intel system with SSD. - -const CONCURRENT_QUEUE_ITEMS = (_fs || _load_fs()).default.copyFile ? 128 : 4; - -const fsSymlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.symlink); -const invariant = __webpack_require__(9); -const stripBOM = __webpack_require__(160); - -const noop = () => {}; - -function copy(src, dest, reporter) { - return copyBulk([{ src, dest }], reporter); -} - -function _readFile(loc, encoding) { - return new Promise((resolve, reject) => { - (_fs || _load_fs()).default.readFile(loc, encoding, function (err, content) { - if (err) { - reject(err); - } else { - resolve(content); - } - }); - }); -} - -function readFile(loc) { - return _readFile(loc, 'utf8').then(normalizeOS); -} - -function readFileRaw(loc) { - return _readFile(loc, 'binary'); -} - -function normalizeOS(body) { - return body.replace(/\r\n/g, '\n'); -} - -const cr = '\r'.charCodeAt(0); -const lf = '\n'.charCodeAt(0); - -/***/ }), -/* 5 */ -/***/ (function(module, exports) { - -module.exports = require("fs"); - -/***/ }), -/* 6 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -class MessageError extends Error { - constructor(msg, code) { - super(msg); - this.code = code; - } - -} - -exports.MessageError = MessageError; -class ProcessSpawnError extends MessageError { - constructor(msg, code, process) { - super(msg, code); - this.process = process; - } - -} - -exports.ProcessSpawnError = ProcessSpawnError; -class SecurityError extends MessageError {} - -exports.SecurityError = SecurityError; -class ProcessTermError extends MessageError {} - -exports.ProcessTermError = ProcessTermError; -class ResponseError extends Error { - constructor(msg, responseCode) { - super(msg); - this.responseCode = responseCode; - } - -} - -exports.ResponseError = ResponseError; -class OneTimePasswordError extends Error {} -exports.OneTimePasswordError = OneTimePasswordError; - -/***/ }), -/* 7 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subscriber; }); -/* unused harmony export SafeSubscriber */ -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_isFunction__ = __webpack_require__(154); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__Observer__ = __webpack_require__(420); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Subscription__ = __webpack_require__(25); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__internal_symbol_rxSubscriber__ = __webpack_require__(321); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__config__ = __webpack_require__(185); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__util_hostReportError__ = __webpack_require__(323); -/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ - - - - - - - -var Subscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](Subscriber, _super); - function Subscriber(destinationOrNext, error, complete) { - var _this = _super.call(this) || this; - _this.syncErrorValue = null; - _this.syncErrorThrown = false; - _this.syncErrorThrowable = false; - _this.isStopped = false; - _this._parentSubscription = null; - switch (arguments.length) { - case 0: - _this.destination = __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]; - break; - case 1: - if (!destinationOrNext) { - _this.destination = __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]; - break; - } - if (typeof destinationOrNext === 'object') { - if (destinationOrNext instanceof Subscriber) { - _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable; - _this.destination = destinationOrNext; - destinationOrNext.add(_this); - } - else { - _this.syncErrorThrowable = true; - _this.destination = new SafeSubscriber(_this, destinationOrNext); - } - break; - } - default: - _this.syncErrorThrowable = true; - _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete); - break; - } - return _this; - } - Subscriber.prototype[__WEBPACK_IMPORTED_MODULE_4__internal_symbol_rxSubscriber__["a" /* rxSubscriber */]] = function () { return this; }; - Subscriber.create = function (next, error, complete) { - var subscriber = new Subscriber(next, error, complete); - subscriber.syncErrorThrowable = false; - return subscriber; - }; - Subscriber.prototype.next = function (value) { - if (!this.isStopped) { - this._next(value); - } - }; - Subscriber.prototype.error = function (err) { - if (!this.isStopped) { - this.isStopped = true; - this._error(err); - } - }; - Subscriber.prototype.complete = function () { - if (!this.isStopped) { - this.isStopped = true; - this._complete(); - } - }; - Subscriber.prototype.unsubscribe = function () { - if (this.closed) { - return; - } - this.isStopped = true; - _super.prototype.unsubscribe.call(this); - }; - Subscriber.prototype._next = function (value) { - this.destination.next(value); - }; - Subscriber.prototype._error = function (err) { - this.destination.error(err); - this.unsubscribe(); - }; - Subscriber.prototype._complete = function () { - this.destination.complete(); - this.unsubscribe(); - }; - Subscriber.prototype._unsubscribeAndRecycle = function () { - var _a = this, _parent = _a._parent, _parents = _a._parents; - this._parent = null; - this._parents = null; - this.unsubscribe(); - this.closed = false; - this.isStopped = false; - this._parent = _parent; - this._parents = _parents; - this._parentSubscription = null; - return this; - }; - return Subscriber; -}(__WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */])); - -var SafeSubscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](SafeSubscriber, _super); - function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) { - var _this = _super.call(this) || this; - _this._parentSubscriber = _parentSubscriber; - var next; - var context = _this; - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isFunction__["a" /* isFunction */])(observerOrNext)) { - next = observerOrNext; - } - else if (observerOrNext) { - next = observerOrNext.next; - error = observerOrNext.error; - complete = observerOrNext.complete; - if (observerOrNext !== __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]) { - context = Object.create(observerOrNext); - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isFunction__["a" /* isFunction */])(context.unsubscribe)) { - _this.add(context.unsubscribe.bind(context)); - } - context.unsubscribe = _this.unsubscribe.bind(_this); - } - } - _this._context = context; - _this._next = next; - _this._error = error; - _this._complete = complete; - return _this; - } - SafeSubscriber.prototype.next = function (value) { - if (!this.isStopped && this._next) { - var _parentSubscriber = this._parentSubscriber; - if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(this._next, value); - } - else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) { - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.error = function (err) { - if (!this.isStopped) { - var _parentSubscriber = this._parentSubscriber; - var useDeprecatedSynchronousErrorHandling = __WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling; - if (this._error) { - if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(this._error, err); - this.unsubscribe(); - } - else { - this.__tryOrSetError(_parentSubscriber, this._error, err); - this.unsubscribe(); - } - } - else if (!_parentSubscriber.syncErrorThrowable) { - this.unsubscribe(); - if (useDeprecatedSynchronousErrorHandling) { - throw err; - } - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - } - else { - if (useDeprecatedSynchronousErrorHandling) { - _parentSubscriber.syncErrorValue = err; - _parentSubscriber.syncErrorThrown = true; - } - else { - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - } - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.complete = function () { - var _this = this; - if (!this.isStopped) { - var _parentSubscriber = this._parentSubscriber; - if (this._complete) { - var wrappedComplete = function () { return _this._complete.call(_this._context); }; - if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(wrappedComplete); - this.unsubscribe(); - } - else { - this.__tryOrSetError(_parentSubscriber, wrappedComplete); - this.unsubscribe(); - } - } - else { - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) { - try { - fn.call(this._context, value); - } - catch (err) { - this.unsubscribe(); - if (__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - throw err; - } - else { - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - } - } - }; - SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) { - if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - throw new Error('bad call'); - } - try { - fn.call(this._context, value); - } - catch (err) { - if (__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - parent.syncErrorValue = err; - parent.syncErrorThrown = true; - return true; - } - else { - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - return true; - } - } - return false; - }; - SafeSubscriber.prototype._unsubscribe = function () { - var _parentSubscriber = this._parentSubscriber; - this._context = null; - this._parentSubscriber = null; - _parentSubscriber.unsubscribe(); - }; - return SafeSubscriber; -}(Subscriber)); - -//# sourceMappingURL=Subscriber.js.map - - -/***/ }), -/* 8 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getPathKey = getPathKey; -const os = __webpack_require__(46); -const path = __webpack_require__(0); -const userHome = __webpack_require__(67).default; - -var _require = __webpack_require__(225); - -const getCacheDir = _require.getCacheDir, - getConfigDir = _require.getConfigDir, - getDataDir = _require.getDataDir; - -const isWebpackBundle = __webpack_require__(278); - -const DEPENDENCY_TYPES = exports.DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies', 'peerDependencies']; -const OWNED_DEPENDENCY_TYPES = exports.OWNED_DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies']; - -const RESOLUTIONS = exports.RESOLUTIONS = 'resolutions'; -const MANIFEST_FIELDS = exports.MANIFEST_FIELDS = [RESOLUTIONS, ...DEPENDENCY_TYPES]; - -const SUPPORTED_NODE_VERSIONS = exports.SUPPORTED_NODE_VERSIONS = '^4.8.0 || ^5.7.0 || ^6.2.2 || >=8.0.0'; - -const YARN_REGISTRY = exports.YARN_REGISTRY = 'https://registry.yarnpkg.com/'; -const NPM_REGISTRY_RE = exports.NPM_REGISTRY_RE = /https?:\/\/registry\.npmjs\.org/g; - -const YARN_DOCS = exports.YARN_DOCS = 'https://yarnpkg.com/en/docs/cli/'; -const YARN_INSTALLER_SH = exports.YARN_INSTALLER_SH = 'https://yarnpkg.com/install.sh'; -const YARN_INSTALLER_MSI = exports.YARN_INSTALLER_MSI = 'https://yarnpkg.com/latest.msi'; - -const SELF_UPDATE_VERSION_URL = exports.SELF_UPDATE_VERSION_URL = 'https://yarnpkg.com/latest-version'; - -// cache version, bump whenever we make backwards incompatible changes -const CACHE_VERSION = exports.CACHE_VERSION = 6; - -// lockfile version, bump whenever we make backwards incompatible changes -const LOCKFILE_VERSION = exports.LOCKFILE_VERSION = 1; - -// max amount of network requests to perform concurrently -const NETWORK_CONCURRENCY = exports.NETWORK_CONCURRENCY = 8; - -// HTTP timeout used when downloading packages -const NETWORK_TIMEOUT = exports.NETWORK_TIMEOUT = 30 * 1000; // in milliseconds - -// max amount of child processes to execute concurrently -const CHILD_CONCURRENCY = exports.CHILD_CONCURRENCY = 5; - -const REQUIRED_PACKAGE_KEYS = exports.REQUIRED_PACKAGE_KEYS = ['name', 'version', '_uid']; - -function getPreferredCacheDirectories() { - const preferredCacheDirectories = [getCacheDir()]; - - if (process.getuid) { - // $FlowFixMe: process.getuid exists, dammit - preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache-${process.getuid()}`)); - } - - preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache`)); - - return preferredCacheDirectories; -} - -const PREFERRED_MODULE_CACHE_DIRECTORIES = exports.PREFERRED_MODULE_CACHE_DIRECTORIES = getPreferredCacheDirectories(); -const CONFIG_DIRECTORY = exports.CONFIG_DIRECTORY = getConfigDir(); -const DATA_DIRECTORY = exports.DATA_DIRECTORY = getDataDir(); -const LINK_REGISTRY_DIRECTORY = exports.LINK_REGISTRY_DIRECTORY = path.join(DATA_DIRECTORY, 'link'); -const GLOBAL_MODULE_DIRECTORY = exports.GLOBAL_MODULE_DIRECTORY = path.join(DATA_DIRECTORY, 'global'); - -const NODE_BIN_PATH = exports.NODE_BIN_PATH = process.execPath; -const YARN_BIN_PATH = exports.YARN_BIN_PATH = getYarnBinPath(); - -// Webpack needs to be configured with node.__dirname/__filename = false -function getYarnBinPath() { - if (isWebpackBundle) { - return __filename; - } else { - return path.join(__dirname, '..', 'bin', 'yarn.js'); - } -} - -const NODE_MODULES_FOLDER = exports.NODE_MODULES_FOLDER = 'node_modules'; -const NODE_PACKAGE_JSON = exports.NODE_PACKAGE_JSON = 'package.json'; - -const PNP_FILENAME = exports.PNP_FILENAME = '.pnp.js'; - -const POSIX_GLOBAL_PREFIX = exports.POSIX_GLOBAL_PREFIX = `${process.env.DESTDIR || ''}/usr/local`; -const FALLBACK_GLOBAL_PREFIX = exports.FALLBACK_GLOBAL_PREFIX = path.join(userHome, '.yarn'); - -const META_FOLDER = exports.META_FOLDER = '.yarn-meta'; -const INTEGRITY_FILENAME = exports.INTEGRITY_FILENAME = '.yarn-integrity'; -const LOCKFILE_FILENAME = exports.LOCKFILE_FILENAME = 'yarn.lock'; -const METADATA_FILENAME = exports.METADATA_FILENAME = '.yarn-metadata.json'; -const TARBALL_FILENAME = exports.TARBALL_FILENAME = '.yarn-tarball.tgz'; -const CLEAN_FILENAME = exports.CLEAN_FILENAME = '.yarnclean'; - -const NPM_LOCK_FILENAME = exports.NPM_LOCK_FILENAME = 'package-lock.json'; -const NPM_SHRINKWRAP_FILENAME = exports.NPM_SHRINKWRAP_FILENAME = 'npm-shrinkwrap.json'; - -const DEFAULT_INDENT = exports.DEFAULT_INDENT = ' '; -const SINGLE_INSTANCE_PORT = exports.SINGLE_INSTANCE_PORT = 31997; -const SINGLE_INSTANCE_FILENAME = exports.SINGLE_INSTANCE_FILENAME = '.yarn-single-instance'; - -const ENV_PATH_KEY = exports.ENV_PATH_KEY = getPathKey(process.platform, process.env); - -function getPathKey(platform, env) { - let pathKey = 'PATH'; - - // windows calls its path "Path" usually, but this is not guaranteed. - if (platform === 'win32') { - pathKey = 'Path'; - - for (const key in env) { - if (key.toLowerCase() === 'path') { - pathKey = key; - } - } - } - - return pathKey; -} - -const VERSION_COLOR_SCHEME = exports.VERSION_COLOR_SCHEME = { - major: 'red', - premajor: 'red', - minor: 'yellow', - preminor: 'yellow', - patch: 'green', - prepatch: 'green', - prerelease: 'red', - unchanged: 'white', - unknown: 'red' -}; - -/***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - - -/** - * Use invariant() to assert state which your program assumes to be true. - * - * Provide sprintf-style format (only %s is supported) and arguments - * to provide information about what broke and what you were - * expecting. - * - * The invariant message will be stripped in production, but the invariant - * will remain to ensure logic does not differ in production. - */ - -var NODE_ENV = process.env.NODE_ENV; - -var invariant = function(condition, format, a, b, c, d, e, f) { - if (NODE_ENV !== 'production') { - if (format === undefined) { - throw new Error('invariant requires an error message argument'); - } - } - - if (!condition) { - var error; - if (format === undefined) { - error = new Error( - 'Minified exception occurred; use the non-minified dev environment ' + - 'for the full error message and additional helpful warnings.' - ); - } else { - var args = [a, b, c, d, e, f]; - var argIndex = 0; - error = new Error( - format.replace(/%s/g, function() { return args[argIndex++]; }) - ); - error.name = 'Invariant Violation'; - } - - error.framesToPop = 1; // we don't care about invariant's own frame - throw error; - } -}; - -module.exports = invariant; - - -/***/ }), -/* 10 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var YAMLException = __webpack_require__(54); - -var TYPE_CONSTRUCTOR_OPTIONS = [ - 'kind', - 'resolve', - 'construct', - 'instanceOf', - 'predicate', - 'represent', - 'defaultStyle', - 'styleAliases' -]; - -var YAML_NODE_KINDS = [ - 'scalar', - 'sequence', - 'mapping' -]; - -function compileStyleAliases(map) { - var result = {}; - - if (map !== null) { - Object.keys(map).forEach(function (style) { - map[style].forEach(function (alias) { - result[String(alias)] = style; - }); - }); - } - - return result; -} - -function Type(tag, options) { - options = options || {}; - - Object.keys(options).forEach(function (name) { - if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { - throw new YAMLException('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); - } - }); - - // TODO: Add tag format check. - this.tag = tag; - this.kind = options['kind'] || null; - this.resolve = options['resolve'] || function () { return true; }; - this.construct = options['construct'] || function (data) { return data; }; - this.instanceOf = options['instanceOf'] || null; - this.predicate = options['predicate'] || null; - this.represent = options['represent'] || null; - this.defaultStyle = options['defaultStyle'] || null; - this.styleAliases = compileStyleAliases(options['styleAliases'] || null); - - if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { - throw new YAMLException('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); - } -} - -module.exports = Type; - - -/***/ }), -/* 11 */ -/***/ (function(module, exports) { - -module.exports = require("crypto"); - -/***/ }), -/* 12 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Observable; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_canReportError__ = __webpack_require__(322); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_toSubscriber__ = __webpack_require__(932); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__internal_symbol_observable__ = __webpack_require__(117); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__util_pipe__ = __webpack_require__(324); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__config__ = __webpack_require__(185); -/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_internal_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ - - - - - -var Observable = /*@__PURE__*/ (function () { - function Observable(subscribe) { - this._isScalar = false; - if (subscribe) { - this._subscribe = subscribe; - } - } - Observable.prototype.lift = function (operator) { - var observable = new Observable(); - observable.source = this; - observable.operator = operator; - return observable; - }; - Observable.prototype.subscribe = function (observerOrNext, error, complete) { - var operator = this.operator; - var sink = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_toSubscriber__["a" /* toSubscriber */])(observerOrNext, error, complete); - if (operator) { - operator.call(sink, this.source); - } - else { - sink.add(this.source || (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? - this._subscribe(sink) : - this._trySubscribe(sink)); - } - if (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - if (sink.syncErrorThrowable) { - sink.syncErrorThrowable = false; - if (sink.syncErrorThrown) { - throw sink.syncErrorValue; - } - } - } - return sink; - }; - Observable.prototype._trySubscribe = function (sink) { - try { - return this._subscribe(sink); - } - catch (err) { - if (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - sink.syncErrorThrown = true; - sink.syncErrorValue = err; - } - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__util_canReportError__["a" /* canReportError */])(sink)) { - sink.error(err); - } - else { - console.warn(err); - } - } - }; - Observable.prototype.forEach = function (next, promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var subscription; - subscription = _this.subscribe(function (value) { - try { - next(value); - } - catch (err) { - reject(err); - if (subscription) { - subscription.unsubscribe(); - } - } - }, reject, resolve); - }); - }; - Observable.prototype._subscribe = function (subscriber) { - var source = this.source; - return source && source.subscribe(subscriber); - }; - Observable.prototype[__WEBPACK_IMPORTED_MODULE_2__internal_symbol_observable__["a" /* observable */]] = function () { - return this; - }; - Observable.prototype.pipe = function () { - var operations = []; - for (var _i = 0; _i < arguments.length; _i++) { - operations[_i] = arguments[_i]; - } - if (operations.length === 0) { - return this; - } - return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_pipe__["b" /* pipeFromArray */])(operations)(this); - }; - Observable.prototype.toPromise = function (promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var value; - _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); }); - }); - }; - Observable.create = function (subscribe) { - return new Observable(subscribe); - }; - return Observable; -}()); - -function getPromiseCtor(promiseCtor) { - if (!promiseCtor) { - promiseCtor = __WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].Promise || Promise; - } - if (!promiseCtor) { - throw new Error('no Promise impl found'); - } - return promiseCtor; -} -//# sourceMappingURL=Observable.js.map - - -/***/ }), -/* 13 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return OuterSubscriber; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Subscriber__ = __webpack_require__(7); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - - -var OuterSubscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](OuterSubscriber, _super); - function OuterSubscriber() { - return _super !== null && _super.apply(this, arguments) || this; - } - OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(innerValue); - }; - OuterSubscriber.prototype.notifyError = function (error, innerSub) { - this.destination.error(error); - }; - OuterSubscriber.prototype.notifyComplete = function (innerSub) { - this.destination.complete(); - }; - return OuterSubscriber; -}(__WEBPACK_IMPORTED_MODULE_1__Subscriber__["a" /* Subscriber */])); - -//# sourceMappingURL=OuterSubscriber.js.map - - -/***/ }), -/* 14 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (immutable) */ __webpack_exports__["a"] = subscribeToResult; -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__InnerSubscriber__ = __webpack_require__(84); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__subscribeTo__ = __webpack_require__(446); -/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo PURE_IMPORTS_END */ - - -function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) { - if (destination === void 0) { - destination = new __WEBPACK_IMPORTED_MODULE_0__InnerSubscriber__["a" /* InnerSubscriber */](outerSubscriber, outerValue, outerIndex); - } - if (destination.closed) { - return; - } - return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__subscribeTo__["a" /* subscribeTo */])(result)(destination); -} -//# sourceMappingURL=subscribeToResult.js.map - - -/***/ }), -/* 15 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* eslint-disable node/no-deprecated-api */ - - - -var buffer = __webpack_require__(64) -var Buffer = buffer.Buffer - -var safer = {} - -var key - -for (key in buffer) { - if (!buffer.hasOwnProperty(key)) continue - if (key === 'SlowBuffer' || key === 'Buffer') continue - safer[key] = buffer[key] -} - -var Safer = safer.Buffer = {} -for (key in Buffer) { - if (!Buffer.hasOwnProperty(key)) continue - if (key === 'allocUnsafe' || key === 'allocUnsafeSlow') continue - Safer[key] = Buffer[key] -} - -safer.Buffer.prototype = Buffer.prototype - -if (!Safer.from || Safer.from === Uint8Array.from) { - Safer.from = function (value, encodingOrOffset, length) { - if (typeof value === 'number') { - throw new TypeError('The "value" argument must not be of type number. Received type ' + typeof value) - } - if (value && typeof value.length === 'undefined') { - throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ' + typeof value) - } - return Buffer(value, encodingOrOffset, length) - } -} - -if (!Safer.alloc) { - Safer.alloc = function (size, fill, encoding) { - if (typeof size !== 'number') { - throw new TypeError('The "size" argument must be of type number. Received type ' + typeof size) - } - if (size < 0 || size >= 2 * (1 << 30)) { - throw new RangeError('The value "' + size + '" is invalid for option "size"') - } - var buf = Buffer(size) - if (!fill || fill.length === 0) { - buf.fill(0) - } else if (typeof encoding === 'string') { - buf.fill(fill, encoding) - } else { - buf.fill(fill) - } - return buf - } -} - -if (!safer.kStringMaxLength) { - try { - safer.kStringMaxLength = process.binding('buffer').kStringMaxLength - } catch (e) { - // we can't determine kStringMaxLength in environments where process.binding - // is unsupported, so let's not set it - } -} - -if (!safer.constants) { - safer.constants = { - MAX_LENGTH: safer.kMaxLength - } - if (safer.kStringMaxLength) { - safer.constants.MAX_STRING_LENGTH = safer.kStringMaxLength - } -} - -module.exports = safer - - -/***/ }), -/* 16 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright (c) 2012, Mark Cavage. All rights reserved. -// Copyright 2015 Joyent, Inc. - -var assert = __webpack_require__(28); -var Stream = __webpack_require__(23).Stream; -var util = __webpack_require__(3); - - -///--- Globals - -/* JSSTYLED */ -var UUID_REGEXP = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/; - - -///--- Internal - -function _capitalize(str) { - return (str.charAt(0).toUpperCase() + str.slice(1)); -} - -function _toss(name, expected, oper, arg, actual) { - throw new assert.AssertionError({ - message: util.format('%s (%s) is required', name, expected), - actual: (actual === undefined) ? typeof (arg) : actual(arg), - expected: expected, - operator: oper || '===', - stackStartFunction: _toss.caller - }); -} - -function _getClass(arg) { - return (Object.prototype.toString.call(arg).slice(8, -1)); -} - -function noop() { - // Why even bother with asserts? -} - - -///--- Exports - -var types = { - bool: { - check: function (arg) { return typeof (arg) === 'boolean'; } - }, - func: { - check: function (arg) { return typeof (arg) === 'function'; } - }, - string: { - check: function (arg) { return typeof (arg) === 'string'; } - }, - object: { - check: function (arg) { - return typeof (arg) === 'object' && arg !== null; - } - }, - number: { - check: function (arg) { - return typeof (arg) === 'number' && !isNaN(arg); - } - }, - finite: { - check: function (arg) { - return typeof (arg) === 'number' && !isNaN(arg) && isFinite(arg); - } - }, - buffer: { - check: function (arg) { return Buffer.isBuffer(arg); }, - operator: 'Buffer.isBuffer' - }, - array: { - check: function (arg) { return Array.isArray(arg); }, - operator: 'Array.isArray' - }, - stream: { - check: function (arg) { return arg instanceof Stream; }, - operator: 'instanceof', - actual: _getClass - }, - date: { - check: function (arg) { return arg instanceof Date; }, - operator: 'instanceof', - actual: _getClass - }, - regexp: { - check: function (arg) { return arg instanceof RegExp; }, - operator: 'instanceof', - actual: _getClass - }, - uuid: { - check: function (arg) { - return typeof (arg) === 'string' && UUID_REGEXP.test(arg); - }, - operator: 'isUUID' - } -}; - -function _setExports(ndebug) { - var keys = Object.keys(types); - var out; - - /* re-export standard assert */ - if (process.env.NODE_NDEBUG) { - out = noop; - } else { - out = function (arg, msg) { - if (!arg) { - _toss(msg, 'true', arg); - } - }; - } - - /* standard checks */ - keys.forEach(function (k) { - if (ndebug) { - out[k] = noop; - return; - } - var type = types[k]; - out[k] = function (arg, msg) { - if (!type.check(arg)) { - _toss(msg, k, type.operator, arg, type.actual); - } - }; - }); - - /* optional checks */ - keys.forEach(function (k) { - var name = 'optional' + _capitalize(k); - if (ndebug) { - out[name] = noop; - return; - } - var type = types[k]; - out[name] = function (arg, msg) { - if (arg === undefined || arg === null) { - return; - } - if (!type.check(arg)) { - _toss(msg, k, type.operator, arg, type.actual); - } - }; - }); - - /* arrayOf checks */ - keys.forEach(function (k) { - var name = 'arrayOf' + _capitalize(k); - if (ndebug) { - out[name] = noop; - return; - } - var type = types[k]; - var expected = '[' + k + ']'; - out[name] = function (arg, msg) { - if (!Array.isArray(arg)) { - _toss(msg, expected, type.operator, arg, type.actual); - } - var i; - for (i = 0; i < arg.length; i++) { - if (!type.check(arg[i])) { - _toss(msg, expected, type.operator, arg, type.actual); - } - } - }; - }); - - /* optionalArrayOf checks */ - keys.forEach(function (k) { - var name = 'optionalArrayOf' + _capitalize(k); - if (ndebug) { - out[name] = noop; - return; - } - var type = types[k]; - var expected = '[' + k + ']'; - out[name] = function (arg, msg) { - if (arg === undefined || arg === null) { - return; - } - if (!Array.isArray(arg)) { - _toss(msg, expected, type.operator, arg, type.actual); - } - var i; - for (i = 0; i < arg.length; i++) { - if (!type.check(arg[i])) { - _toss(msg, expected, type.operator, arg, type.actual); - } - } - }; - }); - - /* re-export built-in assertions */ - Object.keys(assert).forEach(function (k) { - if (k === 'AssertionError') { - out[k] = assert[k]; - return; - } - if (ndebug) { - out[k] = noop; - return; - } - out[k] = assert[k]; - }); - - /* export ourselves (for unit tests _only_) */ - out._setExports = _setExports; - - return out; -} - -module.exports = _setExports(process.env.NODE_NDEBUG); - - -/***/ }), -/* 17 */ -/***/ (function(module, exports) { - -// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 -var global = module.exports = typeof window != 'undefined' && window.Math == Math - ? window : typeof self != 'undefined' && self.Math == Math ? self - // eslint-disable-next-line no-new-func - : Function('return this')(); -if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef - - -/***/ }), -/* 18 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.sortAlpha = sortAlpha; -exports.sortOptionsByFlags = sortOptionsByFlags; -exports.entries = entries; -exports.removePrefix = removePrefix; -exports.removeSuffix = removeSuffix; -exports.addSuffix = addSuffix; -exports.hyphenate = hyphenate; -exports.camelCase = camelCase; -exports.compareSortedArrays = compareSortedArrays; -exports.sleep = sleep; -const _camelCase = __webpack_require__(230); - -function sortAlpha(a, b) { - // sort alphabetically in a deterministic way - const shortLen = Math.min(a.length, b.length); - for (let i = 0; i < shortLen; i++) { - const aChar = a.charCodeAt(i); - const bChar = b.charCodeAt(i); - if (aChar !== bChar) { - return aChar - bChar; - } - } - return a.length - b.length; -} - -function sortOptionsByFlags(a, b) { - const aOpt = a.flags.replace(/-/g, ''); - const bOpt = b.flags.replace(/-/g, ''); - return sortAlpha(aOpt, bOpt); -} - -function entries(obj) { - const entries = []; - if (obj) { - for (const key in obj) { - entries.push([key, obj[key]]); - } - } - return entries; -} - -function removePrefix(pattern, prefix) { - if (pattern.startsWith(prefix)) { - pattern = pattern.slice(prefix.length); - } - - return pattern; -} - -function removeSuffix(pattern, suffix) { - if (pattern.endsWith(suffix)) { - return pattern.slice(0, -suffix.length); - } - - return pattern; -} - -function addSuffix(pattern, suffix) { - if (!pattern.endsWith(suffix)) { - return pattern + suffix; - } - - return pattern; -} - -function hyphenate(str) { - return str.replace(/[A-Z]/g, match => { - return '-' + match.charAt(0).toLowerCase(); - }); -} - -function camelCase(str) { - if (/[A-Z]/.test(str)) { - return null; - } else { - return _camelCase(str); - } -} - -function compareSortedArrays(array1, array2) { - if (array1.length !== array2.length) { - return false; - } - for (let i = 0, len = array1.length; i < len; i++) { - if (array1[i] !== array2[i]) { - return false; - } - } - return true; -} - -function sleep(ms) { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} - -/***/ }), -/* 19 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.stringify = exports.parse = undefined; - -var _asyncToGenerator2; - -function _load_asyncToGenerator() { - return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); -} - -var _parse; - -function _load_parse() { - return _parse = __webpack_require__(105); -} - -Object.defineProperty(exports, 'parse', { - enumerable: true, - get: function get() { - return _interopRequireDefault(_parse || _load_parse()).default; - } -}); - -var _stringify; - -function _load_stringify() { - return _stringify = __webpack_require__(199); -} - -Object.defineProperty(exports, 'stringify', { - enumerable: true, - get: function get() { - return _interopRequireDefault(_stringify || _load_stringify()).default; - } -}); -exports.implodeEntry = implodeEntry; -exports.explodeEntry = explodeEntry; - -var _misc; - -function _load_misc() { - return _misc = __webpack_require__(18); -} - -var _normalizePattern; - -function _load_normalizePattern() { - return _normalizePattern = __webpack_require__(37); -} - -var _parse2; - -function _load_parse2() { - return _parse2 = _interopRequireDefault(__webpack_require__(105)); -} - -var _constants; - -function _load_constants() { - return _constants = __webpack_require__(8); -} - -var _fs; - -function _load_fs() { - return _fs = _interopRequireWildcard(__webpack_require__(4)); -} - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const invariant = __webpack_require__(9); - -const path = __webpack_require__(0); -const ssri = __webpack_require__(65); - -function getName(pattern) { - return (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern).name; -} - -function blankObjectUndefined(obj) { - return obj && Object.keys(obj).length ? obj : undefined; -} - -function keyForRemote(remote) { - return remote.resolved || (remote.reference && remote.hash ? `${remote.reference}#${remote.hash}` : null); -} - -function serializeIntegrity(integrity) { - // We need this because `Integrity.toString()` does not use sorting to ensure a stable string output - // See https://git.io/vx2Hy - return integrity.toString().split(' ').sort().join(' '); -} - -function implodeEntry(pattern, obj) { - const inferredName = getName(pattern); - const integrity = obj.integrity ? serializeIntegrity(obj.integrity) : ''; - const imploded = { - name: inferredName === obj.name ? undefined : obj.name, - version: obj.version, - uid: obj.uid === obj.version ? undefined : obj.uid, - resolved: obj.resolved, - registry: obj.registry === 'npm' ? undefined : obj.registry, - dependencies: blankObjectUndefined(obj.dependencies), - optionalDependencies: blankObjectUndefined(obj.optionalDependencies), - permissions: blankObjectUndefined(obj.permissions), - prebuiltVariants: blankObjectUndefined(obj.prebuiltVariants) - }; - if (integrity) { - imploded.integrity = integrity; - } - return imploded; -} - -function explodeEntry(pattern, obj) { - obj.optionalDependencies = obj.optionalDependencies || {}; - obj.dependencies = obj.dependencies || {}; - obj.uid = obj.uid || obj.version; - obj.permissions = obj.permissions || {}; - obj.registry = obj.registry || 'npm'; - obj.name = obj.name || getName(pattern); - const integrity = obj.integrity; - if (integrity && integrity.isIntegrity) { - obj.integrity = ssri.parse(integrity); - } - return obj; -} - -class Lockfile { - constructor({ cache, source, parseResultType } = {}) { - this.source = source || ''; - this.cache = cache; - this.parseResultType = parseResultType; - } - - // source string if the `cache` was parsed - - - // if true, we're parsing an old yarn file and need to update integrity fields - hasEntriesExistWithoutIntegrity() { - if (!this.cache) { - return false; - } - - for (const key in this.cache) { - // $FlowFixMe - `this.cache` is clearly defined at this point - if (!/^.*@(file:|http)/.test(key) && this.cache[key] && !this.cache[key].integrity) { - return true; - } - } - - return false; - } - - static fromDirectory(dir, reporter) { - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // read the manifest in this directory - const lockfileLoc = path.join(dir, (_constants || _load_constants()).LOCKFILE_FILENAME); - - let lockfile; - let rawLockfile = ''; - let parseResult; - - if (yield (_fs || _load_fs()).exists(lockfileLoc)) { - rawLockfile = yield (_fs || _load_fs()).readFile(lockfileLoc); - parseResult = (0, (_parse2 || _load_parse2()).default)(rawLockfile, lockfileLoc); - - if (reporter) { - if (parseResult.type === 'merge') { - reporter.info(reporter.lang('lockfileMerged')); - } else if (parseResult.type === 'conflict') { - reporter.warn(reporter.lang('lockfileConflict')); - } - } - - lockfile = parseResult.object; - } else if (reporter) { - reporter.info(reporter.lang('noLockfileFound')); - } - - if (lockfile && lockfile.__metadata) { - const lockfilev2 = lockfile; - lockfile = {}; - } - - return new Lockfile({ cache: lockfile, source: rawLockfile, parseResultType: parseResult && parseResult.type }); - })(); - } - - getLocked(pattern) { - const cache = this.cache; - if (!cache) { - return undefined; - } - - const shrunk = pattern in cache && cache[pattern]; - - if (typeof shrunk === 'string') { - return this.getLocked(shrunk); - } else if (shrunk) { - explodeEntry(pattern, shrunk); - return shrunk; - } - - return undefined; - } - - removePattern(pattern) { - const cache = this.cache; - if (!cache) { - return; - } - delete cache[pattern]; - } - - getLockfile(patterns) { - const lockfile = {}; - const seen = new Map(); - - // order by name so that lockfile manifest is assigned to the first dependency with this manifest - // the others that have the same remoteKey will just refer to the first - // ordering allows for consistency in lockfile when it is serialized - const sortedPatternsKeys = Object.keys(patterns).sort((_misc || _load_misc()).sortAlpha); - - for (var _iterator = sortedPatternsKeys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - const pattern = _ref; - - const pkg = patterns[pattern]; - const remote = pkg._remote, - ref = pkg._reference; - - invariant(ref, 'Package is missing a reference'); - invariant(remote, 'Package is missing a remote'); - - const remoteKey = keyForRemote(remote); - const seenPattern = remoteKey && seen.get(remoteKey); - if (seenPattern) { - // no point in duplicating it - lockfile[pattern] = seenPattern; - - // if we're relying on our name being inferred and two of the patterns have - // different inferred names then we need to set it - if (!seenPattern.name && getName(pattern) !== pkg.name) { - seenPattern.name = pkg.name; - } - continue; - } - const obj = implodeEntry(pattern, { - name: pkg.name, - version: pkg.version, - uid: pkg._uid, - resolved: remote.resolved, - integrity: remote.integrity, - registry: remote.registry, - dependencies: pkg.dependencies, - peerDependencies: pkg.peerDependencies, - optionalDependencies: pkg.optionalDependencies, - permissions: ref.permissions, - prebuiltVariants: pkg.prebuiltVariants - }); - - lockfile[pattern] = obj; - - if (remoteKey) { - seen.set(remoteKey, obj); - } - } - - return lockfile; - } -} -exports.default = Lockfile; - -/***/ }), -/* 20 */ -/***/ (function(module, exports, __webpack_require__) { - -var store = __webpack_require__(133)('wks'); -var uid = __webpack_require__(137); -var Symbol = __webpack_require__(17).Symbol; -var USE_SYMBOL = typeof Symbol == 'function'; - -var $exports = module.exports = function (name) { - return store[name] || (store[name] = - USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name)); -}; - -$exports.store = store; - - -/***/ }), -/* 21 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -exports.__esModule = true; - -var _assign = __webpack_require__(591); - -var _assign2 = _interopRequireDefault(_assign); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = _assign2.default || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; -}; - -/***/ }), -/* 22 */ -/***/ (function(module, exports) { - -exports = module.exports = SemVer; - -// The debug function is excluded entirely from the minified version. -/* nomin */ var debug; -/* nomin */ if (typeof process === 'object' && - /* nomin */ process.env && - /* nomin */ process.env.NODE_DEBUG && - /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) - /* nomin */ debug = function() { - /* nomin */ var args = Array.prototype.slice.call(arguments, 0); - /* nomin */ args.unshift('SEMVER'); - /* nomin */ console.log.apply(console, args); - /* nomin */ }; -/* nomin */ else - /* nomin */ debug = function() {}; - -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -exports.SEMVER_SPEC_VERSION = '2.0.0'; - -var MAX_LENGTH = 256; -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; - -// Max safe segment length for coercion. -var MAX_SAFE_COMPONENT_LENGTH = 16; - -// The actual regexps go on exports.re -var re = exports.re = []; -var src = exports.src = []; -var R = 0; - -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. - -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. - -var NUMERICIDENTIFIER = R++; -src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; -var NUMERICIDENTIFIERLOOSE = R++; -src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; - - -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. - -var NONNUMERICIDENTIFIER = R++; -src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; - - -// ## Main Version -// Three dot-separated numeric identifiers. - -var MAINVERSION = R++; -src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')'; - -var MAINVERSIONLOOSE = R++; -src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; - -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. - -var PRERELEASEIDENTIFIER = R++; -src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; - -var PRERELEASEIDENTIFIERLOOSE = R++; -src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; - - -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. - -var PRERELEASE = R++; -src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + - '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; - -var PRERELEASELOOSE = R++; -src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + - '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; - -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. - -var BUILDIDENTIFIER = R++; -src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; - -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. - -var BUILD = R++; -src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + - '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; - - -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. - -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. - -var FULL = R++; -var FULLPLAIN = 'v?' + src[MAINVERSION] + - src[PRERELEASE] + '?' + - src[BUILD] + '?'; - -src[FULL] = '^' + FULLPLAIN + '$'; - -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + - src[PRERELEASELOOSE] + '?' + - src[BUILD] + '?'; - -var LOOSE = R++; -src[LOOSE] = '^' + LOOSEPLAIN + '$'; - -var GTLT = R++; -src[GTLT] = '((?:<|>)?=?)'; - -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -var XRANGEIDENTIFIERLOOSE = R++; -src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; -var XRANGEIDENTIFIER = R++; -src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; - -var XRANGEPLAIN = R++; -src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:' + src[PRERELEASE] + ')?' + - src[BUILD] + '?' + - ')?)?'; - -var XRANGEPLAINLOOSE = R++; -src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:' + src[PRERELEASELOOSE] + ')?' + - src[BUILD] + '?' + - ')?)?'; - -var XRANGE = R++; -src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; -var XRANGELOOSE = R++; -src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; - -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -var COERCE = R++; -src[COERCE] = '(?:^|[^\\d])' + - '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:$|[^\\d])'; - -// Tilde ranges. -// Meaning is "reasonably at or greater than" -var LONETILDE = R++; -src[LONETILDE] = '(?:~>?)'; - -var TILDETRIM = R++; -src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; -re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); -var tildeTrimReplace = '$1~'; - -var TILDE = R++; -src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; -var TILDELOOSE = R++; -src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; - -// Caret ranges. -// Meaning is "at least and backwards compatible with" -var LONECARET = R++; -src[LONECARET] = '(?:\\^)'; - -var CARETTRIM = R++; -src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; -re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); -var caretTrimReplace = '$1^'; - -var CARET = R++; -src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; -var CARETLOOSE = R++; -src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; - -// A simple gt/lt/eq thing, or just "" to indicate "any version" -var COMPARATORLOOSE = R++; -src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; -var COMPARATOR = R++; -src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; - - -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -var COMPARATORTRIM = R++; -src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + - '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; - -// this one has to use the /g flag -re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); -var comparatorTrimReplace = '$1$2$3'; - - -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -var HYPHENRANGE = R++; -src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAIN] + ')' + - '\\s*$'; - -var HYPHENRANGELOOSE = R++; -src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s*$'; - -// Star ranges basically just allow anything at all. -var STAR = R++; -src[STAR] = '(<|>)?=?\\s*\\*'; - -// Compile to actual regexp objects. -// All are flag-free, unless they were created above with a flag. -for (var i = 0; i < R; i++) { - debug(i, src[i]); - if (!re[i]) - re[i] = new RegExp(src[i]); -} - -exports.parse = parse; -function parse(version, loose) { - if (version instanceof SemVer) - return version; - - if (typeof version !== 'string') - return null; - - if (version.length > MAX_LENGTH) - return null; - - var r = loose ? re[LOOSE] : re[FULL]; - if (!r.test(version)) - return null; - - try { - return new SemVer(version, loose); - } catch (er) { - return null; - } -} - -exports.valid = valid; -function valid(version, loose) { - var v = parse(version, loose); - return v ? v.version : null; -} - - -exports.clean = clean; -function clean(version, loose) { - var s = parse(version.trim().replace(/^[=v]+/, ''), loose); - return s ? s.version : null; -} - -exports.SemVer = SemVer; - -function SemVer(version, loose) { - if (version instanceof SemVer) { - if (version.loose === loose) - return version; - else - version = version.version; - } else if (typeof version !== 'string') { - throw new TypeError('Invalid Version: ' + version); - } - - if (version.length > MAX_LENGTH) - throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') - - if (!(this instanceof SemVer)) - return new SemVer(version, loose); - - debug('SemVer', version, loose); - this.loose = loose; - var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); - - if (!m) - throw new TypeError('Invalid Version: ' + version); - - this.raw = version; - - // these are actually numbers - this.major = +m[1]; - this.minor = +m[2]; - this.patch = +m[3]; - - if (this.major > MAX_SAFE_INTEGER || this.major < 0) - throw new TypeError('Invalid major version') - - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) - throw new TypeError('Invalid minor version') - - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) - throw new TypeError('Invalid patch version') - - // numberify any prerelease numeric ids - if (!m[4]) - this.prerelease = []; - else - this.prerelease = m[4].split('.').map(function(id) { - if (/^[0-9]+$/.test(id)) { - var num = +id; - if (num >= 0 && num < MAX_SAFE_INTEGER) - return num; - } - return id; - }); - - this.build = m[5] ? m[5].split('.') : []; - this.format(); -} - -SemVer.prototype.format = function() { - this.version = this.major + '.' + this.minor + '.' + this.patch; - if (this.prerelease.length) - this.version += '-' + this.prerelease.join('.'); - return this.version; -}; - -SemVer.prototype.toString = function() { - return this.version; -}; - -SemVer.prototype.compare = function(other) { - debug('SemVer.compare', this.version, this.loose, other); - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); - - return this.compareMain(other) || this.comparePre(other); -}; - -SemVer.prototype.compareMain = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); - - return compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch); -}; - -SemVer.prototype.comparePre = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); - - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) - return -1; - else if (!this.prerelease.length && other.prerelease.length) - return 1; - else if (!this.prerelease.length && !other.prerelease.length) - return 0; - - var i = 0; - do { - var a = this.prerelease[i]; - var b = other.prerelease[i]; - debug('prerelease compare', i, a, b); - if (a === undefined && b === undefined) - return 0; - else if (b === undefined) - return 1; - else if (a === undefined) - return -1; - else if (a === b) - continue; - else - return compareIdentifiers(a, b); - } while (++i); -}; - -// preminor will bump the version up to the next minor release, and immediately -// down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function(release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0; - this.patch = 0; - this.minor = 0; - this.major++; - this.inc('pre', identifier); - break; - case 'preminor': - this.prerelease.length = 0; - this.patch = 0; - this.minor++; - this.inc('pre', identifier); - break; - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0; - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) - this.major++; - this.minor = 0; - this.patch = 0; - this.prerelease = []; - break; - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) - this.minor++; - this.patch = 0; - this.prerelease = []; - break; - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) - this.patch++; - this.prerelease = []; - break; - // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) - this.prerelease = [0]; - else { - var i = this.prerelease.length; - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++; - i = -2; - } - } - if (i === -1) // didn't increment anything - this.prerelease.push(0); - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) - this.prerelease = [identifier, 0]; - } else - this.prerelease = [identifier, 0]; - } - break; - - default: - throw new Error('invalid increment argument: ' + release); - } - this.format(); - this.raw = this.version; - return this; -}; - -exports.inc = inc; -function inc(version, release, loose, identifier) { - if (typeof(loose) === 'string') { - identifier = loose; - loose = undefined; - } - - try { - return new SemVer(version, loose).inc(release, identifier).version; - } catch (er) { - return null; - } -} - -exports.diff = diff; -function diff(version1, version2) { - if (eq(version1, version2)) { - return null; - } else { - var v1 = parse(version1); - var v2 = parse(version2); - if (v1.prerelease.length || v2.prerelease.length) { - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return 'pre'+key; - } - } - } - return 'prerelease'; - } - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return key; - } - } - } - } -} - -exports.compareIdentifiers = compareIdentifiers; - -var numeric = /^[0-9]+$/; -function compareIdentifiers(a, b) { - var anum = numeric.test(a); - var bnum = numeric.test(b); - - if (anum && bnum) { - a = +a; - b = +b; - } - - return (anum && !bnum) ? -1 : - (bnum && !anum) ? 1 : - a < b ? -1 : - a > b ? 1 : - 0; -} - -exports.rcompareIdentifiers = rcompareIdentifiers; -function rcompareIdentifiers(a, b) { - return compareIdentifiers(b, a); -} - -exports.major = major; -function major(a, loose) { - return new SemVer(a, loose).major; -} - -exports.minor = minor; -function minor(a, loose) { - return new SemVer(a, loose).minor; -} - -exports.patch = patch; -function patch(a, loose) { - return new SemVer(a, loose).patch; -} - -exports.compare = compare; -function compare(a, b, loose) { - return new SemVer(a, loose).compare(new SemVer(b, loose)); -} - -exports.compareLoose = compareLoose; -function compareLoose(a, b) { - return compare(a, b, true); -} - -exports.rcompare = rcompare; -function rcompare(a, b, loose) { - return compare(b, a, loose); -} - -exports.sort = sort; -function sort(list, loose) { - return list.sort(function(a, b) { - return exports.compare(a, b, loose); - }); -} - -exports.rsort = rsort; -function rsort(list, loose) { - return list.sort(function(a, b) { - return exports.rcompare(a, b, loose); - }); -} - -exports.gt = gt; -function gt(a, b, loose) { - return compare(a, b, loose) > 0; -} - -exports.lt = lt; -function lt(a, b, loose) { - return compare(a, b, loose) < 0; -} - -exports.eq = eq; -function eq(a, b, loose) { - return compare(a, b, loose) === 0; -} - -exports.neq = neq; -function neq(a, b, loose) { - return compare(a, b, loose) !== 0; -} - -exports.gte = gte; -function gte(a, b, loose) { - return compare(a, b, loose) >= 0; -} - -exports.lte = lte; -function lte(a, b, loose) { - return compare(a, b, loose) <= 0; -} - -exports.cmp = cmp; -function cmp(a, op, b, loose) { - var ret; - switch (op) { - case '===': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a === b; - break; - case '!==': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a !== b; - break; - case '': case '=': case '==': ret = eq(a, b, loose); break; - case '!=': ret = neq(a, b, loose); break; - case '>': ret = gt(a, b, loose); break; - case '>=': ret = gte(a, b, loose); break; - case '<': ret = lt(a, b, loose); break; - case '<=': ret = lte(a, b, loose); break; - default: throw new TypeError('Invalid operator: ' + op); - } - return ret; -} - -exports.Comparator = Comparator; -function Comparator(comp, loose) { - if (comp instanceof Comparator) { - if (comp.loose === loose) - return comp; - else - comp = comp.value; - } - - if (!(this instanceof Comparator)) - return new Comparator(comp, loose); - - debug('comparator', comp, loose); - this.loose = loose; - this.parse(comp); - - if (this.semver === ANY) - this.value = ''; - else - this.value = this.operator + this.semver.version; - - debug('comp', this); -} - -var ANY = {}; -Comparator.prototype.parse = function(comp) { - var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var m = comp.match(r); - - if (!m) - throw new TypeError('Invalid comparator: ' + comp); - - this.operator = m[1]; - if (this.operator === '=') - this.operator = ''; - - // if it literally is just '>' or '' then allow anything. - if (!m[2]) - this.semver = ANY; - else - this.semver = new SemVer(m[2], this.loose); -}; - -Comparator.prototype.toString = function() { - return this.value; -}; - -Comparator.prototype.test = function(version) { - debug('Comparator.test', version, this.loose); - - if (this.semver === ANY) - return true; - - if (typeof version === 'string') - version = new SemVer(version, this.loose); - - return cmp(version, this.operator, this.semver, this.loose); -}; - -Comparator.prototype.intersects = function(comp, loose) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required'); - } - - var rangeTmp; - - if (this.operator === '') { - rangeTmp = new Range(comp.value, loose); - return satisfies(this.value, rangeTmp, loose); - } else if (comp.operator === '') { - rangeTmp = new Range(this.value, loose); - return satisfies(comp.semver, rangeTmp, loose); - } - - var sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>'); - var sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<'); - var sameSemVer = this.semver.version === comp.semver.version; - var differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<='); - var oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, loose) && - ((this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<')); - var oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, loose) && - ((this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>')); - - return sameDirectionIncreasing || sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; -}; - - -exports.Range = Range; -function Range(range, loose) { - if (range instanceof Range) { - if (range.loose === loose) { - return range; - } else { - return new Range(range.raw, loose); - } - } - - if (range instanceof Comparator) { - return new Range(range.value, loose); - } - - if (!(this instanceof Range)) - return new Range(range, loose); - - this.loose = loose; - - // First, split based on boolean or || - this.raw = range; - this.set = range.split(/\s*\|\|\s*/).map(function(range) { - return this.parseRange(range.trim()); - }, this).filter(function(c) { - // throw out any that are not relevant for whatever reason - return c.length; - }); - - if (!this.set.length) { - throw new TypeError('Invalid SemVer Range: ' + range); - } - - this.format(); -} - -Range.prototype.format = function() { - this.range = this.set.map(function(comps) { - return comps.join(' ').trim(); - }).join('||').trim(); - return this.range; -}; - -Range.prototype.toString = function() { - return this.range; -}; - -Range.prototype.parseRange = function(range) { - var loose = this.loose; - range = range.trim(); - debug('range', range, loose); - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; - range = range.replace(hr, hyphenReplace); - debug('hyphen replace', range); - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); - debug('comparator trim', range, re[COMPARATORTRIM]); - - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace); - - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace); - - // normalize spaces - range = range.split(/\s+/).join(' '); - - // At this point, the range is completely trimmed and - // ready to be split into comparators. - - var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var set = range.split(' ').map(function(comp) { - return parseComparator(comp, loose); - }).join(' ').split(/\s+/); - if (this.loose) { - // in loose mode, throw out any that are not valid comparators - set = set.filter(function(comp) { - return !!comp.match(compRe); - }); - } - set = set.map(function(comp) { - return new Comparator(comp, loose); - }); - - return set; -}; - -Range.prototype.intersects = function(range, loose) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required'); - } - - return this.set.some(function(thisComparators) { - return thisComparators.every(function(thisComparator) { - return range.set.some(function(rangeComparators) { - return rangeComparators.every(function(rangeComparator) { - return thisComparator.intersects(rangeComparator, loose); - }); - }); - }); - }); -}; - -// Mostly just for testing and legacy API reasons -exports.toComparators = toComparators; -function toComparators(range, loose) { - return new Range(range, loose).set.map(function(comp) { - return comp.map(function(c) { - return c.value; - }).join(' ').trim().split(' '); - }); -} - -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -function parseComparator(comp, loose) { - debug('comp', comp); - comp = replaceCarets(comp, loose); - debug('caret', comp); - comp = replaceTildes(comp, loose); - debug('tildes', comp); - comp = replaceXRanges(comp, loose); - debug('xrange', comp); - comp = replaceStars(comp, loose); - debug('stars', comp); - return comp; -} - -function isX(id) { - return !id || id.toLowerCase() === 'x' || id === '*'; -} - -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 -function replaceTildes(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceTilde(comp, loose); - }).join(' '); -} - -function replaceTilde(comp, loose) { - var r = loose ? re[TILDELOOSE] : re[TILDE]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('tilde', comp, _, M, m, p, pr); - var ret; - - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) - // ~1.2 == >=1.2.0 <1.3.0 - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else if (pr) { - debug('replaceTilde pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - - debug('tilde return', ret); - return ret; - }); -} - -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2.0 --> >=1.2.0 <2.0.0 -function replaceCarets(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceCaret(comp, loose); - }).join(' '); -} - -function replaceCaret(comp, loose) { - debug('caret', comp, loose); - var r = loose ? re[CARETLOOSE] : re[CARET]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('caret', comp, _, M, m, p, pr); - var ret; - - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) { - if (M === '0') - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else - ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; - } else if (pr) { - debug('replaceCaret pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + (+M + 1) + '.0.0'; - } else { - debug('no pr'); - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + (+M + 1) + '.0.0'; - } - - debug('caret return', ret); - return ret; - }); -} - -function replaceXRanges(comp, loose) { - debug('replaceXRanges', comp, loose); - return comp.split(/\s+/).map(function(comp) { - return replaceXRange(comp, loose); - }).join(' '); -} - -function replaceXRange(comp, loose) { - comp = comp.trim(); - var r = loose ? re[XRANGELOOSE] : re[XRANGE]; - return comp.replace(r, function(ret, gtlt, M, m, p, pr) { - debug('xRange', comp, ret, gtlt, M, m, p, pr); - var xM = isX(M); - var xm = xM || isX(m); - var xp = xm || isX(p); - var anyX = xp; - - if (gtlt === '=' && anyX) - gtlt = ''; - - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0'; - } else { - // nothing is forbidden - ret = '*'; - } - } else if (gtlt && anyX) { - // replace X with 0 - if (xm) - m = 0; - if (xp) - p = 0; - - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - gtlt = '>='; - if (xm) { - M = +M + 1; - m = 0; - p = 0; - } else if (xp) { - m = +m + 1; - p = 0; - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<'; - if (xm) - M = +M + 1; - else - m = +m + 1; - } - - ret = gtlt + M + '.' + m + '.' + p; - } else if (xm) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - } else if (xp) { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - } - - debug('xRange return', ret); - - return ret; - }); -} - -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -function replaceStars(comp, loose) { - debug('replaceStars', comp, loose); - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], ''); -} - -// This function is passed to string.replace(re[HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) { - - if (isX(fM)) - from = ''; - else if (isX(fm)) - from = '>=' + fM + '.0.0'; - else if (isX(fp)) - from = '>=' + fM + '.' + fm + '.0'; - else - from = '>=' + from; - - if (isX(tM)) - to = ''; - else if (isX(tm)) - to = '<' + (+tM + 1) + '.0.0'; - else if (isX(tp)) - to = '<' + tM + '.' + (+tm + 1) + '.0'; - else if (tpr) - to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; - else - to = '<=' + to; - - return (from + ' ' + to).trim(); -} - - -// if ANY of the sets match ALL of its comparators, then pass -Range.prototype.test = function(version) { - if (!version) - return false; - - if (typeof version === 'string') - version = new SemVer(version, this.loose); - - for (var i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version)) - return true; - } - return false; -}; - -function testSet(set, version) { - for (var i = 0; i < set.length; i++) { - if (!set[i].test(version)) - return false; - } - - if (version.prerelease.length) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (var i = 0; i < set.length; i++) { - debug(set[i].semver); - if (set[i].semver === ANY) - continue; - - if (set[i].semver.prerelease.length > 0) { - var allowed = set[i].semver; - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) - return true; - } - } - - // Version has a -pre, but it's not one of the ones we like. - return false; - } - - return true; -} - -exports.satisfies = satisfies; -function satisfies(version, range, loose) { - try { - range = new Range(range, loose); - } catch (er) { - return false; - } - return range.test(version); -} - -exports.maxSatisfying = maxSatisfying; -function maxSatisfying(versions, range, loose) { - var max = null; - var maxSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) - max = v; - maxSV = new SemVer(max, loose); - } - } - }) - return max; -} - -exports.minSatisfying = minSatisfying; -function minSatisfying(versions, range, loose) { - var min = null; - var minSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!min || minSV.compare(v) === 1) { // compare(min, v, true) - min = v; - minSV = new SemVer(min, loose); - } - } - }) - return min; -} - -exports.validRange = validRange; -function validRange(range, loose) { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, loose).range || '*'; - } catch (er) { - return null; - } -} - -// Determine if version is less than all the versions possible in the range -exports.ltr = ltr; -function ltr(version, range, loose) { - return outside(version, range, '<', loose); -} - -// Determine if version is greater than all the versions possible in the range. -exports.gtr = gtr; -function gtr(version, range, loose) { - return outside(version, range, '>', loose); -} - -exports.outside = outside; -function outside(version, range, hilo, loose) { - version = new SemVer(version, loose); - range = new Range(range, loose); - - var gtfn, ltefn, ltfn, comp, ecomp; - switch (hilo) { - case '>': - gtfn = gt; - ltefn = lte; - ltfn = lt; - comp = '>'; - ecomp = '>='; - break; - case '<': - gtfn = lt; - ltefn = gte; - ltfn = gt; - comp = '<'; - ecomp = '<='; - break; - default: - throw new TypeError('Must provide a hilo val of "<" or ">"'); - } - - // If it satisifes the range it is not outside - if (satisfies(version, range, loose)) { - return false; - } - - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. - - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i]; - - var high = null; - var low = null; - - comparators.forEach(function(comparator) { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator; - low = low || comparator; - if (gtfn(comparator.semver, high.semver, loose)) { - high = comparator; - } else if (ltfn(comparator.semver, low.semver, loose)) { - low = comparator; - } - }); - - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false; - } - - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false; - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false; - } - } - return true; -} - -exports.prerelease = prerelease; -function prerelease(version, loose) { - var parsed = parse(version, loose); - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; -} - -exports.intersects = intersects; -function intersects(r1, r2, loose) { - r1 = new Range(r1, loose) - r2 = new Range(r2, loose) - return r1.intersects(r2) -} - -exports.coerce = coerce; -function coerce(version) { - if (version instanceof SemVer) - return version; - - if (typeof version !== 'string') - return null; - - var match = version.match(re[COERCE]); - - if (match == null) - return null; - - return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0')); -} - - -/***/ }), -/* 23 */ -/***/ (function(module, exports) { - -module.exports = require("stream"); - -/***/ }), -/* 24 */ -/***/ (function(module, exports) { - -module.exports = require("url"); - -/***/ }), -/* 25 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subscription; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_isArray__ = __webpack_require__(41); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_isObject__ = __webpack_require__(444); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__util_isFunction__ = __webpack_require__(154); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__util_tryCatch__ = __webpack_require__(56); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__util_errorObject__ = __webpack_require__(48); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__ = __webpack_require__(441); -/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_tryCatch,_util_errorObject,_util_UnsubscriptionError PURE_IMPORTS_END */ - - - - - - -var Subscription = /*@__PURE__*/ (function () { - function Subscription(unsubscribe) { - this.closed = false; - this._parent = null; - this._parents = null; - this._subscriptions = null; - if (unsubscribe) { - this._unsubscribe = unsubscribe; - } - } - Subscription.prototype.unsubscribe = function () { - var hasErrors = false; - var errors; - if (this.closed) { - return; - } - var _a = this, _parent = _a._parent, _parents = _a._parents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; - this.closed = true; - this._parent = null; - this._parents = null; - this._subscriptions = null; - var index = -1; - var len = _parents ? _parents.length : 0; - while (_parent) { - _parent.remove(this); - _parent = ++index < len && _parents[index] || null; - } - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__util_isFunction__["a" /* isFunction */])(_unsubscribe)) { - var trial = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_tryCatch__["a" /* tryCatch */])(_unsubscribe).call(this); - if (trial === __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */]) { - hasErrors = true; - errors = errors || (__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */] ? - flattenUnsubscriptionErrors(__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e.errors) : [__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e]); - } - } - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__util_isArray__["a" /* isArray */])(_subscriptions)) { - index = -1; - len = _subscriptions.length; - while (++index < len) { - var sub = _subscriptions[index]; - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isObject__["a" /* isObject */])(sub)) { - var trial = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_tryCatch__["a" /* tryCatch */])(sub.unsubscribe).call(sub); - if (trial === __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */]) { - hasErrors = true; - errors = errors || []; - var err = __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e; - if (err instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */]) { - errors = errors.concat(flattenUnsubscriptionErrors(err.errors)); - } - else { - errors.push(err); - } - } - } - } - } - if (hasErrors) { - throw new __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */](errors); - } - }; - Subscription.prototype.add = function (teardown) { - if (!teardown || (teardown === Subscription.EMPTY)) { - return Subscription.EMPTY; - } - if (teardown === this) { - return this; - } - var subscription = teardown; - switch (typeof teardown) { - case 'function': - subscription = new Subscription(teardown); - case 'object': - if (subscription.closed || typeof subscription.unsubscribe !== 'function') { - return subscription; - } - else if (this.closed) { - subscription.unsubscribe(); - return subscription; - } - else if (typeof subscription._addParent !== 'function') { - var tmp = subscription; - subscription = new Subscription(); - subscription._subscriptions = [tmp]; - } - break; - default: - throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.'); - } - var subscriptions = this._subscriptions || (this._subscriptions = []); - subscriptions.push(subscription); - subscription._addParent(this); - return subscription; - }; - Subscription.prototype.remove = function (subscription) { - var subscriptions = this._subscriptions; - if (subscriptions) { - var subscriptionIndex = subscriptions.indexOf(subscription); - if (subscriptionIndex !== -1) { - subscriptions.splice(subscriptionIndex, 1); - } - } - }; - Subscription.prototype._addParent = function (parent) { - var _a = this, _parent = _a._parent, _parents = _a._parents; - if (!_parent || _parent === parent) { - this._parent = parent; - } - else if (!_parents) { - this._parents = [parent]; - } - else if (_parents.indexOf(parent) === -1) { - _parents.push(parent); - } - }; - Subscription.EMPTY = (function (empty) { - empty.closed = true; - return empty; - }(new Subscription())); - return Subscription; -}()); - -function flattenUnsubscriptionErrors(errors) { - return errors.reduce(function (errs, err) { return errs.concat((err instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */]) ? err.errors : err); }, []); -} -//# sourceMappingURL=Subscription.js.map - - -/***/ }), -/* 26 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2015 Joyent, Inc. - -module.exports = { - bufferSplit: bufferSplit, - addRSAMissing: addRSAMissing, - calculateDSAPublic: calculateDSAPublic, - calculateED25519Public: calculateED25519Public, - calculateX25519Public: calculateX25519Public, - mpNormalize: mpNormalize, - mpDenormalize: mpDenormalize, - ecNormalize: ecNormalize, - countZeros: countZeros, - assertCompatible: assertCompatible, - isCompatible: isCompatible, - opensslKeyDeriv: opensslKeyDeriv, - opensshCipherInfo: opensshCipherInfo, - publicFromPrivateECDSA: publicFromPrivateECDSA, - zeroPadToLength: zeroPadToLength, - writeBitString: writeBitString, - readBitString: readBitString -}; - -var assert = __webpack_require__(16); -var Buffer = __webpack_require__(15).Buffer; -var PrivateKey = __webpack_require__(33); -var Key = __webpack_require__(27); -var crypto = __webpack_require__(11); -var algs = __webpack_require__(32); -var asn1 = __webpack_require__(66); - -var ec, jsbn; -var nacl; - -var MAX_CLASS_DEPTH = 3; - -function isCompatible(obj, klass, needVer) { - if (obj === null || typeof (obj) !== 'object') - return (false); - if (needVer === undefined) - needVer = klass.prototype._sshpkApiVersion; - if (obj instanceof klass && - klass.prototype._sshpkApiVersion[0] == needVer[0]) - return (true); - var proto = Object.getPrototypeOf(obj); - var depth = 0; - while (proto.constructor.name !== klass.name) { - proto = Object.getPrototypeOf(proto); - if (!proto || ++depth > MAX_CLASS_DEPTH) - return (false); - } - if (proto.constructor.name !== klass.name) - return (false); - var ver = proto._sshpkApiVersion; - if (ver === undefined) - ver = klass._oldVersionDetect(obj); - if (ver[0] != needVer[0] || ver[1] < needVer[1]) - return (false); - return (true); -} - -function assertCompatible(obj, klass, needVer, name) { - if (name === undefined) - name = 'object'; - assert.ok(obj, name + ' must not be null'); - assert.object(obj, name + ' must be an object'); - if (needVer === undefined) - needVer = klass.prototype._sshpkApiVersion; - if (obj instanceof klass && - klass.prototype._sshpkApiVersion[0] == needVer[0]) - return; - var proto = Object.getPrototypeOf(obj); - var depth = 0; - while (proto.constructor.name !== klass.name) { - proto = Object.getPrototypeOf(proto); - assert.ok(proto && ++depth <= MAX_CLASS_DEPTH, - name + ' must be a ' + klass.name + ' instance'); - } - assert.strictEqual(proto.constructor.name, klass.name, - name + ' must be a ' + klass.name + ' instance'); - var ver = proto._sshpkApiVersion; - if (ver === undefined) - ver = klass._oldVersionDetect(obj); - assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1], - name + ' must be compatible with ' + klass.name + ' klass ' + - 'version ' + needVer[0] + '.' + needVer[1]); -} - -var CIPHER_LEN = { - 'des-ede3-cbc': { key: 7, iv: 8 }, - 'aes-128-cbc': { key: 16, iv: 16 } -}; -var PKCS5_SALT_LEN = 8; - -function opensslKeyDeriv(cipher, salt, passphrase, count) { - assert.buffer(salt, 'salt'); - assert.buffer(passphrase, 'passphrase'); - assert.number(count, 'iteration count'); - - var clen = CIPHER_LEN[cipher]; - assert.object(clen, 'supported cipher'); - - salt = salt.slice(0, PKCS5_SALT_LEN); - - var D, D_prev, bufs; - var material = Buffer.alloc(0); - while (material.length < clen.key + clen.iv) { - bufs = []; - if (D_prev) - bufs.push(D_prev); - bufs.push(passphrase); - bufs.push(salt); - D = Buffer.concat(bufs); - for (var j = 0; j < count; ++j) - D = crypto.createHash('md5').update(D).digest(); - material = Buffer.concat([material, D]); - D_prev = D; - } - - return ({ - key: material.slice(0, clen.key), - iv: material.slice(clen.key, clen.key + clen.iv) - }); -} - -/* Count leading zero bits on a buffer */ -function countZeros(buf) { - var o = 0, obit = 8; - while (o < buf.length) { - var mask = (1 << obit); - if ((buf[o] & mask) === mask) - break; - obit--; - if (obit < 0) { - o++; - obit = 8; - } - } - return (o*8 + (8 - obit) - 1); -} - -function bufferSplit(buf, chr) { - assert.buffer(buf); - assert.string(chr); - - var parts = []; - var lastPart = 0; - var matches = 0; - for (var i = 0; i < buf.length; ++i) { - if (buf[i] === chr.charCodeAt(matches)) - ++matches; - else if (buf[i] === chr.charCodeAt(0)) - matches = 1; - else - matches = 0; - - if (matches >= chr.length) { - var newPart = i + 1; - parts.push(buf.slice(lastPart, newPart - matches)); - lastPart = newPart; - matches = 0; - } - } - if (lastPart <= buf.length) - parts.push(buf.slice(lastPart, buf.length)); - - return (parts); -} - -function ecNormalize(buf, addZero) { - assert.buffer(buf); - if (buf[0] === 0x00 && buf[1] === 0x04) { - if (addZero) - return (buf); - return (buf.slice(1)); - } else if (buf[0] === 0x04) { - if (!addZero) - return (buf); - } else { - while (buf[0] === 0x00) - buf = buf.slice(1); - if (buf[0] === 0x02 || buf[0] === 0x03) - throw (new Error('Compressed elliptic curve points ' + - 'are not supported')); - if (buf[0] !== 0x04) - throw (new Error('Not a valid elliptic curve point')); - if (!addZero) - return (buf); - } - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x0; - buf.copy(b, 1); - return (b); -} - -function readBitString(der, tag) { - if (tag === undefined) - tag = asn1.Ber.BitString; - var buf = der.readString(tag, true); - assert.strictEqual(buf[0], 0x00, 'bit strings with unused bits are ' + - 'not supported (0x' + buf[0].toString(16) + ')'); - return (buf.slice(1)); -} - -function writeBitString(der, buf, tag) { - if (tag === undefined) - tag = asn1.Ber.BitString; - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x00; - buf.copy(b, 1); - der.writeBuffer(b, tag); -} - -function mpNormalize(buf) { - assert.buffer(buf); - while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00) - buf = buf.slice(1); - if ((buf[0] & 0x80) === 0x80) { - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x00; - buf.copy(b, 1); - buf = b; - } - return (buf); -} - -function mpDenormalize(buf) { - assert.buffer(buf); - while (buf.length > 1 && buf[0] === 0x00) - buf = buf.slice(1); - return (buf); -} - -function zeroPadToLength(buf, len) { - assert.buffer(buf); - assert.number(len); - while (buf.length > len) { - assert.equal(buf[0], 0x00); - buf = buf.slice(1); - } - while (buf.length < len) { - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x00; - buf.copy(b, 1); - buf = b; - } - return (buf); -} - -function bigintToMpBuf(bigint) { - var buf = Buffer.from(bigint.toByteArray()); - buf = mpNormalize(buf); - return (buf); -} - -function calculateDSAPublic(g, p, x) { - assert.buffer(g); - assert.buffer(p); - assert.buffer(x); - try { - var bigInt = __webpack_require__(81).BigInteger; - } catch (e) { - throw (new Error('To load a PKCS#8 format DSA private key, ' + - 'the node jsbn library is required.')); - } - g = new bigInt(g); - p = new bigInt(p); - x = new bigInt(x); - var y = g.modPow(x, p); - var ybuf = bigintToMpBuf(y); - return (ybuf); -} - -function calculateED25519Public(k) { - assert.buffer(k); - - if (nacl === undefined) - nacl = __webpack_require__(76); - - var kp = nacl.sign.keyPair.fromSeed(new Uint8Array(k)); - return (Buffer.from(kp.publicKey)); -} - -function calculateX25519Public(k) { - assert.buffer(k); - - if (nacl === undefined) - nacl = __webpack_require__(76); - - var kp = nacl.box.keyPair.fromSeed(new Uint8Array(k)); - return (Buffer.from(kp.publicKey)); -} - -function addRSAMissing(key) { - assert.object(key); - assertCompatible(key, PrivateKey, [1, 1]); - try { - var bigInt = __webpack_require__(81).BigInteger; - } catch (e) { - throw (new Error('To write a PEM private key from ' + - 'this source, the node jsbn lib is required.')); - } - - var d = new bigInt(key.part.d.data); - var buf; - - if (!key.part.dmodp) { - var p = new bigInt(key.part.p.data); - var dmodp = d.mod(p.subtract(1)); - - buf = bigintToMpBuf(dmodp); - key.part.dmodp = {name: 'dmodp', data: buf}; - key.parts.push(key.part.dmodp); - } - if (!key.part.dmodq) { - var q = new bigInt(key.part.q.data); - var dmodq = d.mod(q.subtract(1)); - - buf = bigintToMpBuf(dmodq); - key.part.dmodq = {name: 'dmodq', data: buf}; - key.parts.push(key.part.dmodq); - } -} - -function publicFromPrivateECDSA(curveName, priv) { - assert.string(curveName, 'curveName'); - assert.buffer(priv); - if (ec === undefined) - ec = __webpack_require__(139); - if (jsbn === undefined) - jsbn = __webpack_require__(81).BigInteger; - var params = algs.curves[curveName]; - var p = new jsbn(params.p); - var a = new jsbn(params.a); - var b = new jsbn(params.b); - var curve = new ec.ECCurveFp(p, a, b); - var G = curve.decodePointHex(params.G.toString('hex')); - - var d = new jsbn(mpNormalize(priv)); - var pub = G.multiply(d); - pub = Buffer.from(curve.encodePointHex(pub), 'hex'); - - var parts = []; - parts.push({name: 'curve', data: Buffer.from(curveName)}); - parts.push({name: 'Q', data: pub}); - - var key = new Key({type: 'ecdsa', curve: curve, parts: parts}); - return (key); -} - -function opensshCipherInfo(cipher) { - var inf = {}; - switch (cipher) { - case '3des-cbc': - inf.keySize = 24; - inf.blockSize = 8; - inf.opensslName = 'des-ede3-cbc'; - break; - case 'blowfish-cbc': - inf.keySize = 16; - inf.blockSize = 8; - inf.opensslName = 'bf-cbc'; - break; - case 'aes128-cbc': - case 'aes128-ctr': - case 'aes128-gcm@openssh.com': - inf.keySize = 16; - inf.blockSize = 16; - inf.opensslName = 'aes-128-' + cipher.slice(7, 10); - break; - case 'aes192-cbc': - case 'aes192-ctr': - case 'aes192-gcm@openssh.com': - inf.keySize = 24; - inf.blockSize = 16; - inf.opensslName = 'aes-192-' + cipher.slice(7, 10); - break; - case 'aes256-cbc': - case 'aes256-ctr': - case 'aes256-gcm@openssh.com': - inf.keySize = 32; - inf.blockSize = 16; - inf.opensslName = 'aes-256-' + cipher.slice(7, 10); - break; - default: - throw (new Error( - 'Unsupported openssl cipher "' + cipher + '"')); - } - return (inf); -} - - -/***/ }), -/* 27 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2017 Joyent, Inc. - -module.exports = Key; - -var assert = __webpack_require__(16); -var algs = __webpack_require__(32); -var crypto = __webpack_require__(11); -var Fingerprint = __webpack_require__(156); -var Signature = __webpack_require__(75); -var DiffieHellman = __webpack_require__(325).DiffieHellman; -var errs = __webpack_require__(74); -var utils = __webpack_require__(26); -var PrivateKey = __webpack_require__(33); -var edCompat; - -try { - edCompat = __webpack_require__(454); -} catch (e) { - /* Just continue through, and bail out if we try to use it. */ -} - -var InvalidAlgorithmError = errs.InvalidAlgorithmError; -var KeyParseError = errs.KeyParseError; - -var formats = {}; -formats['auto'] = __webpack_require__(455); -formats['pem'] = __webpack_require__(86); -formats['pkcs1'] = __webpack_require__(327); -formats['pkcs8'] = __webpack_require__(157); -formats['rfc4253'] = __webpack_require__(103); -formats['ssh'] = __webpack_require__(456); -formats['ssh-private'] = __webpack_require__(192); -formats['openssh'] = formats['ssh-private']; -formats['dnssec'] = __webpack_require__(326); - -function Key(opts) { - assert.object(opts, 'options'); - assert.arrayOfObject(opts.parts, 'options.parts'); - assert.string(opts.type, 'options.type'); - assert.optionalString(opts.comment, 'options.comment'); - - var algInfo = algs.info[opts.type]; - if (typeof (algInfo) !== 'object') - throw (new InvalidAlgorithmError(opts.type)); - - var partLookup = {}; - for (var i = 0; i < opts.parts.length; ++i) { - var part = opts.parts[i]; - partLookup[part.name] = part; - } - - this.type = opts.type; - this.parts = opts.parts; - this.part = partLookup; - this.comment = undefined; - this.source = opts.source; - - /* for speeding up hashing/fingerprint operations */ - this._rfc4253Cache = opts._rfc4253Cache; - this._hashCache = {}; - - var sz; - this.curve = undefined; - if (this.type === 'ecdsa') { - var curve = this.part.curve.data.toString(); - this.curve = curve; - sz = algs.curves[curve].size; - } else if (this.type === 'ed25519' || this.type === 'curve25519') { - sz = 256; - this.curve = 'curve25519'; - } else { - var szPart = this.part[algInfo.sizePart]; - sz = szPart.data.length; - sz = sz * 8 - utils.countZeros(szPart.data); - } - this.size = sz; -} - -Key.formats = formats; - -Key.prototype.toBuffer = function (format, options) { - if (format === undefined) - format = 'ssh'; - assert.string(format, 'format'); - assert.object(formats[format], 'formats[format]'); - assert.optionalObject(options, 'options'); - - if (format === 'rfc4253') { - if (this._rfc4253Cache === undefined) - this._rfc4253Cache = formats['rfc4253'].write(this); - return (this._rfc4253Cache); - } - - return (formats[format].write(this, options)); -}; - -Key.prototype.toString = function (format, options) { - return (this.toBuffer(format, options).toString()); -}; - -Key.prototype.hash = function (algo) { - assert.string(algo, 'algorithm'); - algo = algo.toLowerCase(); - if (algs.hashAlgs[algo] === undefined) - throw (new InvalidAlgorithmError(algo)); - - if (this._hashCache[algo]) - return (this._hashCache[algo]); - var hash = crypto.createHash(algo). - update(this.toBuffer('rfc4253')).digest(); - this._hashCache[algo] = hash; - return (hash); -}; - -Key.prototype.fingerprint = function (algo) { - if (algo === undefined) - algo = 'sha256'; - assert.string(algo, 'algorithm'); - var opts = { - type: 'key', - hash: this.hash(algo), - algorithm: algo - }; - return (new Fingerprint(opts)); -}; - -Key.prototype.defaultHashAlgorithm = function () { - var hashAlgo = 'sha1'; - if (this.type === 'rsa') - hashAlgo = 'sha256'; - if (this.type === 'dsa' && this.size > 1024) - hashAlgo = 'sha256'; - if (this.type === 'ed25519') - hashAlgo = 'sha512'; - if (this.type === 'ecdsa') { - if (this.size <= 256) - hashAlgo = 'sha256'; - else if (this.size <= 384) - hashAlgo = 'sha384'; - else - hashAlgo = 'sha512'; - } - return (hashAlgo); -}; - -Key.prototype.createVerify = function (hashAlgo) { - if (hashAlgo === undefined) - hashAlgo = this.defaultHashAlgorithm(); - assert.string(hashAlgo, 'hash algorithm'); - - /* ED25519 is not supported by OpenSSL, use a javascript impl. */ - if (this.type === 'ed25519' && edCompat !== undefined) - return (new edCompat.Verifier(this, hashAlgo)); - if (this.type === 'curve25519') - throw (new Error('Curve25519 keys are not suitable for ' + - 'signing or verification')); - - var v, nm, err; - try { - nm = hashAlgo.toUpperCase(); - v = crypto.createVerify(nm); - } catch (e) { - err = e; - } - if (v === undefined || (err instanceof Error && - err.message.match(/Unknown message digest/))) { - nm = 'RSA-'; - nm += hashAlgo.toUpperCase(); - v = crypto.createVerify(nm); - } - assert.ok(v, 'failed to create verifier'); - var oldVerify = v.verify.bind(v); - var key = this.toBuffer('pkcs8'); - var curve = this.curve; - var self = this; - v.verify = function (signature, fmt) { - if (Signature.isSignature(signature, [2, 0])) { - if (signature.type !== self.type) - return (false); - if (signature.hashAlgorithm && - signature.hashAlgorithm !== hashAlgo) - return (false); - if (signature.curve && self.type === 'ecdsa' && - signature.curve !== curve) - return (false); - return (oldVerify(key, signature.toBuffer('asn1'))); - - } else if (typeof (signature) === 'string' || - Buffer.isBuffer(signature)) { - return (oldVerify(key, signature, fmt)); - - /* - * Avoid doing this on valid arguments, walking the prototype - * chain can be quite slow. - */ - } else if (Signature.isSignature(signature, [1, 0])) { - throw (new Error('signature was created by too old ' + - 'a version of sshpk and cannot be verified')); - - } else { - throw (new TypeError('signature must be a string, ' + - 'Buffer, or Signature object')); - } - }; - return (v); -}; - -Key.prototype.createDiffieHellman = function () { - if (this.type === 'rsa') - throw (new Error('RSA keys do not support Diffie-Hellman')); - - return (new DiffieHellman(this)); -}; -Key.prototype.createDH = Key.prototype.createDiffieHellman; - -Key.parse = function (data, format, options) { - if (typeof (data) !== 'string') - assert.buffer(data, 'data'); - if (format === undefined) - format = 'auto'; - assert.string(format, 'format'); - if (typeof (options) === 'string') - options = { filename: options }; - assert.optionalObject(options, 'options'); - if (options === undefined) - options = {}; - assert.optionalString(options.filename, 'options.filename'); - if (options.filename === undefined) - options.filename = '(unnamed)'; - - assert.object(formats[format], 'formats[format]'); - - try { - var k = formats[format].read(data, options); - if (k instanceof PrivateKey) - k = k.toPublic(); - if (!k.comment) - k.comment = options.filename; - return (k); - } catch (e) { - if (e.name === 'KeyEncryptedError') - throw (e); - throw (new KeyParseError(options.filename, format, e)); - } -}; - -Key.isKey = function (obj, ver) { - return (utils.isCompatible(obj, Key, ver)); -}; - -/* - * API versions for Key: - * [1,0] -- initial ver, may take Signature for createVerify or may not - * [1,1] -- added pkcs1, pkcs8 formats - * [1,2] -- added auto, ssh-private, openssh formats - * [1,3] -- added defaultHashAlgorithm - * [1,4] -- added ed support, createDH - * [1,5] -- first explicitly tagged version - * [1,6] -- changed ed25519 part names - */ -Key.prototype._sshpkApiVersion = [1, 6]; - -Key._oldVersionDetect = function (obj) { - assert.func(obj.toBuffer); - assert.func(obj.fingerprint); - if (obj.createDH) - return ([1, 4]); - if (obj.defaultHashAlgorithm) - return ([1, 3]); - if (obj.formats['auto']) - return ([1, 2]); - if (obj.formats['pkcs1']) - return ([1, 1]); - return ([1, 0]); -}; - - -/***/ }), -/* 28 */ -/***/ (function(module, exports) { - -module.exports = require("assert"); - -/***/ }), -/* 29 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = nullify; -function nullify(obj = {}) { - if (Array.isArray(obj)) { - for (var _iterator = obj, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - const item = _ref; - - nullify(item); - } - } else if (obj !== null && typeof obj === 'object' || typeof obj === 'function') { - Object.setPrototypeOf(obj, null); - - // for..in can only be applied to 'object', not 'function' - if (typeof obj === 'object') { - for (const key in obj) { - nullify(obj[key]); - } - } - } - - return obj; -} - -/***/ }), -/* 30 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const escapeStringRegexp = __webpack_require__(388); -const ansiStyles = __webpack_require__(506); -const stdoutColor = __webpack_require__(598).stdout; - -const template = __webpack_require__(599); - -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); - -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; - -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); - -const styles = Object.create(null); - -function applyOptions(obj, options) { - options = options || {}; - - // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; -} - -function Chalk(options) { - // We check for this.template here since calling `chalk.constructor()` - // by itself will have a `this` of a previously constructed chalk object - if (!this || !(this instanceof Chalk) || this.template) { - const chalk = {}; - applyOptions(chalk, options); - - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; - - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); - - chalk.template.constructor = Chalk; - - return chalk.template; - } - - applyOptions(this, options); -} - -// Use bright blue on Windows as the normal blue color is illegible -if (isSimpleWindowsTerm) { - ansiStyles.blue.open = '\u001B[94m'; -} - -for (const key of Object.keys(ansiStyles)) { - ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); - - styles[key] = { - get() { - const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); - } - }; -} - -styles.visible = { - get() { - return build.call(this, this._styles || [], true, 'visible'); - } -}; - -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); -for (const model of Object.keys(ansiStyles.color.ansi)) { - if (skipModels.has(model)) { - continue; - } - - styles[model] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} - -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } - - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} - -const proto = Object.defineProperties(() => {}, styles); - -function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; - - builder._styles = _styles; - builder._empty = _empty; - - const self = this; - - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); - - Object.defineProperty(builder, 'enabled', { - enumerable: true, - get() { - return self.enabled; - }, - set(enabled) { - self.enabled = enabled; - } - }); - - // See below for fix regarding invisible grey/dim combination on Windows - builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; - - // `__proto__` is used because we must return a function, but there is - // no way to create a function with a different prototype - builder.__proto__ = proto; // eslint-disable-line no-proto - - return builder; -} - -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); - - if (argsLen === 0) { - return ''; - } - - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } - - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; - } - - // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, - // see https://github.com/chalk/chalk/issues/58 - // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. - const originalDim = ansiStyles.dim.open; - if (isSimpleWindowsTerm && this.hasGrey) { - ansiStyles.dim.open = ''; - } - - for (const code of this._styles.slice().reverse()) { - // Replace any instances already present with a re-opening code - // otherwise only the part of the string until said closing code - // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; - - // Close the styling before a linebreak and reopen - // after next line to fix a bleed issue on macOS - // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); - } - - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; - - return str; -} - -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } - - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; - - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); - } - - return template(chalk, parts.join('')); -} - -Object.defineProperties(Chalk.prototype, styles); - -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript - - -/***/ }), -/* 31 */ -/***/ (function(module, exports) { - -var core = module.exports = { version: '2.5.7' }; -if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef - - -/***/ }), -/* 32 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2015 Joyent, Inc. - -var Buffer = __webpack_require__(15).Buffer; - -var algInfo = { - 'dsa': { - parts: ['p', 'q', 'g', 'y'], - sizePart: 'p' - }, - 'rsa': { - parts: ['e', 'n'], - sizePart: 'n' - }, - 'ecdsa': { - parts: ['curve', 'Q'], - sizePart: 'Q' - }, - 'ed25519': { - parts: ['A'], - sizePart: 'A' - } -}; -algInfo['curve25519'] = algInfo['ed25519']; - -var algPrivInfo = { - 'dsa': { - parts: ['p', 'q', 'g', 'y', 'x'] - }, - 'rsa': { - parts: ['n', 'e', 'd', 'iqmp', 'p', 'q'] - }, - 'ecdsa': { - parts: ['curve', 'Q', 'd'] - }, - 'ed25519': { - parts: ['A', 'k'] - } -}; -algPrivInfo['curve25519'] = algPrivInfo['ed25519']; - -var hashAlgs = { - 'md5': true, - 'sha1': true, - 'sha256': true, - 'sha384': true, - 'sha512': true -}; - -/* - * Taken from - * http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf - */ -var curves = { - 'nistp256': { - size: 256, - pkcs8oid: '1.2.840.10045.3.1.7', - p: Buffer.from(('00' + - 'ffffffff 00000001 00000000 00000000' + - '00000000 ffffffff ffffffff ffffffff'). - replace(/ /g, ''), 'hex'), - a: Buffer.from(('00' + - 'FFFFFFFF 00000001 00000000 00000000' + - '00000000 FFFFFFFF FFFFFFFF FFFFFFFC'). - replace(/ /g, ''), 'hex'), - b: Buffer.from(( - '5ac635d8 aa3a93e7 b3ebbd55 769886bc' + - '651d06b0 cc53b0f6 3bce3c3e 27d2604b'). - replace(/ /g, ''), 'hex'), - s: Buffer.from(('00' + - 'c49d3608 86e70493 6a6678e1 139d26b7' + - '819f7e90'). - replace(/ /g, ''), 'hex'), - n: Buffer.from(('00' + - 'ffffffff 00000000 ffffffff ffffffff' + - 'bce6faad a7179e84 f3b9cac2 fc632551'). - replace(/ /g, ''), 'hex'), - G: Buffer.from(('04' + - '6b17d1f2 e12c4247 f8bce6e5 63a440f2' + - '77037d81 2deb33a0 f4a13945 d898c296' + - '4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16' + - '2bce3357 6b315ece cbb64068 37bf51f5'). - replace(/ /g, ''), 'hex') - }, - 'nistp384': { - size: 384, - pkcs8oid: '1.3.132.0.34', - p: Buffer.from(('00' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff fffffffe' + - 'ffffffff 00000000 00000000 ffffffff'). - replace(/ /g, ''), 'hex'), - a: Buffer.from(('00' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE' + - 'FFFFFFFF 00000000 00000000 FFFFFFFC'). - replace(/ /g, ''), 'hex'), - b: Buffer.from(( - 'b3312fa7 e23ee7e4 988e056b e3f82d19' + - '181d9c6e fe814112 0314088f 5013875a' + - 'c656398d 8a2ed19d 2a85c8ed d3ec2aef'). - replace(/ /g, ''), 'hex'), - s: Buffer.from(('00' + - 'a335926a a319a27a 1d00896a 6773a482' + - '7acdac73'). - replace(/ /g, ''), 'hex'), - n: Buffer.from(('00' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff c7634d81 f4372ddf' + - '581a0db2 48b0a77a ecec196a ccc52973'). - replace(/ /g, ''), 'hex'), - G: Buffer.from(('04' + - 'aa87ca22 be8b0537 8eb1c71e f320ad74' + - '6e1d3b62 8ba79b98 59f741e0 82542a38' + - '5502f25d bf55296c 3a545e38 72760ab7' + - '3617de4a 96262c6f 5d9e98bf 9292dc29' + - 'f8f41dbd 289a147c e9da3113 b5f0b8c0' + - '0a60b1ce 1d7e819d 7a431d7c 90ea0e5f'). - replace(/ /g, ''), 'hex') - }, - 'nistp521': { - size: 521, - pkcs8oid: '1.3.132.0.35', - p: Buffer.from(( - '01ffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffff').replace(/ /g, ''), 'hex'), - a: Buffer.from(('01FF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFC'). - replace(/ /g, ''), 'hex'), - b: Buffer.from(('51' + - '953eb961 8e1c9a1f 929a21a0 b68540ee' + - 'a2da725b 99b315f3 b8b48991 8ef109e1' + - '56193951 ec7e937b 1652c0bd 3bb1bf07' + - '3573df88 3d2c34f1 ef451fd4 6b503f00'). - replace(/ /g, ''), 'hex'), - s: Buffer.from(('00' + - 'd09e8800 291cb853 96cc6717 393284aa' + - 'a0da64ba').replace(/ /g, ''), 'hex'), - n: Buffer.from(('01ff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff fffffffa' + - '51868783 bf2f966b 7fcc0148 f709a5d0' + - '3bb5c9b8 899c47ae bb6fb71e 91386409'). - replace(/ /g, ''), 'hex'), - G: Buffer.from(('04' + - '00c6 858e06b7 0404e9cd 9e3ecb66 2395b442' + - '9c648139 053fb521 f828af60 6b4d3dba' + - 'a14b5e77 efe75928 fe1dc127 a2ffa8de' + - '3348b3c1 856a429b f97e7e31 c2e5bd66' + - '0118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9' + - '98f54449 579b4468 17afbd17 273e662c' + - '97ee7299 5ef42640 c550b901 3fad0761' + - '353c7086 a272c240 88be9476 9fd16650'). - replace(/ /g, ''), 'hex') - } -}; - -module.exports = { - info: algInfo, - privInfo: algPrivInfo, - hashAlgs: hashAlgs, - curves: curves -}; - - -/***/ }), -/* 33 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2017 Joyent, Inc. - -module.exports = PrivateKey; - -var assert = __webpack_require__(16); -var Buffer = __webpack_require__(15).Buffer; -var algs = __webpack_require__(32); -var crypto = __webpack_require__(11); -var Fingerprint = __webpack_require__(156); -var Signature = __webpack_require__(75); -var errs = __webpack_require__(74); -var util = __webpack_require__(3); -var utils = __webpack_require__(26); -var dhe = __webpack_require__(325); -var generateECDSA = dhe.generateECDSA; -var generateED25519 = dhe.generateED25519; -var edCompat; -var nacl; - -try { - edCompat = __webpack_require__(454); -} catch (e) { - /* Just continue through, and bail out if we try to use it. */ -} - -var Key = __webpack_require__(27); - -var InvalidAlgorithmError = errs.InvalidAlgorithmError; -var KeyParseError = errs.KeyParseError; -var KeyEncryptedError = errs.KeyEncryptedError; - -var formats = {}; -formats['auto'] = __webpack_require__(455); -formats['pem'] = __webpack_require__(86); -formats['pkcs1'] = __webpack_require__(327); -formats['pkcs8'] = __webpack_require__(157); -formats['rfc4253'] = __webpack_require__(103); -formats['ssh-private'] = __webpack_require__(192); -formats['openssh'] = formats['ssh-private']; -formats['ssh'] = formats['ssh-private']; -formats['dnssec'] = __webpack_require__(326); - -function PrivateKey(opts) { - assert.object(opts, 'options'); - Key.call(this, opts); - - this._pubCache = undefined; -} -util.inherits(PrivateKey, Key); - -PrivateKey.formats = formats; - -PrivateKey.prototype.toBuffer = function (format, options) { - if (format === undefined) - format = 'pkcs1'; - assert.string(format, 'format'); - assert.object(formats[format], 'formats[format]'); - assert.optionalObject(options, 'options'); - - return (formats[format].write(this, options)); -}; - -PrivateKey.prototype.hash = function (algo) { - return (this.toPublic().hash(algo)); -}; - -PrivateKey.prototype.toPublic = function () { - if (this._pubCache) - return (this._pubCache); - - var algInfo = algs.info[this.type]; - var pubParts = []; - for (var i = 0; i < algInfo.parts.length; ++i) { - var p = algInfo.parts[i]; - pubParts.push(this.part[p]); - } - - this._pubCache = new Key({ - type: this.type, - source: this, - parts: pubParts - }); - if (this.comment) - this._pubCache.comment = this.comment; - return (this._pubCache); -}; - -PrivateKey.prototype.derive = function (newType) { - assert.string(newType, 'type'); - var priv, pub, pair; - - if (this.type === 'ed25519' && newType === 'curve25519') { - if (nacl === undefined) - nacl = __webpack_require__(76); - - priv = this.part.k.data; - if (priv[0] === 0x00) - priv = priv.slice(1); - - pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv)); - pub = Buffer.from(pair.publicKey); - - return (new PrivateKey({ - type: 'curve25519', - parts: [ - { name: 'A', data: utils.mpNormalize(pub) }, - { name: 'k', data: utils.mpNormalize(priv) } - ] - })); - } else if (this.type === 'curve25519' && newType === 'ed25519') { - if (nacl === undefined) - nacl = __webpack_require__(76); - - priv = this.part.k.data; - if (priv[0] === 0x00) - priv = priv.slice(1); - - pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv)); - pub = Buffer.from(pair.publicKey); - - return (new PrivateKey({ - type: 'ed25519', - parts: [ - { name: 'A', data: utils.mpNormalize(pub) }, - { name: 'k', data: utils.mpNormalize(priv) } - ] - })); - } - throw (new Error('Key derivation not supported from ' + this.type + - ' to ' + newType)); -}; - -PrivateKey.prototype.createVerify = function (hashAlgo) { - return (this.toPublic().createVerify(hashAlgo)); -}; - -PrivateKey.prototype.createSign = function (hashAlgo) { - if (hashAlgo === undefined) - hashAlgo = this.defaultHashAlgorithm(); - assert.string(hashAlgo, 'hash algorithm'); - - /* ED25519 is not supported by OpenSSL, use a javascript impl. */ - if (this.type === 'ed25519' && edCompat !== undefined) - return (new edCompat.Signer(this, hashAlgo)); - if (this.type === 'curve25519') - throw (new Error('Curve25519 keys are not suitable for ' + - 'signing or verification')); - - var v, nm, err; - try { - nm = hashAlgo.toUpperCase(); - v = crypto.createSign(nm); - } catch (e) { - err = e; - } - if (v === undefined || (err instanceof Error && - err.message.match(/Unknown message digest/))) { - nm = 'RSA-'; - nm += hashAlgo.toUpperCase(); - v = crypto.createSign(nm); - } - assert.ok(v, 'failed to create verifier'); - var oldSign = v.sign.bind(v); - var key = this.toBuffer('pkcs1'); - var type = this.type; - var curve = this.curve; - v.sign = function () { - var sig = oldSign(key); - if (typeof (sig) === 'string') - sig = Buffer.from(sig, 'binary'); - sig = Signature.parse(sig, type, 'asn1'); - sig.hashAlgorithm = hashAlgo; - sig.curve = curve; - return (sig); - }; - return (v); -}; - -PrivateKey.parse = function (data, format, options) { - if (typeof (data) !== 'string') - assert.buffer(data, 'data'); - if (format === undefined) - format = 'auto'; - assert.string(format, 'format'); - if (typeof (options) === 'string') - options = { filename: options }; - assert.optionalObject(options, 'options'); - if (options === undefined) - options = {}; - assert.optionalString(options.filename, 'options.filename'); - if (options.filename === undefined) - options.filename = '(unnamed)'; - - assert.object(formats[format], 'formats[format]'); - - try { - var k = formats[format].read(data, options); - assert.ok(k instanceof PrivateKey, 'key is not a private key'); - if (!k.comment) - k.comment = options.filename; - return (k); - } catch (e) { - if (e.name === 'KeyEncryptedError') - throw (e); - throw (new KeyParseError(options.filename, format, e)); - } -}; - -PrivateKey.isPrivateKey = function (obj, ver) { - return (utils.isCompatible(obj, PrivateKey, ver)); -}; - -PrivateKey.generate = function (type, options) { - if (options === undefined) - options = {}; - assert.object(options, 'options'); - - switch (type) { - case 'ecdsa': - if (options.curve === undefined) - options.curve = 'nistp256'; - assert.string(options.curve, 'options.curve'); - return (generateECDSA(options.curve)); - case 'ed25519': - return (generateED25519()); - default: - throw (new Error('Key generation not supported with key ' + - 'type "' + type + '"')); - } -}; - -/* - * API versions for PrivateKey: - * [1,0] -- initial ver - * [1,1] -- added auto, pkcs[18], openssh/ssh-private formats - * [1,2] -- added defaultHashAlgorithm - * [1,3] -- added derive, ed, createDH - * [1,4] -- first tagged version - * [1,5] -- changed ed25519 part names and format - */ -PrivateKey.prototype._sshpkApiVersion = [1, 5]; - -PrivateKey._oldVersionDetect = function (obj) { - assert.func(obj.toPublic); - assert.func(obj.createSign); - if (obj.derive) - return ([1, 3]); - if (obj.defaultHashAlgorithm) - return ([1, 2]); - if (obj.formats['auto']) - return ([1, 1]); - return ([1, 0]); -}; - - -/***/ }), -/* 34 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.wrapLifecycle = exports.run = exports.install = exports.Install = undefined; - -var _extends2; - -function _load_extends() { - return _extends2 = _interopRequireDefault(__webpack_require__(21)); -} - -var _asyncToGenerator2; - -function _load_asyncToGenerator() { - return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); -} - -let install = exports.install = (() => { - var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, reporter, flags, lockfile) { - yield wrapLifecycle(config, flags, (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const install = new Install(flags, config, reporter, lockfile); - yield install.init(); - })); - }); - - return function install(_x7, _x8, _x9, _x10) { - return _ref29.apply(this, arguments); - }; -})(); - -let run = exports.run = (() => { - var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, reporter, flags, args) { - let lockfile; - let error = 'installCommandRenamed'; - if (flags.lockfile === false) { - lockfile = new (_lockfile || _load_lockfile()).default(); - } else { - lockfile = yield (_lockfile || _load_lockfile()).default.fromDirectory(config.lockfileFolder, reporter); - } - - if (args.length) { - const exampleArgs = args.slice(); - - if (flags.saveDev) { - exampleArgs.push('--dev'); - } - if (flags.savePeer) { - exampleArgs.push('--peer'); - } - if (flags.saveOptional) { - exampleArgs.push('--optional'); - } - if (flags.saveExact) { - exampleArgs.push('--exact'); - } - if (flags.saveTilde) { - exampleArgs.push('--tilde'); - } - let command = 'add'; - if (flags.global) { - error = 'globalFlagRemoved'; - command = 'global add'; - } - throw new (_errors || _load_errors()).MessageError(reporter.lang(error, `yarn ${command} ${exampleArgs.join(' ')}`)); - } - - yield install(config, reporter, flags, lockfile); - }); - - return function run(_x11, _x12, _x13, _x14) { - return _ref31.apply(this, arguments); - }; -})(); - -let wrapLifecycle = exports.wrapLifecycle = (() => { - var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, flags, factory) { - yield config.executeLifecycleScript('preinstall'); - - yield factory(); - - // npm behaviour, seems kinda funky but yay compatibility - yield config.executeLifecycleScript('install'); - yield config.executeLifecycleScript('postinstall'); - - if (!config.production) { - if (!config.disablePrepublish) { - yield config.executeLifecycleScript('prepublish'); - } - yield config.executeLifecycleScript('prepare'); - } - }); - - return function wrapLifecycle(_x15, _x16, _x17) { - return _ref32.apply(this, arguments); - }; -})(); - -exports.hasWrapper = hasWrapper; -exports.setFlags = setFlags; - -var _objectPath; - -function _load_objectPath() { - return _objectPath = _interopRequireDefault(__webpack_require__(304)); -} - -var _hooks; - -function _load_hooks() { - return _hooks = __webpack_require__(374); -} - -var _index; - -function _load_index() { - return _index = _interopRequireDefault(__webpack_require__(220)); -} - -var _errors; - -function _load_errors() { - return _errors = __webpack_require__(6); -} - -var _integrityChecker; - -function _load_integrityChecker() { - return _integrityChecker = _interopRequireDefault(__webpack_require__(208)); -} - -var _lockfile; - -function _load_lockfile() { - return _lockfile = _interopRequireDefault(__webpack_require__(19)); -} - -var _lockfile2; - -function _load_lockfile2() { - return _lockfile2 = __webpack_require__(19); -} - -var _packageFetcher; - -function _load_packageFetcher() { - return _packageFetcher = _interopRequireWildcard(__webpack_require__(210)); -} - -var _packageInstallScripts; - -function _load_packageInstallScripts() { - return _packageInstallScripts = _interopRequireDefault(__webpack_require__(557)); -} - -var _packageCompatibility; - -function _load_packageCompatibility() { - return _packageCompatibility = _interopRequireWildcard(__webpack_require__(209)); -} - -var _packageResolver; - -function _load_packageResolver() { - return _packageResolver = _interopRequireDefault(__webpack_require__(366)); -} - -var _packageLinker; - -function _load_packageLinker() { - return _packageLinker = _interopRequireDefault(__webpack_require__(211)); -} - -var _index2; - -function _load_index2() { - return _index2 = __webpack_require__(57); -} - -var _index3; - -function _load_index3() { - return _index3 = __webpack_require__(78); -} - -var _autoclean; - -function _load_autoclean() { - return _autoclean = __webpack_require__(354); -} - -var _constants; - -function _load_constants() { - return _constants = _interopRequireWildcard(__webpack_require__(8)); -} - -var _normalizePattern; - -function _load_normalizePattern() { - return _normalizePattern = __webpack_require__(37); -} - -var _fs; - -function _load_fs() { - return _fs = _interopRequireWildcard(__webpack_require__(4)); -} - -var _map; - -function _load_map() { - return _map = _interopRequireDefault(__webpack_require__(29)); -} - -var _yarnVersion; - -function _load_yarnVersion() { - return _yarnVersion = __webpack_require__(120); -} - -var _generatePnpMap; - -function _load_generatePnpMap() { - return _generatePnpMap = __webpack_require__(579); -} - -var _workspaceLayout; - -function _load_workspaceLayout() { - return _workspaceLayout = _interopRequireDefault(__webpack_require__(90)); -} - -var _resolutionMap; - -function _load_resolutionMap() { - return _resolutionMap = _interopRequireDefault(__webpack_require__(214)); -} - -var _guessName; - -function _load_guessName() { - return _guessName = _interopRequireDefault(__webpack_require__(169)); -} - -var _audit; - -function _load_audit() { - return _audit = _interopRequireDefault(__webpack_require__(353)); -} - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const deepEqual = __webpack_require__(631); - -const emoji = __webpack_require__(302); -const invariant = __webpack_require__(9); -const path = __webpack_require__(0); -const semver = __webpack_require__(22); -const uuid = __webpack_require__(119); -const ssri = __webpack_require__(65); - -const ONE_DAY = 1000 * 60 * 60 * 24; - -/** - * Try and detect the installation method for Yarn and provide a command to update it with. - */ - -function getUpdateCommand(installationMethod) { - if (installationMethod === 'tar') { - return `curl --compressed -o- -L ${(_constants || _load_constants()).YARN_INSTALLER_SH} | bash`; - } - - if (installationMethod === 'homebrew') { - return 'brew upgrade yarn'; - } - - if (installationMethod === 'deb') { - return 'sudo apt-get update && sudo apt-get install yarn'; - } - - if (installationMethod === 'rpm') { - return 'sudo yum install yarn'; - } - - if (installationMethod === 'npm') { - return 'npm install --global yarn'; - } - - if (installationMethod === 'chocolatey') { - return 'choco upgrade yarn'; - } - - if (installationMethod === 'apk') { - return 'apk update && apk add -u yarn'; - } - - if (installationMethod === 'portage') { - return 'sudo emerge --sync && sudo emerge -au sys-apps/yarn'; - } - - return null; -} - -function getUpdateInstaller(installationMethod) { - // Windows - if (installationMethod === 'msi') { - return (_constants || _load_constants()).YARN_INSTALLER_MSI; - } - - return null; -} - -function normalizeFlags(config, rawFlags) { - const flags = { - // install - har: !!rawFlags.har, - ignorePlatform: !!rawFlags.ignorePlatform, - ignoreEngines: !!rawFlags.ignoreEngines, - ignoreScripts: !!rawFlags.ignoreScripts, - ignoreOptional: !!rawFlags.ignoreOptional, - force: !!rawFlags.force, - flat: !!rawFlags.flat, - lockfile: rawFlags.lockfile !== false, - pureLockfile: !!rawFlags.pureLockfile, - updateChecksums: !!rawFlags.updateChecksums, - skipIntegrityCheck: !!rawFlags.skipIntegrityCheck, - frozenLockfile: !!rawFlags.frozenLockfile, - linkDuplicates: !!rawFlags.linkDuplicates, - checkFiles: !!rawFlags.checkFiles, - audit: !!rawFlags.audit, - - // add - peer: !!rawFlags.peer, - dev: !!rawFlags.dev, - optional: !!rawFlags.optional, - exact: !!rawFlags.exact, - tilde: !!rawFlags.tilde, - ignoreWorkspaceRootCheck: !!rawFlags.ignoreWorkspaceRootCheck, - - // outdated, update-interactive - includeWorkspaceDeps: !!rawFlags.includeWorkspaceDeps, - - // add, remove, update - workspaceRootIsCwd: rawFlags.workspaceRootIsCwd !== false - }; - - if (config.getOption('ignore-scripts')) { - flags.ignoreScripts = true; - } - - if (config.getOption('ignore-platform')) { - flags.ignorePlatform = true; - } - - if (config.getOption('ignore-engines')) { - flags.ignoreEngines = true; - } - - if (config.getOption('ignore-optional')) { - flags.ignoreOptional = true; - } - - if (config.getOption('force')) { - flags.force = true; - } - - return flags; -} - -class Install { - constructor(flags, config, reporter, lockfile) { - this.rootManifestRegistries = []; - this.rootPatternsToOrigin = (0, (_map || _load_map()).default)(); - this.lockfile = lockfile; - this.reporter = reporter; - this.config = config; - this.flags = normalizeFlags(config, flags); - this.resolutions = (0, (_map || _load_map()).default)(); // Legacy resolutions field used for flat install mode - this.resolutionMap = new (_resolutionMap || _load_resolutionMap()).default(config); // Selective resolutions for nested dependencies - this.resolver = new (_packageResolver || _load_packageResolver()).default(config, lockfile, this.resolutionMap); - this.integrityChecker = new (_integrityChecker || _load_integrityChecker()).default(config); - this.linker = new (_packageLinker || _load_packageLinker()).default(config, this.resolver); - this.scripts = new (_packageInstallScripts || _load_packageInstallScripts()).default(config, this.resolver, this.flags.force); - } - - /** - * Create a list of dependency requests from the current directories manifests. - */ - - fetchRequestFromCwd(excludePatterns = [], ignoreUnusedPatterns = false) { - var _this = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const patterns = []; - const deps = []; - let resolutionDeps = []; - const manifest = {}; - - const ignorePatterns = []; - const usedPatterns = []; - let workspaceLayout; - - // some commands should always run in the context of the entire workspace - const cwd = _this.flags.includeWorkspaceDeps || _this.flags.workspaceRootIsCwd ? _this.config.lockfileFolder : _this.config.cwd; - - // non-workspaces are always root, otherwise check for workspace root - const cwdIsRoot = !_this.config.workspaceRootFolder || _this.config.lockfileFolder === cwd; - - // exclude package names that are in install args - const excludeNames = []; - for (var _iterator = excludePatterns, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - const pattern = _ref; - - if ((0, (_index3 || _load_index3()).getExoticResolver)(pattern)) { - excludeNames.push((0, (_guessName || _load_guessName()).default)(pattern)); - } else { - // extract the name - const parts = (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern); - excludeNames.push(parts.name); - } - } - - const stripExcluded = function stripExcluded(manifest) { - for (var _iterator2 = excludeNames, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { - var _ref2; - - if (_isArray2) { - if (_i2 >= _iterator2.length) break; - _ref2 = _iterator2[_i2++]; - } else { - _i2 = _iterator2.next(); - if (_i2.done) break; - _ref2 = _i2.value; - } - - const exclude = _ref2; - - if (manifest.dependencies && manifest.dependencies[exclude]) { - delete manifest.dependencies[exclude]; - } - if (manifest.devDependencies && manifest.devDependencies[exclude]) { - delete manifest.devDependencies[exclude]; - } - if (manifest.optionalDependencies && manifest.optionalDependencies[exclude]) { - delete manifest.optionalDependencies[exclude]; - } - } - }; - - for (var _iterator3 = Object.keys((_index2 || _load_index2()).registries), _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { - var _ref3; - - if (_isArray3) { - if (_i3 >= _iterator3.length) break; - _ref3 = _iterator3[_i3++]; - } else { - _i3 = _iterator3.next(); - if (_i3.done) break; - _ref3 = _i3.value; - } - - const registry = _ref3; - - const filename = (_index2 || _load_index2()).registries[registry].filename; - - const loc = path.join(cwd, filename); - if (!(yield (_fs || _load_fs()).exists(loc))) { - continue; - } - - _this.rootManifestRegistries.push(registry); - - const projectManifestJson = yield _this.config.readJson(loc); - yield (0, (_index || _load_index()).default)(projectManifestJson, cwd, _this.config, cwdIsRoot); - - Object.assign(_this.resolutions, projectManifestJson.resolutions); - Object.assign(manifest, projectManifestJson); - - _this.resolutionMap.init(_this.resolutions); - for (var _iterator4 = Object.keys(_this.resolutionMap.resolutionsByPackage), _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { - var _ref4; - - if (_isArray4) { - if (_i4 >= _iterator4.length) break; - _ref4 = _iterator4[_i4++]; - } else { - _i4 = _iterator4.next(); - if (_i4.done) break; - _ref4 = _i4.value; - } - - const packageName = _ref4; - - const optional = (_objectPath || _load_objectPath()).default.has(manifest.optionalDependencies, packageName) && _this.flags.ignoreOptional; - for (var _iterator8 = _this.resolutionMap.resolutionsByPackage[packageName], _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { - var _ref9; - - if (_isArray8) { - if (_i8 >= _iterator8.length) break; - _ref9 = _iterator8[_i8++]; - } else { - _i8 = _iterator8.next(); - if (_i8.done) break; - _ref9 = _i8.value; - } - - const _ref8 = _ref9; - const pattern = _ref8.pattern; - - resolutionDeps = [...resolutionDeps, { registry, pattern, optional, hint: 'resolution' }]; - } - } - - const pushDeps = function pushDeps(depType, manifest, { hint, optional }, isUsed) { - if (ignoreUnusedPatterns && !isUsed) { - return; - } - // We only take unused dependencies into consideration to get deterministic hoisting. - // Since flat mode doesn't care about hoisting and everything is top level and specified then we can safely - // leave these out. - if (_this.flags.flat && !isUsed) { - return; - } - const depMap = manifest[depType]; - for (const name in depMap) { - if (excludeNames.indexOf(name) >= 0) { - continue; - } - - let pattern = name; - if (!_this.lockfile.getLocked(pattern)) { - // when we use --save we save the dependency to the lockfile with just the name rather than the - // version combo - pattern += '@' + depMap[name]; - } - - // normalization made sure packages are mentioned only once - if (isUsed) { - usedPatterns.push(pattern); - } else { - ignorePatterns.push(pattern); - } - - _this.rootPatternsToOrigin[pattern] = depType; - patterns.push(pattern); - deps.push({ pattern, registry, hint, optional, workspaceName: manifest.name, workspaceLoc: manifest._loc }); - } - }; - - if (cwdIsRoot) { - pushDeps('dependencies', projectManifestJson, { hint: null, optional: false }, true); - pushDeps('devDependencies', projectManifestJson, { hint: 'dev', optional: false }, !_this.config.production); - pushDeps('optionalDependencies', projectManifestJson, { hint: 'optional', optional: true }, true); - } - - if (_this.config.workspaceRootFolder) { - const workspaceLoc = cwdIsRoot ? loc : path.join(_this.config.lockfileFolder, filename); - const workspacesRoot = path.dirname(workspaceLoc); - - let workspaceManifestJson = projectManifestJson; - if (!cwdIsRoot) { - // the manifest we read before was a child workspace, so get the root - workspaceManifestJson = yield _this.config.readJson(workspaceLoc); - yield (0, (_index || _load_index()).default)(workspaceManifestJson, workspacesRoot, _this.config, true); - } - - const workspaces = yield _this.config.resolveWorkspaces(workspacesRoot, workspaceManifestJson); - workspaceLayout = new (_workspaceLayout || _load_workspaceLayout()).default(workspaces, _this.config); - - // add virtual manifest that depends on all workspaces, this way package hoisters and resolvers will work fine - const workspaceDependencies = (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.dependencies); - for (var _iterator5 = Object.keys(workspaces), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { - var _ref5; - - if (_isArray5) { - if (_i5 >= _iterator5.length) break; - _ref5 = _iterator5[_i5++]; - } else { - _i5 = _iterator5.next(); - if (_i5.done) break; - _ref5 = _i5.value; - } - - const workspaceName = _ref5; - - const workspaceManifest = workspaces[workspaceName].manifest; - workspaceDependencies[workspaceName] = workspaceManifest.version; - - // include dependencies from all workspaces - if (_this.flags.includeWorkspaceDeps) { - pushDeps('dependencies', workspaceManifest, { hint: null, optional: false }, true); - pushDeps('devDependencies', workspaceManifest, { hint: 'dev', optional: false }, !_this.config.production); - pushDeps('optionalDependencies', workspaceManifest, { hint: 'optional', optional: true }, true); - } - } - const virtualDependencyManifest = { - _uid: '', - name: `workspace-aggregator-${uuid.v4()}`, - version: '1.0.0', - _registry: 'npm', - _loc: workspacesRoot, - dependencies: workspaceDependencies, - devDependencies: (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.devDependencies), - optionalDependencies: (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.optionalDependencies), - private: workspaceManifestJson.private, - workspaces: workspaceManifestJson.workspaces - }; - workspaceLayout.virtualManifestName = virtualDependencyManifest.name; - const virtualDep = {}; - virtualDep[virtualDependencyManifest.name] = virtualDependencyManifest.version; - workspaces[virtualDependencyManifest.name] = { loc: workspacesRoot, manifest: virtualDependencyManifest }; - - // ensure dependencies that should be excluded are stripped from the correct manifest - stripExcluded(cwdIsRoot ? virtualDependencyManifest : workspaces[projectManifestJson.name].manifest); - - pushDeps('workspaces', { workspaces: virtualDep }, { hint: 'workspaces', optional: false }, true); - - const implicitWorkspaceDependencies = (0, (_extends2 || _load_extends()).default)({}, workspaceDependencies); - - for (var _iterator6 = (_constants || _load_constants()).OWNED_DEPENDENCY_TYPES, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { - var _ref6; - - if (_isArray6) { - if (_i6 >= _iterator6.length) break; - _ref6 = _iterator6[_i6++]; - } else { - _i6 = _iterator6.next(); - if (_i6.done) break; - _ref6 = _i6.value; - } - - const type = _ref6; - - for (var _iterator7 = Object.keys(projectManifestJson[type] || {}), _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { - var _ref7; - - if (_isArray7) { - if (_i7 >= _iterator7.length) break; - _ref7 = _iterator7[_i7++]; - } else { - _i7 = _iterator7.next(); - if (_i7.done) break; - _ref7 = _i7.value; - } - - const dependencyName = _ref7; - - delete implicitWorkspaceDependencies[dependencyName]; - } - } - - pushDeps('dependencies', { dependencies: implicitWorkspaceDependencies }, { hint: 'workspaces', optional: false }, true); - } - - break; - } - - // inherit root flat flag - if (manifest.flat) { - _this.flags.flat = true; - } - - return { - requests: [...resolutionDeps, ...deps], - patterns, - manifest, - usedPatterns, - ignorePatterns, - workspaceLayout - }; - })(); - } - - /** - * TODO description - */ - - prepareRequests(requests) { - return requests; - } - - preparePatterns(patterns) { - return patterns; - } - preparePatternsForLinking(patterns, cwdManifest, cwdIsRoot) { - return patterns; - } - - prepareManifests() { - var _this2 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const manifests = yield _this2.config.getRootManifests(); - return manifests; - })(); - } - - bailout(patterns, workspaceLayout) { - var _this3 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // We don't want to skip the audit - it could yield important errors - if (_this3.flags.audit) { - return false; - } - // PNP is so fast that the integrity check isn't pertinent - if (_this3.config.plugnplayEnabled) { - return false; - } - if (_this3.flags.skipIntegrityCheck || _this3.flags.force) { - return false; - } - const lockfileCache = _this3.lockfile.cache; - if (!lockfileCache) { - return false; - } - const lockfileClean = _this3.lockfile.parseResultType === 'success'; - const match = yield _this3.integrityChecker.check(patterns, lockfileCache, _this3.flags, workspaceLayout); - if (_this3.flags.frozenLockfile && (!lockfileClean || match.missingPatterns.length > 0)) { - throw new (_errors || _load_errors()).MessageError(_this3.reporter.lang('frozenLockfileError')); - } - - const haveLockfile = yield (_fs || _load_fs()).exists(path.join(_this3.config.lockfileFolder, (_constants || _load_constants()).LOCKFILE_FILENAME)); - - const lockfileIntegrityPresent = !_this3.lockfile.hasEntriesExistWithoutIntegrity(); - const integrityBailout = lockfileIntegrityPresent || !_this3.config.autoAddIntegrity; - - if (match.integrityMatches && haveLockfile && lockfileClean && integrityBailout) { - _this3.reporter.success(_this3.reporter.lang('upToDate')); - return true; - } - - if (match.integrityFileMissing && haveLockfile) { - // Integrity file missing, force script installations - _this3.scripts.setForce(true); - return false; - } - - if (match.hardRefreshRequired) { - // e.g. node version doesn't match, force script installations - _this3.scripts.setForce(true); - return false; - } - - if (!patterns.length && !match.integrityFileMissing) { - _this3.reporter.success(_this3.reporter.lang('nothingToInstall')); - yield _this3.createEmptyManifestFolders(); - yield _this3.saveLockfileAndIntegrity(patterns, workspaceLayout); - return true; - } - - return false; - })(); - } - - /** - * Produce empty folders for all used root manifests. - */ - - createEmptyManifestFolders() { - var _this4 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - if (_this4.config.modulesFolder) { - // already created - return; - } - - for (var _iterator9 = _this4.rootManifestRegistries, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { - var _ref10; - - if (_isArray9) { - if (_i9 >= _iterator9.length) break; - _ref10 = _iterator9[_i9++]; - } else { - _i9 = _iterator9.next(); - if (_i9.done) break; - _ref10 = _i9.value; - } - - const registryName = _ref10; - const folder = _this4.config.registries[registryName].folder; - - yield (_fs || _load_fs()).mkdirp(path.join(_this4.config.lockfileFolder, folder)); - } - })(); - } - - /** - * TODO description - */ - - markIgnored(patterns) { - for (var _iterator10 = patterns, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { - var _ref11; - - if (_isArray10) { - if (_i10 >= _iterator10.length) break; - _ref11 = _iterator10[_i10++]; - } else { - _i10 = _iterator10.next(); - if (_i10.done) break; - _ref11 = _i10.value; - } - - const pattern = _ref11; - - const manifest = this.resolver.getStrictResolvedPattern(pattern); - const ref = manifest._reference; - invariant(ref, 'expected package reference'); - - // just mark the package as ignored. if the package is used by a required package, the hoister - // will take care of that. - ref.ignore = true; - } - } - - /** - * helper method that gets only recent manifests - * used by global.ls command - */ - getFlattenedDeps() { - var _this5 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - var _ref12 = yield _this5.fetchRequestFromCwd(); - - const depRequests = _ref12.requests, - rawPatterns = _ref12.patterns; - - - yield _this5.resolver.init(depRequests, {}); - - const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this5.resolver.getManifests(), _this5.config); - _this5.resolver.updateManifests(manifests); - - return _this5.flatten(rawPatterns); - })(); - } - - /** - * TODO description - */ - - init() { - var _this6 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.checkUpdate(); - - // warn if we have a shrinkwrap - if (yield (_fs || _load_fs()).exists(path.join(_this6.config.lockfileFolder, (_constants || _load_constants()).NPM_SHRINKWRAP_FILENAME))) { - _this6.reporter.warn(_this6.reporter.lang('shrinkwrapWarning')); - } - - // warn if we have an npm lockfile - if (yield (_fs || _load_fs()).exists(path.join(_this6.config.lockfileFolder, (_constants || _load_constants()).NPM_LOCK_FILENAME))) { - _this6.reporter.warn(_this6.reporter.lang('npmLockfileWarning')); - } - - if (_this6.config.plugnplayEnabled) { - _this6.reporter.info(_this6.reporter.lang('plugnplaySuggestV2L1')); - _this6.reporter.info(_this6.reporter.lang('plugnplaySuggestV2L2')); - } - - let flattenedTopLevelPatterns = []; - const steps = []; - - var _ref13 = yield _this6.fetchRequestFromCwd(); - - const depRequests = _ref13.requests, - rawPatterns = _ref13.patterns, - ignorePatterns = _ref13.ignorePatterns, - workspaceLayout = _ref13.workspaceLayout, - manifest = _ref13.manifest; - - let topLevelPatterns = []; - - const artifacts = yield _this6.integrityChecker.getArtifacts(); - if (artifacts) { - _this6.linker.setArtifacts(artifacts); - _this6.scripts.setArtifacts(artifacts); - } - - if ((_packageCompatibility || _load_packageCompatibility()).shouldCheck(manifest, _this6.flags)) { - steps.push((() => { - var _ref14 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { - _this6.reporter.step(curr, total, _this6.reporter.lang('checkingManifest'), emoji.get('mag')); - yield _this6.checkCompatibility(); - }); - - return function (_x, _x2) { - return _ref14.apply(this, arguments); - }; - })()); - } - - const audit = new (_audit || _load_audit()).default(_this6.config, _this6.reporter, { groups: (_constants || _load_constants()).OWNED_DEPENDENCY_TYPES }); - let auditFoundProblems = false; - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('resolveStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.reporter.step(curr, total, _this6.reporter.lang('resolvingPackages'), emoji.get('mag')); - yield _this6.resolver.init(_this6.prepareRequests(depRequests), { - isFlat: _this6.flags.flat, - isFrozen: _this6.flags.frozenLockfile, - workspaceLayout - }); - topLevelPatterns = _this6.preparePatterns(rawPatterns); - flattenedTopLevelPatterns = yield _this6.flatten(topLevelPatterns); - return { bailout: !_this6.flags.audit && (yield _this6.bailout(topLevelPatterns, workspaceLayout)) }; - })); - }); - - if (_this6.flags.audit) { - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('auditStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.reporter.step(curr, total, _this6.reporter.lang('auditRunning'), emoji.get('mag')); - if (_this6.flags.offline) { - _this6.reporter.warn(_this6.reporter.lang('auditOffline')); - return { bailout: false }; - } - const preparedManifests = yield _this6.prepareManifests(); - // $FlowFixMe - Flow considers `m` in the map operation to be "mixed", so does not recognize `m.object` - const mergedManifest = Object.assign({}, ...Object.values(preparedManifests).map(function (m) { - return m.object; - })); - const auditVulnerabilityCounts = yield audit.performAudit(mergedManifest, _this6.lockfile, _this6.resolver, _this6.linker, topLevelPatterns); - auditFoundProblems = auditVulnerabilityCounts.info || auditVulnerabilityCounts.low || auditVulnerabilityCounts.moderate || auditVulnerabilityCounts.high || auditVulnerabilityCounts.critical; - return { bailout: yield _this6.bailout(topLevelPatterns, workspaceLayout) }; - })); - }); - } - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('fetchStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.markIgnored(ignorePatterns); - _this6.reporter.step(curr, total, _this6.reporter.lang('fetchingPackages'), emoji.get('truck')); - const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this6.resolver.getManifests(), _this6.config); - _this6.resolver.updateManifests(manifests); - yield (_packageCompatibility || _load_packageCompatibility()).check(_this6.resolver.getManifests(), _this6.config, _this6.flags.ignoreEngines); - })); - }); - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('linkStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // remove integrity hash to make this operation atomic - yield _this6.integrityChecker.removeIntegrityFile(); - _this6.reporter.step(curr, total, _this6.reporter.lang('linkingDependencies'), emoji.get('link')); - flattenedTopLevelPatterns = _this6.preparePatternsForLinking(flattenedTopLevelPatterns, manifest, _this6.config.lockfileFolder === _this6.config.cwd); - yield _this6.linker.init(flattenedTopLevelPatterns, workspaceLayout, { - linkDuplicates: _this6.flags.linkDuplicates, - ignoreOptional: _this6.flags.ignoreOptional - }); - })); - }); - - if (_this6.config.plugnplayEnabled) { - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('pnpStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const pnpPath = `${_this6.config.lockfileFolder}/${(_constants || _load_constants()).PNP_FILENAME}`; - - const code = yield (0, (_generatePnpMap || _load_generatePnpMap()).generatePnpMap)(_this6.config, flattenedTopLevelPatterns, { - resolver: _this6.resolver, - reporter: _this6.reporter, - targetPath: pnpPath, - workspaceLayout - }); - - try { - const file = yield (_fs || _load_fs()).readFile(pnpPath); - if (file === code) { - return; - } - } catch (error) {} - - yield (_fs || _load_fs()).writeFile(pnpPath, code); - yield (_fs || _load_fs()).chmod(pnpPath, 0o755); - })); - }); - } - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('buildStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.reporter.step(curr, total, _this6.flags.force ? _this6.reporter.lang('rebuildingPackages') : _this6.reporter.lang('buildingFreshPackages'), emoji.get('hammer')); - - if (_this6.config.ignoreScripts) { - _this6.reporter.warn(_this6.reporter.lang('ignoredScripts')); - } else { - yield _this6.scripts.init(flattenedTopLevelPatterns); - } - })); - }); - - if (_this6.flags.har) { - steps.push((() => { - var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { - const formattedDate = new Date().toISOString().replace(/:/g, '-'); - const filename = `yarn-install_${formattedDate}.har`; - _this6.reporter.step(curr, total, _this6.reporter.lang('savingHar', filename), emoji.get('black_circle_for_record')); - yield _this6.config.requestManager.saveHar(filename); - }); - - return function (_x3, _x4) { - return _ref21.apply(this, arguments); - }; - })()); - } - - if (yield _this6.shouldClean()) { - steps.push((() => { - var _ref22 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { - _this6.reporter.step(curr, total, _this6.reporter.lang('cleaningModules'), emoji.get('recycle')); - yield (0, (_autoclean || _load_autoclean()).clean)(_this6.config, _this6.reporter); - }); - - return function (_x5, _x6) { - return _ref22.apply(this, arguments); - }; - })()); - } - - let currentStep = 0; - for (var _iterator11 = steps, _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { - var _ref23; - - if (_isArray11) { - if (_i11 >= _iterator11.length) break; - _ref23 = _iterator11[_i11++]; - } else { - _i11 = _iterator11.next(); - if (_i11.done) break; - _ref23 = _i11.value; - } - - const step = _ref23; - - const stepResult = yield step(++currentStep, steps.length); - if (stepResult && stepResult.bailout) { - if (_this6.flags.audit) { - audit.summary(); - } - if (auditFoundProblems) { - _this6.reporter.warn(_this6.reporter.lang('auditRunAuditForDetails')); - } - _this6.maybeOutputUpdate(); - return flattenedTopLevelPatterns; - } - } - - // fin! - if (_this6.flags.audit) { - audit.summary(); - } - if (auditFoundProblems) { - _this6.reporter.warn(_this6.reporter.lang('auditRunAuditForDetails')); - } - yield _this6.saveLockfileAndIntegrity(topLevelPatterns, workspaceLayout); - yield _this6.persistChanges(); - _this6.maybeOutputUpdate(); - _this6.config.requestManager.clearCache(); - return flattenedTopLevelPatterns; - })(); - } - - checkCompatibility() { - var _this7 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - var _ref24 = yield _this7.fetchRequestFromCwd(); - - const manifest = _ref24.manifest; - - yield (_packageCompatibility || _load_packageCompatibility()).checkOne(manifest, _this7.config, _this7.flags.ignoreEngines); - })(); - } - - persistChanges() { - var _this8 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // get all the different registry manifests in this folder - const manifests = yield _this8.config.getRootManifests(); - - if (yield _this8.applyChanges(manifests)) { - yield _this8.config.saveRootManifests(manifests); - } - })(); - } - - applyChanges(manifests) { - let hasChanged = false; - - if (this.config.plugnplayPersist) { - const object = manifests.npm.object; - - - if (typeof object.installConfig !== 'object') { - object.installConfig = {}; - } - - if (this.config.plugnplayEnabled && object.installConfig.pnp !== true) { - object.installConfig.pnp = true; - hasChanged = true; - } else if (!this.config.plugnplayEnabled && typeof object.installConfig.pnp !== 'undefined') { - delete object.installConfig.pnp; - hasChanged = true; - } - - if (Object.keys(object.installConfig).length === 0) { - delete object.installConfig; - } - } - - return Promise.resolve(hasChanged); - } - - /** - * Check if we should run the cleaning step. - */ - - shouldClean() { - return (_fs || _load_fs()).exists(path.join(this.config.lockfileFolder, (_constants || _load_constants()).CLEAN_FILENAME)); - } - - /** - * TODO - */ - - flatten(patterns) { - var _this9 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - if (!_this9.flags.flat) { - return patterns; - } - - const flattenedPatterns = []; - - for (var _iterator12 = _this9.resolver.getAllDependencyNamesByLevelOrder(patterns), _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { - var _ref25; - - if (_isArray12) { - if (_i12 >= _iterator12.length) break; - _ref25 = _iterator12[_i12++]; - } else { - _i12 = _iterator12.next(); - if (_i12.done) break; - _ref25 = _i12.value; - } - - const name = _ref25; - - const infos = _this9.resolver.getAllInfoForPackageName(name).filter(function (manifest) { - const ref = manifest._reference; - invariant(ref, 'expected package reference'); - return !ref.ignore; - }); - - if (infos.length === 0) { - continue; - } - - if (infos.length === 1) { - // single version of this package - // take out a single pattern as multiple patterns may have resolved to this package - flattenedPatterns.push(_this9.resolver.patternsByPackage[name][0]); - continue; - } - - const options = infos.map(function (info) { - const ref = info._reference; - invariant(ref, 'expected reference'); - return { - // TODO `and is required by {PARENT}`, - name: _this9.reporter.lang('manualVersionResolutionOption', ref.patterns.join(', '), info.version), - - value: info.version - }; - }); - const versions = infos.map(function (info) { - return info.version; - }); - let version; - - const resolutionVersion = _this9.resolutions[name]; - if (resolutionVersion && versions.indexOf(resolutionVersion) >= 0) { - // use json `resolution` version - version = resolutionVersion; - } else { - version = yield _this9.reporter.select(_this9.reporter.lang('manualVersionResolution', name), _this9.reporter.lang('answer'), options); - _this9.resolutions[name] = version; - } - - flattenedPatterns.push(_this9.resolver.collapseAllVersionsOfPackage(name, version)); - } - - // save resolutions to their appropriate root manifest - if (Object.keys(_this9.resolutions).length) { - const manifests = yield _this9.config.getRootManifests(); - - for (const name in _this9.resolutions) { - const version = _this9.resolutions[name]; - - const patterns = _this9.resolver.patternsByPackage[name]; - if (!patterns) { - continue; - } - - let manifest; - for (var _iterator13 = patterns, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { - var _ref26; - - if (_isArray13) { - if (_i13 >= _iterator13.length) break; - _ref26 = _iterator13[_i13++]; - } else { - _i13 = _iterator13.next(); - if (_i13.done) break; - _ref26 = _i13.value; - } - - const pattern = _ref26; - - manifest = _this9.resolver.getResolvedPattern(pattern); - if (manifest) { - break; - } - } - invariant(manifest, 'expected manifest'); - - const ref = manifest._reference; - invariant(ref, 'expected reference'); - - const object = manifests[ref.registry].object; - object.resolutions = object.resolutions || {}; - object.resolutions[name] = version; - } - - yield _this9.config.saveRootManifests(manifests); - } - - return flattenedPatterns; - })(); - } - - /** - * Remove offline tarballs that are no longer required - */ - - pruneOfflineMirror(lockfile) { - var _this10 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const mirror = _this10.config.getOfflineMirrorPath(); - if (!mirror) { - return; - } - - const requiredTarballs = new Set(); - for (const dependency in lockfile) { - const resolved = lockfile[dependency].resolved; - if (resolved) { - const basename = path.basename(resolved.split('#')[0]); - if (dependency[0] === '@' && basename[0] !== '@') { - requiredTarballs.add(`${dependency.split('/')[0]}-${basename}`); - } - requiredTarballs.add(basename); - } - } - - const mirrorFiles = yield (_fs || _load_fs()).walk(mirror); - for (var _iterator14 = mirrorFiles, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { - var _ref27; - - if (_isArray14) { - if (_i14 >= _iterator14.length) break; - _ref27 = _iterator14[_i14++]; - } else { - _i14 = _iterator14.next(); - if (_i14.done) break; - _ref27 = _i14.value; - } - - const file = _ref27; - - const isTarball = path.extname(file.basename) === '.tgz'; - // if using experimental-pack-script-packages-in-mirror flag, don't unlink prebuilt packages - const hasPrebuiltPackage = file.relative.startsWith('prebuilt/'); - if (isTarball && !hasPrebuiltPackage && !requiredTarballs.has(file.basename)) { - yield (_fs || _load_fs()).unlink(file.absolute); - } - } - })(); - } - - /** - * Save updated integrity and lockfiles. - */ - - saveLockfileAndIntegrity(patterns, workspaceLayout) { - var _this11 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const resolvedPatterns = {}; - Object.keys(_this11.resolver.patterns).forEach(function (pattern) { - if (!workspaceLayout || !workspaceLayout.getManifestByPattern(pattern)) { - resolvedPatterns[pattern] = _this11.resolver.patterns[pattern]; - } - }); - - // TODO this code is duplicated in a few places, need a common way to filter out workspace patterns from lockfile - patterns = patterns.filter(function (p) { - return !workspaceLayout || !workspaceLayout.getManifestByPattern(p); - }); - - const lockfileBasedOnResolver = _this11.lockfile.getLockfile(resolvedPatterns); - - if (_this11.config.pruneOfflineMirror) { - yield _this11.pruneOfflineMirror(lockfileBasedOnResolver); - } - - // write integrity hash - if (!_this11.config.plugnplayEnabled) { - yield _this11.integrityChecker.save(patterns, lockfileBasedOnResolver, _this11.flags, workspaceLayout, _this11.scripts.getArtifacts()); - } - - // --no-lockfile or --pure-lockfile or --frozen-lockfile - if (_this11.flags.lockfile === false || _this11.flags.pureLockfile || _this11.flags.frozenLockfile) { - return; - } - - const lockFileHasAllPatterns = patterns.every(function (p) { - return _this11.lockfile.getLocked(p); - }); - const lockfilePatternsMatch = Object.keys(_this11.lockfile.cache || {}).every(function (p) { - return lockfileBasedOnResolver[p]; - }); - const resolverPatternsAreSameAsInLockfile = Object.keys(lockfileBasedOnResolver).every(function (pattern) { - const manifest = _this11.lockfile.getLocked(pattern); - return manifest && manifest.resolved === lockfileBasedOnResolver[pattern].resolved && deepEqual(manifest.prebuiltVariants, lockfileBasedOnResolver[pattern].prebuiltVariants); - }); - const integrityPatternsAreSameAsInLockfile = Object.keys(lockfileBasedOnResolver).every(function (pattern) { - const existingIntegrityInfo = lockfileBasedOnResolver[pattern].integrity; - if (!existingIntegrityInfo) { - // if this entry does not have an integrity, no need to re-write the lockfile because of it - return true; - } - const manifest = _this11.lockfile.getLocked(pattern); - if (manifest && manifest.integrity) { - const manifestIntegrity = ssri.stringify(manifest.integrity); - return manifestIntegrity === existingIntegrityInfo; - } - return false; - }); - - // remove command is followed by install with force, lockfile will be rewritten in any case then - if (!_this11.flags.force && _this11.lockfile.parseResultType === 'success' && lockFileHasAllPatterns && lockfilePatternsMatch && resolverPatternsAreSameAsInLockfile && integrityPatternsAreSameAsInLockfile && patterns.length) { - return; - } - - // build lockfile location - const loc = path.join(_this11.config.lockfileFolder, (_constants || _load_constants()).LOCKFILE_FILENAME); - - // write lockfile - const lockSource = (0, (_lockfile2 || _load_lockfile2()).stringify)(lockfileBasedOnResolver, false, _this11.config.enableLockfileVersions); - yield (_fs || _load_fs()).writeFilePreservingEol(loc, lockSource); - - _this11._logSuccessSaveLockfile(); - })(); - } - - _logSuccessSaveLockfile() { - this.reporter.success(this.reporter.lang('savedLockfile')); - } - - /** - * Load the dependency graph of the current install. Only does package resolving and wont write to the cwd. - */ - hydrate(ignoreUnusedPatterns) { - var _this12 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const request = yield _this12.fetchRequestFromCwd([], ignoreUnusedPatterns); - const depRequests = request.requests, - rawPatterns = request.patterns, - ignorePatterns = request.ignorePatterns, - workspaceLayout = request.workspaceLayout; - - - yield _this12.resolver.init(depRequests, { - isFlat: _this12.flags.flat, - isFrozen: _this12.flags.frozenLockfile, - workspaceLayout - }); - yield _this12.flatten(rawPatterns); - _this12.markIgnored(ignorePatterns); - - // fetch packages, should hit cache most of the time - const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this12.resolver.getManifests(), _this12.config); - _this12.resolver.updateManifests(manifests); - yield (_packageCompatibility || _load_packageCompatibility()).check(_this12.resolver.getManifests(), _this12.config, _this12.flags.ignoreEngines); - - // expand minimal manifests - for (var _iterator15 = _this12.resolver.getManifests(), _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { - var _ref28; - - if (_isArray15) { - if (_i15 >= _iterator15.length) break; - _ref28 = _iterator15[_i15++]; - } else { - _i15 = _iterator15.next(); - if (_i15.done) break; - _ref28 = _i15.value; - } - - const manifest = _ref28; - - const ref = manifest._reference; - invariant(ref, 'expected reference'); - const type = ref.remote.type; - // link specifier won't ever hit cache - - let loc = ''; - if (type === 'link') { - continue; - } else if (type === 'workspace') { - if (!ref.remote.reference) { - continue; - } - loc = ref.remote.reference; - } else { - loc = _this12.config.generateModuleCachePath(ref); - } - const newPkg = yield _this12.config.readManifest(loc); - yield _this12.resolver.updateManifest(ref, newPkg); - } - - return request; - })(); - } - - /** - * Check for updates every day and output a nag message if there's a newer version. - */ - - checkUpdate() { - if (this.config.nonInteractive) { - // don't show upgrade dialog on CI or non-TTY terminals - return; - } - - // don't check if disabled - if (this.config.getOption('disable-self-update-check')) { - return; - } - - // only check for updates once a day - const lastUpdateCheck = Number(this.config.getOption('lastUpdateCheck')) || 0; - if (lastUpdateCheck && Date.now() - lastUpdateCheck < ONE_DAY) { - return; - } - - // don't bug for updates on tagged releases - if ((_yarnVersion || _load_yarnVersion()).version.indexOf('-') >= 0) { - return; - } - - this._checkUpdate().catch(() => { - // swallow errors - }); - } - - _checkUpdate() { - var _this13 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - let latestVersion = yield _this13.config.requestManager.request({ - url: (_constants || _load_constants()).SELF_UPDATE_VERSION_URL - }); - invariant(typeof latestVersion === 'string', 'expected string'); - latestVersion = latestVersion.trim(); - if (!semver.valid(latestVersion)) { - return; - } - - // ensure we only check for updates periodically - _this13.config.registries.yarn.saveHomeConfig({ - lastUpdateCheck: Date.now() - }); - - if (semver.gt(latestVersion, (_yarnVersion || _load_yarnVersion()).version)) { - const installationMethod = yield (0, (_yarnVersion || _load_yarnVersion()).getInstallationMethod)(); - _this13.maybeOutputUpdate = function () { - _this13.reporter.warn(_this13.reporter.lang('yarnOutdated', latestVersion, (_yarnVersion || _load_yarnVersion()).version)); - - const command = getUpdateCommand(installationMethod); - if (command) { - _this13.reporter.info(_this13.reporter.lang('yarnOutdatedCommand')); - _this13.reporter.command(command); - } else { - const installer = getUpdateInstaller(installationMethod); - if (installer) { - _this13.reporter.info(_this13.reporter.lang('yarnOutdatedInstaller', installer)); - } - } - }; - } - })(); - } - - /** - * Method to override with a possible upgrade message. - */ - - maybeOutputUpdate() {} -} - -exports.Install = Install; -function hasWrapper(commander, args) { - return true; -} - -function setFlags(commander) { - commander.description('Yarn install is used to install all dependencies for a project.'); - commander.usage('install [flags]'); - commander.option('-A, --audit', 'Run vulnerability audit on installed packages'); - commander.option('-g, --global', 'DEPRECATED'); - commander.option('-S, --save', 'DEPRECATED - save package to your `dependencies`'); - commander.option('-D, --save-dev', 'DEPRECATED - save package to your `devDependencies`'); - commander.option('-P, --save-peer', 'DEPRECATED - save package to your `peerDependencies`'); - commander.option('-O, --save-optional', 'DEPRECATED - save package to your `optionalDependencies`'); - commander.option('-E, --save-exact', 'DEPRECATED'); - commander.option('-T, --save-tilde', 'DEPRECATED'); -} - -/***/ }), -/* 35 */ -/***/ (function(module, exports, __webpack_require__) { - -var isObject = __webpack_require__(52); -module.exports = function (it) { - if (!isObject(it)) throw TypeError(it + ' is not an object!'); - return it; -}; - - -/***/ }), -/* 36 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return SubjectSubscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subject; }); -/* unused harmony export AnonymousSubject */ -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Observable__ = __webpack_require__(12); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__Subscriber__ = __webpack_require__(7); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Subscription__ = __webpack_require__(25); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__ = __webpack_require__(189); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__SubjectSubscription__ = __webpack_require__(422); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__internal_symbol_rxSubscriber__ = __webpack_require__(321); -/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ - - - - - - - -var SubjectSubscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](SubjectSubscriber, _super); - function SubjectSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - return _this; - } - return SubjectSubscriber; -}(__WEBPACK_IMPORTED_MODULE_2__Subscriber__["a" /* Subscriber */])); - -var Subject = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](Subject, _super); - function Subject() { - var _this = _super.call(this) || this; - _this.observers = []; - _this.closed = false; - _this.isStopped = false; - _this.hasError = false; - _this.thrownError = null; - return _this; - } - Subject.prototype[__WEBPACK_IMPORTED_MODULE_6__internal_symbol_rxSubscriber__["a" /* rxSubscriber */]] = function () { - return new SubjectSubscriber(this); - }; - Subject.prototype.lift = function (operator) { - var subject = new AnonymousSubject(this, this); - subject.operator = operator; - return subject; - }; - Subject.prototype.next = function (value) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - if (!this.isStopped) { - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].next(value); - } - } - }; - Subject.prototype.error = function (err) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - this.hasError = true; - this.thrownError = err; - this.isStopped = true; - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].error(err); - } - this.observers.length = 0; - }; - Subject.prototype.complete = function () { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - this.isStopped = true; - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].complete(); - } - this.observers.length = 0; - }; - Subject.prototype.unsubscribe = function () { - this.isStopped = true; - this.closed = true; - this.observers = null; - }; - Subject.prototype._trySubscribe = function (subscriber) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - else { - return _super.prototype._trySubscribe.call(this, subscriber); - } - }; - Subject.prototype._subscribe = function (subscriber) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - else if (this.hasError) { - subscriber.error(this.thrownError); - return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; - } - else if (this.isStopped) { - subscriber.complete(); - return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; - } - else { - this.observers.push(subscriber); - return new __WEBPACK_IMPORTED_MODULE_5__SubjectSubscription__["a" /* SubjectSubscription */](this, subscriber); - } - }; - Subject.prototype.asObservable = function () { - var observable = new __WEBPACK_IMPORTED_MODULE_1__Observable__["a" /* Observable */](); - observable.source = this; - return observable; - }; - Subject.create = function (destination, source) { - return new AnonymousSubject(destination, source); - }; - return Subject; -}(__WEBPACK_IMPORTED_MODULE_1__Observable__["a" /* Observable */])); - -var AnonymousSubject = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](AnonymousSubject, _super); - function AnonymousSubject(destination, source) { - var _this = _super.call(this) || this; - _this.destination = destination; - _this.source = source; - return _this; - } - AnonymousSubject.prototype.next = function (value) { - var destination = this.destination; - if (destination && destination.next) { - destination.next(value); - } - }; - AnonymousSubject.prototype.error = function (err) { - var destination = this.destination; - if (destination && destination.error) { - this.destination.error(err); - } - }; - AnonymousSubject.prototype.complete = function () { - var destination = this.destination; - if (destination && destination.complete) { - this.destination.complete(); - } - }; - AnonymousSubject.prototype._subscribe = function (subscriber) { - var source = this.source; - if (source) { - return this.source.subscribe(subscriber); - } - else { - return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; - } - }; - return AnonymousSubject; -}(Subject)); - -//# sourceMappingURL=Subject.js.map - - -/***/ }), -/* 37 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.normalizePattern = normalizePattern; - -/** - * Explode and normalize a pattern into its name and range. - */ - -function normalizePattern(pattern) { - let hasVersion = false; - let range = 'latest'; - let name = pattern; - - // if we're a scope then remove the @ and add it back later - let isScoped = false; - if (name[0] === '@') { - isScoped = true; - name = name.slice(1); - } - - // take first part as the name - const parts = name.split('@'); - if (parts.length > 1) { - name = parts.shift(); - range = parts.join('@'); - - if (range) { - hasVersion = true; - } else { - range = '*'; - } - } - - // add back @ scope suffix - if (isScoped) { - name = `@${name}`; - } - - return { name, range, hasVersion }; -} - -/***/ }), -/* 38 */ -/***/ (function(module, exports, __webpack_require__) { - -/* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_RESULT__;/** - * @license - * Lodash - * Copyright JS Foundation and other contributors - * Released under MIT license - * Based on Underscore.js 1.8.3 - * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - */ -;(function() { - - /** Used as a safe reference for `undefined` in pre-ES5 environments. */ - var undefined; - - /** Used as the semantic version number. */ - var VERSION = '4.17.10'; - - /** Used as the size to enable large array optimizations. */ - var LARGE_ARRAY_SIZE = 200; - - /** Error message constants. */ - var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.', - FUNC_ERROR_TEXT = 'Expected a function'; - - /** Used to stand-in for `undefined` hash values. */ - var HASH_UNDEFINED = '__lodash_hash_undefined__'; - - /** Used as the maximum memoize cache size. */ - var MAX_MEMOIZE_SIZE = 500; - - /** Used as the internal argument placeholder. */ - var PLACEHOLDER = '__lodash_placeholder__'; - - /** Used to compose bitmasks for cloning. */ - var CLONE_DEEP_FLAG = 1, - CLONE_FLAT_FLAG = 2, - CLONE_SYMBOLS_FLAG = 4; - - /** Used to compose bitmasks for value comparisons. */ - var COMPARE_PARTIAL_FLAG = 1, - COMPARE_UNORDERED_FLAG = 2; - - /** Used to compose bitmasks for function metadata. */ - var WRAP_BIND_FLAG = 1, - WRAP_BIND_KEY_FLAG = 2, - WRAP_CURRY_BOUND_FLAG = 4, - WRAP_CURRY_FLAG = 8, - WRAP_CURRY_RIGHT_FLAG = 16, - WRAP_PARTIAL_FLAG = 32, - WRAP_PARTIAL_RIGHT_FLAG = 64, - WRAP_ARY_FLAG = 128, - WRAP_REARG_FLAG = 256, - WRAP_FLIP_FLAG = 512; - - /** Used as default options for `_.truncate`. */ - var DEFAULT_TRUNC_LENGTH = 30, - DEFAULT_TRUNC_OMISSION = '...'; - - /** Used to detect hot functions by number of calls within a span of milliseconds. */ - var HOT_COUNT = 800, - HOT_SPAN = 16; - - /** Used to indicate the type of lazy iteratees. */ - var LAZY_FILTER_FLAG = 1, - LAZY_MAP_FLAG = 2, - LAZY_WHILE_FLAG = 3; - - /** Used as references for various `Number` constants. */ - var INFINITY = 1 / 0, - MAX_SAFE_INTEGER = 9007199254740991, - MAX_INTEGER = 1.7976931348623157e+308, - NAN = 0 / 0; - - /** Used as references for the maximum length and index of an array. */ - var MAX_ARRAY_LENGTH = 4294967295, - MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, - HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; - - /** Used to associate wrap methods with their bit flags. */ - var wrapFlags = [ - ['ary', WRAP_ARY_FLAG], - ['bind', WRAP_BIND_FLAG], - ['bindKey', WRAP_BIND_KEY_FLAG], - ['curry', WRAP_CURRY_FLAG], - ['curryRight', WRAP_CURRY_RIGHT_FLAG], - ['flip', WRAP_FLIP_FLAG], - ['partial', WRAP_PARTIAL_FLAG], - ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], - ['rearg', WRAP_REARG_FLAG] - ]; - - /** `Object#toString` result references. */ - var argsTag = '[object Arguments]', - arrayTag = '[object Array]', - asyncTag = '[object AsyncFunction]', - boolTag = '[object Boolean]', - dateTag = '[object Date]', - domExcTag = '[object DOMException]', - errorTag = '[object Error]', - funcTag = '[object Function]', - genTag = '[object GeneratorFunction]', - mapTag = '[object Map]', - numberTag = '[object Number]', - nullTag = '[object Null]', - objectTag = '[object Object]', - promiseTag = '[object Promise]', - proxyTag = '[object Proxy]', - regexpTag = '[object RegExp]', - setTag = '[object Set]', - stringTag = '[object String]', - symbolTag = '[object Symbol]', - undefinedTag = '[object Undefined]', - weakMapTag = '[object WeakMap]', - weakSetTag = '[object WeakSet]'; - - var arrayBufferTag = '[object ArrayBuffer]', - dataViewTag = '[object DataView]', - float32Tag = '[object Float32Array]', - float64Tag = '[object Float64Array]', - int8Tag = '[object Int8Array]', - int16Tag = '[object Int16Array]', - int32Tag = '[object Int32Array]', - uint8Tag = '[object Uint8Array]', - uint8ClampedTag = '[object Uint8ClampedArray]', - uint16Tag = '[object Uint16Array]', - uint32Tag = '[object Uint32Array]'; - - /** Used to match empty string literals in compiled template source. */ - var reEmptyStringLeading = /\b__p \+= '';/g, - reEmptyStringMiddle = /\b(__p \+=) '' \+/g, - reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; - - /** Used to match HTML entities and HTML characters. */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, - reUnescapedHtml = /[&<>"']/g, - reHasEscapedHtml = RegExp(reEscapedHtml.source), - reHasUnescapedHtml = RegExp(reUnescapedHtml.source); - - /** Used to match template delimiters. */ - var reEscape = /<%-([\s\S]+?)%>/g, - reEvaluate = /<%([\s\S]+?)%>/g, - reInterpolate = /<%=([\s\S]+?)%>/g; - - /** Used to match property names within property paths. */ - var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, - reIsPlainProp = /^\w*$/, - rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; - - /** - * Used to match `RegExp` - * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). - */ - var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, - reHasRegExpChar = RegExp(reRegExpChar.source); - - /** Used to match leading and trailing whitespace. */ - var reTrim = /^\s+|\s+$/g, - reTrimStart = /^\s+/, - reTrimEnd = /\s+$/; - - /** Used to match wrap detail comments. */ - var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, - reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, - reSplitDetails = /,? & /; - - /** Used to match words composed of alphanumeric characters. */ - var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; - - /** Used to match backslashes in property paths. */ - var reEscapeChar = /\\(\\)?/g; - - /** - * Used to match - * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). - */ - var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; - - /** Used to match `RegExp` flags from their coerced string values. */ - var reFlags = /\w*$/; - - /** Used to detect bad signed hexadecimal string values. */ - var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; - - /** Used to detect binary string values. */ - var reIsBinary = /^0b[01]+$/i; - - /** Used to detect host constructors (Safari). */ - var reIsHostCtor = /^\[object .+?Constructor\]$/; - - /** Used to detect octal string values. */ - var reIsOctal = /^0o[0-7]+$/i; - - /** Used to detect unsigned integer values. */ - var reIsUint = /^(?:0|[1-9]\d*)$/; - - /** Used to match Latin Unicode letters (excluding mathematical operators). */ - var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; - - /** Used to ensure capturing order of template delimiters. */ - var reNoMatch = /($^)/; - - /** Used to match unescaped characters in compiled string literals. */ - var reUnescapedString = /['\n\r\u2028\u2029\\]/g; - - /** Used to compose unicode character classes. */ - var rsAstralRange = '\\ud800-\\udfff', - rsComboMarksRange = '\\u0300-\\u036f', - reComboHalfMarksRange = '\\ufe20-\\ufe2f', - rsComboSymbolsRange = '\\u20d0-\\u20ff', - rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, - rsDingbatRange = '\\u2700-\\u27bf', - rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', - rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', - rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', - rsPunctuationRange = '\\u2000-\\u206f', - rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000', - rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', - rsVarRange = '\\ufe0e\\ufe0f', - rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; - - /** Used to compose unicode capture groups. */ - var rsApos = "['\u2019]", - rsAstral = '[' + rsAstralRange + ']', - rsBreak = '[' + rsBreakRange + ']', - rsCombo = '[' + rsComboRange + ']', - rsDigits = '\\d+', - rsDingbat = '[' + rsDingbatRange + ']', - rsLower = '[' + rsLowerRange + ']', - rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', - rsFitz = '\\ud83c[\\udffb-\\udfff]', - rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', - rsNonAstral = '[^' + rsAstralRange + ']', - rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', - rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', - rsUpper = '[' + rsUpperRange + ']', - rsZWJ = '\\u200d'; - - /** Used to compose unicode regexes. */ - var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', - rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', - rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', - rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', - reOptMod = rsModifier + '?', - rsOptVar = '[' + rsVarRange + ']?', - rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', - rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])', - rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])', - rsSeq = rsOptVar + reOptMod + rsOptJoin, - rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, - rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; - - /** Used to match apostrophes. */ - var reApos = RegExp(rsApos, 'g'); - - /** - * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and - * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). - */ - var reComboMark = RegExp(rsCombo, 'g'); - - /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ - var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); - - /** Used to match complex or compound words. */ - var reUnicodeWord = RegExp([ - rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', - rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', - rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, - rsUpper + '+' + rsOptContrUpper, - rsOrdUpper, - rsOrdLower, - rsDigits, - rsEmoji - ].join('|'), 'g'); - - /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ - var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); - - /** Used to detect strings that need a more robust regexp to match words. */ - var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; - - /** Used to assign default `context` object properties. */ - var contextProps = [ - 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', - 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', - 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', - 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', - '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout' - ]; - - /** Used to make template sourceURLs easier to identify. */ - var templateCounter = -1; - - /** Used to identify `toStringTag` values of typed arrays. */ - var typedArrayTags = {}; - typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = - typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = - typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = - typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = - typedArrayTags[uint32Tag] = true; - typedArrayTags[argsTag] = typedArrayTags[arrayTag] = - typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = - typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = - typedArrayTags[errorTag] = typedArrayTags[funcTag] = - typedArrayTags[mapTag] = typedArrayTags[numberTag] = - typedArrayTags[objectTag] = typedArrayTags[regexpTag] = - typedArrayTags[setTag] = typedArrayTags[stringTag] = - typedArrayTags[weakMapTag] = false; - - /** Used to identify `toStringTag` values supported by `_.clone`. */ - var cloneableTags = {}; - cloneableTags[argsTag] = cloneableTags[arrayTag] = - cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = - cloneableTags[boolTag] = cloneableTags[dateTag] = - cloneableTags[float32Tag] = cloneableTags[float64Tag] = - cloneableTags[int8Tag] = cloneableTags[int16Tag] = - cloneableTags[int32Tag] = cloneableTags[mapTag] = - cloneableTags[numberTag] = cloneableTags[objectTag] = - cloneableTags[regexpTag] = cloneableTags[setTag] = - cloneableTags[stringTag] = cloneableTags[symbolTag] = - cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = - cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; - cloneableTags[errorTag] = cloneableTags[funcTag] = - cloneableTags[weakMapTag] = false; - - /** Used to map Latin Unicode letters to basic Latin letters. */ - var deburredLetters = { - // Latin-1 Supplement block. - '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', - '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', - '\xc7': 'C', '\xe7': 'c', - '\xd0': 'D', '\xf0': 'd', - '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', - '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', - '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', - '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', - '\xd1': 'N', '\xf1': 'n', - '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', - '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', - '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', - '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', - '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', - '\xc6': 'Ae', '\xe6': 'ae', - '\xde': 'Th', '\xfe': 'th', - '\xdf': 'ss', - // Latin Extended-A block. - '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', - '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', - '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', - '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', - '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', - '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', - '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', - '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', - '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', - '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', - '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', - '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', - '\u0134': 'J', '\u0135': 'j', - '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', - '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', - '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', - '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', - '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', - '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', - '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', - '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', - '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', - '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', - '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', - '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', - '\u0163': 't', '\u0165': 't', '\u0167': 't', - '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', - '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', - '\u0174': 'W', '\u0175': 'w', - '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', - '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', - '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', - '\u0132': 'IJ', '\u0133': 'ij', - '\u0152': 'Oe', '\u0153': 'oe', - '\u0149': "'n", '\u017f': 's' - }; - - /** Used to map characters to HTML entities. */ - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - - /** Used to map HTML entities to characters. */ - var htmlUnescapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'" - }; - - /** Used to escape characters for inclusion in compiled string literals. */ - var stringEscapes = { - '\\': '\\', - "'": "'", - '\n': 'n', - '\r': 'r', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - /** Built-in method references without a dependency on `root`. */ - var freeParseFloat = parseFloat, - freeParseInt = parseInt; - - /** Detect free variable `global` from Node.js. */ - var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; - - /** Detect free variable `self`. */ - var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - - /** Used as a reference to the global object. */ - var root = freeGlobal || freeSelf || Function('return this')(); - - /** Detect free variable `exports`. */ - var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; - - /** Detect free variable `module`. */ - var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; - - /** Detect the popular CommonJS extension `module.exports`. */ - var moduleExports = freeModule && freeModule.exports === freeExports; - - /** Detect free variable `process` from Node.js. */ - var freeProcess = moduleExports && freeGlobal.process; - - /** Used to access faster Node.js helpers. */ - var nodeUtil = (function() { - try { - // Use `util.types` for Node.js 10+. - var types = freeModule && freeModule.require && freeModule.require('util').types; - - if (types) { - return types; - } - - // Legacy `process.binding('util')` for Node.js < 10. - return freeProcess && freeProcess.binding && freeProcess.binding('util'); - } catch (e) {} - }()); - - /* Node.js helper references. */ - var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, - nodeIsDate = nodeUtil && nodeUtil.isDate, - nodeIsMap = nodeUtil && nodeUtil.isMap, - nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, - nodeIsSet = nodeUtil && nodeUtil.isSet, - nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; - - /*--------------------------------------------------------------------------*/ - - /** - * A faster alternative to `Function#apply`, this function invokes `func` - * with the `this` binding of `thisArg` and the arguments of `args`. - * - * @private - * @param {Function} func The function to invoke. - * @param {*} thisArg The `this` binding of `func`. - * @param {Array} args The arguments to invoke `func` with. - * @returns {*} Returns the result of `func`. - */ - function apply(func, thisArg, args) { - switch (args.length) { - case 0: return func.call(thisArg); - case 1: return func.call(thisArg, args[0]); - case 2: return func.call(thisArg, args[0], args[1]); - case 3: return func.call(thisArg, args[0], args[1], args[2]); - } - return func.apply(thisArg, args); - } - - /** - * A specialized version of `baseAggregator` for arrays. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform keys. - * @param {Object} accumulator The initial aggregated object. - * @returns {Function} Returns `accumulator`. - */ - function arrayAggregator(array, setter, iteratee, accumulator) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - var value = array[index]; - setter(accumulator, value, iteratee(value), array); - } - return accumulator; - } - - /** - * A specialized version of `_.forEach` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. - */ - function arrayEach(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (iteratee(array[index], index, array) === false) { - break; - } - } - return array; - } - - /** - * A specialized version of `_.forEachRight` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. - */ - function arrayEachRight(array, iteratee) { - var length = array == null ? 0 : array.length; - - while (length--) { - if (iteratee(array[length], length, array) === false) { - break; - } - } - return array; - } - - /** - * A specialized version of `_.every` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false`. - */ - function arrayEvery(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (!predicate(array[index], index, array)) { - return false; - } - } - return true; - } - - /** - * A specialized version of `_.filter` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - */ - function arrayFilter(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (predicate(value, index, array)) { - result[resIndex++] = value; - } - } - return result; - } - - /** - * A specialized version of `_.includes` for arrays without support for - * specifying an index to search from. - * - * @private - * @param {Array} [array] The array to inspect. - * @param {*} target The value to search for. - * @returns {boolean} Returns `true` if `target` is found, else `false`. - */ - function arrayIncludes(array, value) { - var length = array == null ? 0 : array.length; - return !!length && baseIndexOf(array, value, 0) > -1; - } - - /** - * This function is like `arrayIncludes` except that it accepts a comparator. - * - * @private - * @param {Array} [array] The array to inspect. - * @param {*} target The value to search for. - * @param {Function} comparator The comparator invoked per element. - * @returns {boolean} Returns `true` if `target` is found, else `false`. - */ - function arrayIncludesWith(array, value, comparator) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (comparator(value, array[index])) { - return true; - } - } - return false; - } - - /** - * A specialized version of `_.map` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ - function arrayMap(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length, - result = Array(length); - - while (++index < length) { - result[index] = iteratee(array[index], index, array); - } - return result; - } - - /** - * Appends the elements of `values` to `array`. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to append. - * @returns {Array} Returns `array`. - */ - function arrayPush(array, values) { - var index = -1, - length = values.length, - offset = array.length; - - while (++index < length) { - array[offset + index] = values[index]; - } - return array; - } - - /** - * A specialized version of `_.reduce` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the first element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduce(array, iteratee, accumulator, initAccum) { - var index = -1, - length = array == null ? 0 : array.length; - - if (initAccum && length) { - accumulator = array[++index]; - } - while (++index < length) { - accumulator = iteratee(accumulator, array[index], index, array); - } - return accumulator; - } - - /** - * A specialized version of `_.reduceRight` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the last element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduceRight(array, iteratee, accumulator, initAccum) { - var length = array == null ? 0 : array.length; - if (initAccum && length) { - accumulator = array[--length]; - } - while (length--) { - accumulator = iteratee(accumulator, array[length], length, array); - } - return accumulator; - } - - /** - * A specialized version of `_.some` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - */ - function arraySome(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (predicate(array[index], index, array)) { - return true; - } - } - return false; - } - - /** - * Gets the size of an ASCII `string`. - * - * @private - * @param {string} string The string inspect. - * @returns {number} Returns the string size. - */ - var asciiSize = baseProperty('length'); - - /** - * Converts an ASCII `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function asciiToArray(string) { - return string.split(''); - } - - /** - * Splits an ASCII `string` into an array of its words. - * - * @private - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. - */ - function asciiWords(string) { - return string.match(reAsciiWord) || []; - } - - /** - * The base implementation of methods like `_.findKey` and `_.findLastKey`, - * without support for iteratee shorthands, which iterates over `collection` - * using `eachFunc`. - * - * @private - * @param {Array|Object} collection The collection to inspect. - * @param {Function} predicate The function invoked per iteration. - * @param {Function} eachFunc The function to iterate over `collection`. - * @returns {*} Returns the found element or its key, else `undefined`. - */ - function baseFindKey(collection, predicate, eachFunc) { - var result; - eachFunc(collection, function(value, key, collection) { - if (predicate(value, key, collection)) { - result = key; - return false; - } - }); - return result; - } - - /** - * The base implementation of `_.findIndex` and `_.findLastIndex` without - * support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} predicate The function invoked per iteration. - * @param {number} fromIndex The index to search from. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseFindIndex(array, predicate, fromIndex, fromRight) { - var length = array.length, - index = fromIndex + (fromRight ? 1 : -1); - - while ((fromRight ? index-- : ++index < length)) { - if (predicate(array[index], index, array)) { - return index; - } - } - return -1; - } - - /** - * The base implementation of `_.indexOf` without `fromIndex` bounds checks. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseIndexOf(array, value, fromIndex) { - return value === value - ? strictIndexOf(array, value, fromIndex) - : baseFindIndex(array, baseIsNaN, fromIndex); - } - - /** - * This function is like `baseIndexOf` except that it accepts a comparator. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @param {Function} comparator The comparator invoked per element. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseIndexOfWith(array, value, fromIndex, comparator) { - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (comparator(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * The base implementation of `_.isNaN` without support for number objects. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. - */ - function baseIsNaN(value) { - return value !== value; - } - - /** - * The base implementation of `_.mean` and `_.meanBy` without support for - * iteratee shorthands. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {number} Returns the mean. - */ - function baseMean(array, iteratee) { - var length = array == null ? 0 : array.length; - return length ? (baseSum(array, iteratee) / length) : NAN; - } - - /** - * The base implementation of `_.property` without support for deep paths. - * - * @private - * @param {string} key The key of the property to get. - * @returns {Function} Returns the new accessor function. - */ - function baseProperty(key) { - return function(object) { - return object == null ? undefined : object[key]; - }; - } - - /** - * The base implementation of `_.propertyOf` without support for deep paths. - * - * @private - * @param {Object} object The object to query. - * @returns {Function} Returns the new accessor function. - */ - function basePropertyOf(object) { - return function(key) { - return object == null ? undefined : object[key]; - }; - } - - /** - * The base implementation of `_.reduce` and `_.reduceRight`, without support - * for iteratee shorthands, which iterates over `collection` using `eachFunc`. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} accumulator The initial value. - * @param {boolean} initAccum Specify using the first or last element of - * `collection` as the initial value. - * @param {Function} eachFunc The function to iterate over `collection`. - * @returns {*} Returns the accumulated value. - */ - function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { - eachFunc(collection, function(value, index, collection) { - accumulator = initAccum - ? (initAccum = false, value) - : iteratee(accumulator, value, index, collection); - }); - return accumulator; - } - - /** - * The base implementation of `_.sortBy` which uses `comparer` to define the - * sort order of `array` and replaces criteria objects with their corresponding - * values. - * - * @private - * @param {Array} array The array to sort. - * @param {Function} comparer The function to define sort order. - * @returns {Array} Returns `array`. - */ - function baseSortBy(array, comparer) { - var length = array.length; - - array.sort(comparer); - while (length--) { - array[length] = array[length].value; - } - return array; - } - - /** - * The base implementation of `_.sum` and `_.sumBy` without support for - * iteratee shorthands. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {number} Returns the sum. - */ - function baseSum(array, iteratee) { - var result, - index = -1, - length = array.length; - - while (++index < length) { - var current = iteratee(array[index]); - if (current !== undefined) { - result = result === undefined ? current : (result + current); - } - } - return result; - } - - /** - * The base implementation of `_.times` without support for iteratee shorthands - * or max array length checks. - * - * @private - * @param {number} n The number of times to invoke `iteratee`. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the array of results. - */ - function baseTimes(n, iteratee) { - var index = -1, - result = Array(n); - - while (++index < n) { - result[index] = iteratee(index); - } - return result; - } - - /** - * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array - * of key-value pairs for `object` corresponding to the property names of `props`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} props The property names to get values for. - * @returns {Object} Returns the key-value pairs. - */ - function baseToPairs(object, props) { - return arrayMap(props, function(key) { - return [key, object[key]]; - }); - } - - /** - * The base implementation of `_.unary` without support for storing metadata. - * - * @private - * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. - */ - function baseUnary(func) { - return function(value) { - return func(value); - }; - } - - /** - * The base implementation of `_.values` and `_.valuesIn` which creates an - * array of `object` property values corresponding to the property names - * of `props`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} props The property names to get values for. - * @returns {Object} Returns the array of property values. - */ - function baseValues(object, props) { - return arrayMap(props, function(key) { - return object[key]; - }); - } - - /** - * Checks if a `cache` value for `key` exists. - * - * @private - * @param {Object} cache The cache to query. - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function cacheHas(cache, key) { - return cache.has(key); - } - - /** - * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol - * that is not found in the character symbols. - * - * @private - * @param {Array} strSymbols The string symbols to inspect. - * @param {Array} chrSymbols The character symbols to find. - * @returns {number} Returns the index of the first unmatched string symbol. - */ - function charsStartIndex(strSymbols, chrSymbols) { - var index = -1, - length = strSymbols.length; - - while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} - return index; - } - - /** - * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol - * that is not found in the character symbols. - * - * @private - * @param {Array} strSymbols The string symbols to inspect. - * @param {Array} chrSymbols The character symbols to find. - * @returns {number} Returns the index of the last unmatched string symbol. - */ - function charsEndIndex(strSymbols, chrSymbols) { - var index = strSymbols.length; - - while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} - return index; - } - - /** - * Gets the number of `placeholder` occurrences in `array`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} placeholder The placeholder to search for. - * @returns {number} Returns the placeholder count. - */ - function countHolders(array, placeholder) { - var length = array.length, - result = 0; - - while (length--) { - if (array[length] === placeholder) { - ++result; - } - } - return result; - } - - /** - * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A - * letters to basic Latin letters. - * - * @private - * @param {string} letter The matched letter to deburr. - * @returns {string} Returns the deburred letter. - */ - var deburrLetter = basePropertyOf(deburredLetters); - - /** - * Used by `_.escape` to convert characters to HTML entities. - * - * @private - * @param {string} chr The matched character to escape. - * @returns {string} Returns the escaped character. - */ - var escapeHtmlChar = basePropertyOf(htmlEscapes); - - /** - * Used by `_.template` to escape characters for inclusion in compiled string literals. - * - * @private - * @param {string} chr The matched character to escape. - * @returns {string} Returns the escaped character. - */ - function escapeStringChar(chr) { - return '\\' + stringEscapes[chr]; - } - - /** - * Gets the value at `key` of `object`. - * - * @private - * @param {Object} [object] The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ - function getValue(object, key) { - return object == null ? undefined : object[key]; - } - - /** - * Checks if `string` contains Unicode symbols. - * - * @private - * @param {string} string The string to inspect. - * @returns {boolean} Returns `true` if a symbol is found, else `false`. - */ - function hasUnicode(string) { - return reHasUnicode.test(string); - } - - /** - * Checks if `string` contains a word composed of Unicode symbols. - * - * @private - * @param {string} string The string to inspect. - * @returns {boolean} Returns `true` if a word is found, else `false`. - */ - function hasUnicodeWord(string) { - return reHasUnicodeWord.test(string); - } - - /** - * Converts `iterator` to an array. - * - * @private - * @param {Object} iterator The iterator to convert. - * @returns {Array} Returns the converted array. - */ - function iteratorToArray(iterator) { - var data, - result = []; - - while (!(data = iterator.next()).done) { - result.push(data.value); - } - return result; - } - - /** - * Converts `map` to its key-value pairs. - * - * @private - * @param {Object} map The map to convert. - * @returns {Array} Returns the key-value pairs. - */ - function mapToArray(map) { - var index = -1, - result = Array(map.size); - - map.forEach(function(value, key) { - result[++index] = [key, value]; - }); - return result; - } - - /** - * Creates a unary function that invokes `func` with its argument transformed. - * - * @private - * @param {Function} func The function to wrap. - * @param {Function} transform The argument transform. - * @returns {Function} Returns the new function. - */ - function overArg(func, transform) { - return function(arg) { - return func(transform(arg)); - }; - } - - /** - * Replaces all `placeholder` elements in `array` with an internal placeholder - * and returns an array of their indexes. - * - * @private - * @param {Array} array The array to modify. - * @param {*} placeholder The placeholder to replace. - * @returns {Array} Returns the new array of placeholder indexes. - */ - function replaceHolders(array, placeholder) { - var index = -1, - length = array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (value === placeholder || value === PLACEHOLDER) { - array[index] = PLACEHOLDER; - result[resIndex++] = index; - } - } - return result; - } - - /** - * Gets the value at `key`, unless `key` is "__proto__". - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ - function safeGet(object, key) { - return key == '__proto__' - ? undefined - : object[key]; - } - - /** - * Converts `set` to an array of its values. - * - * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the values. - */ - function setToArray(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = value; - }); - return result; - } - - /** - * Converts `set` to its value-value pairs. - * - * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the value-value pairs. - */ - function setToPairs(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = [value, value]; - }); - return result; - } - - /** - * A specialized version of `_.indexOf` which performs strict equality - * comparisons of values, i.e. `===`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function strictIndexOf(array, value, fromIndex) { - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; - } - - /** - * A specialized version of `_.lastIndexOf` which performs strict equality - * comparisons of values, i.e. `===`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function strictLastIndexOf(array, value, fromIndex) { - var index = fromIndex + 1; - while (index--) { - if (array[index] === value) { - return index; - } - } - return index; - } - - /** - * Gets the number of symbols in `string`. - * - * @private - * @param {string} string The string to inspect. - * @returns {number} Returns the string size. - */ - function stringSize(string) { - return hasUnicode(string) - ? unicodeSize(string) - : asciiSize(string); - } - - /** - * Converts `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function stringToArray(string) { - return hasUnicode(string) - ? unicodeToArray(string) - : asciiToArray(string); - } - - /** - * Used by `_.unescape` to convert HTML entities to characters. - * - * @private - * @param {string} chr The matched character to unescape. - * @returns {string} Returns the unescaped character. - */ - var unescapeHtmlChar = basePropertyOf(htmlUnescapes); - - /** - * Gets the size of a Unicode `string`. - * - * @private - * @param {string} string The string inspect. - * @returns {number} Returns the string size. - */ - function unicodeSize(string) { - var result = reUnicode.lastIndex = 0; - while (reUnicode.test(string)) { - ++result; - } - return result; - } - - /** - * Converts a Unicode `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function unicodeToArray(string) { - return string.match(reUnicode) || []; - } - - /** - * Splits a Unicode `string` into an array of its words. - * - * @private - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. - */ - function unicodeWords(string) { - return string.match(reUnicodeWord) || []; - } - - /*--------------------------------------------------------------------------*/ - - /** - * Create a new pristine `lodash` function using the `context` object. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Util - * @param {Object} [context=root] The context object. - * @returns {Function} Returns a new `lodash` function. - * @example - * - * _.mixin({ 'foo': _.constant('foo') }); - * - * var lodash = _.runInContext(); - * lodash.mixin({ 'bar': lodash.constant('bar') }); - * - * _.isFunction(_.foo); - * // => true - * _.isFunction(_.bar); - * // => false - * - * lodash.isFunction(lodash.foo); - * // => false - * lodash.isFunction(lodash.bar); - * // => true - * - * // Create a suped-up `defer` in Node.js. - * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; - */ - var runInContext = (function runInContext(context) { - context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps)); - - /** Built-in constructor references. */ - var Array = context.Array, - Date = context.Date, - Error = context.Error, - Function = context.Function, - Math = context.Math, - Object = context.Object, - RegExp = context.RegExp, - String = context.String, - TypeError = context.TypeError; - - /** Used for built-in method references. */ - var arrayProto = Array.prototype, - funcProto = Function.prototype, - objectProto = Object.prototype; - - /** Used to detect overreaching core-js shims. */ - var coreJsData = context['__core-js_shared__']; - - /** Used to resolve the decompiled source of functions. */ - var funcToString = funcProto.toString; - - /** Used to check objects for own properties. */ - var hasOwnProperty = objectProto.hasOwnProperty; - - /** Used to generate unique IDs. */ - var idCounter = 0; - - /** Used to detect methods masquerading as native. */ - var maskSrcKey = (function() { - var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); - return uid ? ('Symbol(src)_1.' + uid) : ''; - }()); - - /** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ - var nativeObjectToString = objectProto.toString; - - /** Used to infer the `Object` constructor. */ - var objectCtorString = funcToString.call(Object); - - /** Used to restore the original `_` reference in `_.noConflict`. */ - var oldDash = root._; - - /** Used to detect if a method is native. */ - var reIsNative = RegExp('^' + - funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' - ); - - /** Built-in value references. */ - var Buffer = moduleExports ? context.Buffer : undefined, - Symbol = context.Symbol, - Uint8Array = context.Uint8Array, - allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, - getPrototype = overArg(Object.getPrototypeOf, Object), - objectCreate = Object.create, - propertyIsEnumerable = objectProto.propertyIsEnumerable, - splice = arrayProto.splice, - spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined, - symIterator = Symbol ? Symbol.iterator : undefined, - symToStringTag = Symbol ? Symbol.toStringTag : undefined; - - var defineProperty = (function() { - try { - var func = getNative(Object, 'defineProperty'); - func({}, '', {}); - return func; - } catch (e) {} - }()); - - /** Mocked built-ins. */ - var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, - ctxNow = Date && Date.now !== root.Date.now && Date.now, - ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout; - - /* Built-in method references for those with the same name as other `lodash` methods. */ - var nativeCeil = Math.ceil, - nativeFloor = Math.floor, - nativeGetSymbols = Object.getOwnPropertySymbols, - nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, - nativeIsFinite = context.isFinite, - nativeJoin = arrayProto.join, - nativeKeys = overArg(Object.keys, Object), - nativeMax = Math.max, - nativeMin = Math.min, - nativeNow = Date.now, - nativeParseInt = context.parseInt, - nativeRandom = Math.random, - nativeReverse = arrayProto.reverse; - - /* Built-in method references that are verified to be native. */ - var DataView = getNative(context, 'DataView'), - Map = getNative(context, 'Map'), - Promise = getNative(context, 'Promise'), - Set = getNative(context, 'Set'), - WeakMap = getNative(context, 'WeakMap'), - nativeCreate = getNative(Object, 'create'); - - /** Used to store function metadata. */ - var metaMap = WeakMap && new WeakMap; - - /** Used to lookup unminified function names. */ - var realNames = {}; - - /** Used to detect maps, sets, and weakmaps. */ - var dataViewCtorString = toSource(DataView), - mapCtorString = toSource(Map), - promiseCtorString = toSource(Promise), - setCtorString = toSource(Set), - weakMapCtorString = toSource(WeakMap); - - /** Used to convert symbols to primitives and strings. */ - var symbolProto = Symbol ? Symbol.prototype : undefined, - symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, - symbolToString = symbolProto ? symbolProto.toString : undefined; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` object which wraps `value` to enable implicit method - * chain sequences. Methods that operate on and return arrays, collections, - * and functions can be chained together. Methods that retrieve a single value - * or may return a primitive value will automatically end the chain sequence - * and return the unwrapped value. Otherwise, the value must be unwrapped - * with `_#value`. - * - * Explicit chain sequences, which must be unwrapped with `_#value`, may be - * enabled using `_.chain`. - * - * The execution of chained methods is lazy, that is, it's deferred until - * `_#value` is implicitly or explicitly called. - * - * Lazy evaluation allows several methods to support shortcut fusion. - * Shortcut fusion is an optimization to merge iteratee calls; this avoids - * the creation of intermediate arrays and can greatly reduce the number of - * iteratee executions. Sections of a chain sequence qualify for shortcut - * fusion if the section is applied to an array and iteratees accept only - * one argument. The heuristic for whether a section qualifies for shortcut - * fusion is subject to change. - * - * Chaining is supported in custom builds as long as the `_#value` method is - * directly or indirectly included in the build. - * - * In addition to lodash methods, wrappers have `Array` and `String` methods. - * - * The wrapper `Array` methods are: - * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` - * - * The wrapper `String` methods are: - * `replace` and `split` - * - * The wrapper methods that support shortcut fusion are: - * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, - * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, - * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` - * - * The chainable wrapper methods are: - * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, - * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, - * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, - * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, - * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, - * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, - * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, - * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, - * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, - * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, - * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, - * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, - * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, - * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, - * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, - * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, - * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, - * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, - * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, - * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, - * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, - * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, - * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, - * `zipObject`, `zipObjectDeep`, and `zipWith` - * - * The wrapper methods that are **not** chainable by default are: - * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, - * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, - * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, - * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, - * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, - * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, - * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, - * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, - * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, - * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, - * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, - * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, - * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, - * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, - * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, - * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, - * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, - * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, - * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, - * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, - * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, - * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, - * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, - * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, - * `upperFirst`, `value`, and `words` - * - * @name _ - * @constructor - * @category Seq - * @param {*} value The value to wrap in a `lodash` instance. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * function square(n) { - * return n * n; - * } - * - * var wrapped = _([1, 2, 3]); - * - * // Returns an unwrapped value. - * wrapped.reduce(_.add); - * // => 6 - * - * // Returns a wrapped value. - * var squares = wrapped.map(square); - * - * _.isArray(squares); - * // => false - * - * _.isArray(squares.value()); - * // => true - */ - function lodash(value) { - if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { - if (value instanceof LodashWrapper) { - return value; - } - if (hasOwnProperty.call(value, '__wrapped__')) { - return wrapperClone(value); - } - } - return new LodashWrapper(value); - } - - /** - * The base implementation of `_.create` without support for assigning - * properties to the created object. - * - * @private - * @param {Object} proto The object to inherit from. - * @returns {Object} Returns the new object. - */ - var baseCreate = (function() { - function object() {} - return function(proto) { - if (!isObject(proto)) { - return {}; - } - if (objectCreate) { - return objectCreate(proto); - } - object.prototype = proto; - var result = new object; - object.prototype = undefined; - return result; - }; - }()); - - /** - * The function whose prototype chain sequence wrappers inherit from. - * - * @private - */ - function baseLodash() { - // No operation performed. - } - - /** - * The base constructor for creating `lodash` wrapper objects. - * - * @private - * @param {*} value The value to wrap. - * @param {boolean} [chainAll] Enable explicit method chain sequences. - */ - function LodashWrapper(value, chainAll) { - this.__wrapped__ = value; - this.__actions__ = []; - this.__chain__ = !!chainAll; - this.__index__ = 0; - this.__values__ = undefined; - } - - /** - * By default, the template delimiters used by lodash are like those in - * embedded Ruby (ERB) as well as ES2015 template strings. Change the - * following template settings to use alternative delimiters. - * - * @static - * @memberOf _ - * @type {Object} - */ - lodash.templateSettings = { - - /** - * Used to detect `data` property values to be HTML-escaped. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'escape': reEscape, - - /** - * Used to detect code to be evaluated. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'evaluate': reEvaluate, - - /** - * Used to detect `data` property values to inject. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'interpolate': reInterpolate, - - /** - * Used to reference the data object in the template text. - * - * @memberOf _.templateSettings - * @type {string} - */ - 'variable': '', - - /** - * Used to import variables into the compiled template. - * - * @memberOf _.templateSettings - * @type {Object} - */ - 'imports': { - - /** - * A reference to the `lodash` function. - * - * @memberOf _.templateSettings.imports - * @type {Function} - */ - '_': lodash - } - }; - - // Ensure wrappers are instances of `baseLodash`. - lodash.prototype = baseLodash.prototype; - lodash.prototype.constructor = lodash; - - LodashWrapper.prototype = baseCreate(baseLodash.prototype); - LodashWrapper.prototype.constructor = LodashWrapper; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation. - * - * @private - * @constructor - * @param {*} value The value to wrap. - */ - function LazyWrapper(value) { - this.__wrapped__ = value; - this.__actions__ = []; - this.__dir__ = 1; - this.__filtered__ = false; - this.__iteratees__ = []; - this.__takeCount__ = MAX_ARRAY_LENGTH; - this.__views__ = []; - } - - /** - * Creates a clone of the lazy wrapper object. - * - * @private - * @name clone - * @memberOf LazyWrapper - * @returns {Object} Returns the cloned `LazyWrapper` object. - */ - function lazyClone() { - var result = new LazyWrapper(this.__wrapped__); - result.__actions__ = copyArray(this.__actions__); - result.__dir__ = this.__dir__; - result.__filtered__ = this.__filtered__; - result.__iteratees__ = copyArray(this.__iteratees__); - result.__takeCount__ = this.__takeCount__; - result.__views__ = copyArray(this.__views__); - return result; - } - - /** - * Reverses the direction of lazy iteration. - * - * @private - * @name reverse - * @memberOf LazyWrapper - * @returns {Object} Returns the new reversed `LazyWrapper` object. - */ - function lazyReverse() { - if (this.__filtered__) { - var result = new LazyWrapper(this); - result.__dir__ = -1; - result.__filtered__ = true; - } else { - result = this.clone(); - result.__dir__ *= -1; - } - return result; - } - - /** - * Extracts the unwrapped value from its lazy wrapper. - * - * @private - * @name value - * @memberOf LazyWrapper - * @returns {*} Returns the unwrapped value. - */ - function lazyValue() { - var array = this.__wrapped__.value(), - dir = this.__dir__, - isArr = isArray(array), - isRight = dir < 0, - arrLength = isArr ? array.length : 0, - view = getView(0, arrLength, this.__views__), - start = view.start, - end = view.end, - length = end - start, - index = isRight ? end : (start - 1), - iteratees = this.__iteratees__, - iterLength = iteratees.length, - resIndex = 0, - takeCount = nativeMin(length, this.__takeCount__); - - if (!isArr || (!isRight && arrLength == length && takeCount == length)) { - return baseWrapperValue(array, this.__actions__); - } - var result = []; - - outer: - while (length-- && resIndex < takeCount) { - index += dir; - - var iterIndex = -1, - value = array[index]; - - while (++iterIndex < iterLength) { - var data = iteratees[iterIndex], - iteratee = data.iteratee, - type = data.type, - computed = iteratee(value); - - if (type == LAZY_MAP_FLAG) { - value = computed; - } else if (!computed) { - if (type == LAZY_FILTER_FLAG) { - continue outer; - } else { - break outer; - } - } - } - result[resIndex++] = value; - } - return result; - } - - // Ensure `LazyWrapper` is an instance of `baseLodash`. - LazyWrapper.prototype = baseCreate(baseLodash.prototype); - LazyWrapper.prototype.constructor = LazyWrapper; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a hash object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Hash(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the hash. - * - * @private - * @name clear - * @memberOf Hash - */ - function hashClear() { - this.__data__ = nativeCreate ? nativeCreate(null) : {}; - this.size = 0; - } - - /** - * Removes `key` and its value from the hash. - * - * @private - * @name delete - * @memberOf Hash - * @param {Object} hash The hash to modify. - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function hashDelete(key) { - var result = this.has(key) && delete this.__data__[key]; - this.size -= result ? 1 : 0; - return result; - } - - /** - * Gets the hash value for `key`. - * - * @private - * @name get - * @memberOf Hash - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function hashGet(key) { - var data = this.__data__; - if (nativeCreate) { - var result = data[key]; - return result === HASH_UNDEFINED ? undefined : result; - } - return hasOwnProperty.call(data, key) ? data[key] : undefined; - } - - /** - * Checks if a hash value for `key` exists. - * - * @private - * @name has - * @memberOf Hash - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function hashHas(key) { - var data = this.__data__; - return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); - } - - /** - * Sets the hash `key` to `value`. - * - * @private - * @name set - * @memberOf Hash - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the hash instance. - */ - function hashSet(key, value) { - var data = this.__data__; - this.size += this.has(key) ? 0 : 1; - data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; - return this; - } - - // Add methods to `Hash`. - Hash.prototype.clear = hashClear; - Hash.prototype['delete'] = hashDelete; - Hash.prototype.get = hashGet; - Hash.prototype.has = hashHas; - Hash.prototype.set = hashSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an list cache object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function ListCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the list cache. - * - * @private - * @name clear - * @memberOf ListCache - */ - function listCacheClear() { - this.__data__ = []; - this.size = 0; - } - - /** - * Removes `key` and its value from the list cache. - * - * @private - * @name delete - * @memberOf ListCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function listCacheDelete(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - return false; - } - var lastIndex = data.length - 1; - if (index == lastIndex) { - data.pop(); - } else { - splice.call(data, index, 1); - } - --this.size; - return true; - } - - /** - * Gets the list cache value for `key`. - * - * @private - * @name get - * @memberOf ListCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function listCacheGet(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - return index < 0 ? undefined : data[index][1]; - } - - /** - * Checks if a list cache value for `key` exists. - * - * @private - * @name has - * @memberOf ListCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function listCacheHas(key) { - return assocIndexOf(this.__data__, key) > -1; - } - - /** - * Sets the list cache `key` to `value`. - * - * @private - * @name set - * @memberOf ListCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the list cache instance. - */ - function listCacheSet(key, value) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - ++this.size; - data.push([key, value]); - } else { - data[index][1] = value; - } - return this; - } - - // Add methods to `ListCache`. - ListCache.prototype.clear = listCacheClear; - ListCache.prototype['delete'] = listCacheDelete; - ListCache.prototype.get = listCacheGet; - ListCache.prototype.has = listCacheHas; - ListCache.prototype.set = listCacheSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a map cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function MapCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the map. - * - * @private - * @name clear - * @memberOf MapCache - */ - function mapCacheClear() { - this.size = 0; - this.__data__ = { - 'hash': new Hash, - 'map': new (Map || ListCache), - 'string': new Hash - }; - } - - /** - * Removes `key` and its value from the map. - * - * @private - * @name delete - * @memberOf MapCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function mapCacheDelete(key) { - var result = getMapData(this, key)['delete'](key); - this.size -= result ? 1 : 0; - return result; - } - - /** - * Gets the map value for `key`. - * - * @private - * @name get - * @memberOf MapCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function mapCacheGet(key) { - return getMapData(this, key).get(key); - } - - /** - * Checks if a map value for `key` exists. - * - * @private - * @name has - * @memberOf MapCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function mapCacheHas(key) { - return getMapData(this, key).has(key); - } - - /** - * Sets the map `key` to `value`. - * - * @private - * @name set - * @memberOf MapCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the map cache instance. - */ - function mapCacheSet(key, value) { - var data = getMapData(this, key), - size = data.size; - - data.set(key, value); - this.size += data.size == size ? 0 : 1; - return this; - } - - // Add methods to `MapCache`. - MapCache.prototype.clear = mapCacheClear; - MapCache.prototype['delete'] = mapCacheDelete; - MapCache.prototype.get = mapCacheGet; - MapCache.prototype.has = mapCacheHas; - MapCache.prototype.set = mapCacheSet; - - /*------------------------------------------------------------------------*/ - - /** - * - * Creates an array cache object to store unique values. - * - * @private - * @constructor - * @param {Array} [values] The values to cache. - */ - function SetCache(values) { - var index = -1, - length = values == null ? 0 : values.length; - - this.__data__ = new MapCache; - while (++index < length) { - this.add(values[index]); - } - } - - /** - * Adds `value` to the array cache. - * - * @private - * @name add - * @memberOf SetCache - * @alias push - * @param {*} value The value to cache. - * @returns {Object} Returns the cache instance. - */ - function setCacheAdd(value) { - this.__data__.set(value, HASH_UNDEFINED); - return this; - } - - /** - * Checks if `value` is in the array cache. - * - * @private - * @name has - * @memberOf SetCache - * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. - */ - function setCacheHas(value) { - return this.__data__.has(value); - } - - // Add methods to `SetCache`. - SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; - SetCache.prototype.has = setCacheHas; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a stack cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Stack(entries) { - var data = this.__data__ = new ListCache(entries); - this.size = data.size; - } - - /** - * Removes all key-value entries from the stack. - * - * @private - * @name clear - * @memberOf Stack - */ - function stackClear() { - this.__data__ = new ListCache; - this.size = 0; - } - - /** - * Removes `key` and its value from the stack. - * - * @private - * @name delete - * @memberOf Stack - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function stackDelete(key) { - var data = this.__data__, - result = data['delete'](key); - - this.size = data.size; - return result; - } - - /** - * Gets the stack value for `key`. - * - * @private - * @name get - * @memberOf Stack - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function stackGet(key) { - return this.__data__.get(key); - } - - /** - * Checks if a stack value for `key` exists. - * - * @private - * @name has - * @memberOf Stack - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function stackHas(key) { - return this.__data__.has(key); - } - - /** - * Sets the stack `key` to `value`. - * - * @private - * @name set - * @memberOf Stack - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the stack cache instance. - */ - function stackSet(key, value) { - var data = this.__data__; - if (data instanceof ListCache) { - var pairs = data.__data__; - if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { - pairs.push([key, value]); - this.size = ++data.size; - return this; - } - data = this.__data__ = new MapCache(pairs); - } - data.set(key, value); - this.size = data.size; - return this; - } - - // Add methods to `Stack`. - Stack.prototype.clear = stackClear; - Stack.prototype['delete'] = stackDelete; - Stack.prototype.get = stackGet; - Stack.prototype.has = stackHas; - Stack.prototype.set = stackSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an array of the enumerable property names of the array-like `value`. - * - * @private - * @param {*} value The value to query. - * @param {boolean} inherited Specify returning inherited property names. - * @returns {Array} Returns the array of property names. - */ - function arrayLikeKeys(value, inherited) { - var isArr = isArray(value), - isArg = !isArr && isArguments(value), - isBuff = !isArr && !isArg && isBuffer(value), - isType = !isArr && !isArg && !isBuff && isTypedArray(value), - skipIndexes = isArr || isArg || isBuff || isType, - result = skipIndexes ? baseTimes(value.length, String) : [], - length = result.length; - - for (var key in value) { - if ((inherited || hasOwnProperty.call(value, key)) && - !(skipIndexes && ( - // Safari 9 has enumerable `arguments.length` in strict mode. - key == 'length' || - // Node.js 0.10 has enumerable non-index properties on buffers. - (isBuff && (key == 'offset' || key == 'parent')) || - // PhantomJS 2 has enumerable non-index properties on typed arrays. - (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || - // Skip index properties. - isIndex(key, length) - ))) { - result.push(key); - } - } - return result; - } - - /** - * A specialized version of `_.sample` for arrays. - * - * @private - * @param {Array} array The array to sample. - * @returns {*} Returns the random element. - */ - function arraySample(array) { - var length = array.length; - return length ? array[baseRandom(0, length - 1)] : undefined; - } - - /** - * A specialized version of `_.sampleSize` for arrays. - * - * @private - * @param {Array} array The array to sample. - * @param {number} n The number of elements to sample. - * @returns {Array} Returns the random elements. - */ - function arraySampleSize(array, n) { - return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); - } - - /** - * A specialized version of `_.shuffle` for arrays. - * - * @private - * @param {Array} array The array to shuffle. - * @returns {Array} Returns the new shuffled array. - */ - function arrayShuffle(array) { - return shuffleSelf(copyArray(array)); - } - - /** - * This function is like `assignValue` except that it doesn't assign - * `undefined` values. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignMergeValue(object, key, value) { - if ((value !== undefined && !eq(object[key], value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } - } - - /** - * Assigns `value` to `key` of `object` if the existing value is not equivalent - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignValue(object, key, value) { - var objValue = object[key]; - if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } - } - - /** - * Gets the index at which the `key` is found in `array` of key-value pairs. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} key The key to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function assocIndexOf(array, key) { - var length = array.length; - while (length--) { - if (eq(array[length][0], key)) { - return length; - } - } - return -1; - } - - /** - * Aggregates elements of `collection` on `accumulator` with keys transformed - * by `iteratee` and values set by `setter`. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform keys. - * @param {Object} accumulator The initial aggregated object. - * @returns {Function} Returns `accumulator`. - */ - function baseAggregator(collection, setter, iteratee, accumulator) { - baseEach(collection, function(value, key, collection) { - setter(accumulator, value, iteratee(value), collection); - }); - return accumulator; - } - - /** - * The base implementation of `_.assign` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssign(object, source) { - return object && copyObject(source, keys(source), object); - } - - /** - * The base implementation of `_.assignIn` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssignIn(object, source) { - return object && copyObject(source, keysIn(source), object); - } - - /** - * The base implementation of `assignValue` and `assignMergeValue` without - * value checks. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function baseAssignValue(object, key, value) { - if (key == '__proto__' && defineProperty) { - defineProperty(object, key, { - 'configurable': true, - 'enumerable': true, - 'value': value, - 'writable': true - }); - } else { - object[key] = value; - } - } - - /** - * The base implementation of `_.at` without support for individual paths. - * - * @private - * @param {Object} object The object to iterate over. - * @param {string[]} paths The property paths to pick. - * @returns {Array} Returns the picked elements. - */ - function baseAt(object, paths) { - var index = -1, - length = paths.length, - result = Array(length), - skip = object == null; - - while (++index < length) { - result[index] = skip ? undefined : get(object, paths[index]); - } - return result; - } - - /** - * The base implementation of `_.clamp` which doesn't coerce arguments. - * - * @private - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - */ - function baseClamp(number, lower, upper) { - if (number === number) { - if (upper !== undefined) { - number = number <= upper ? number : upper; - } - if (lower !== undefined) { - number = number >= lower ? number : lower; - } - } - return number; - } - - /** - * The base implementation of `_.clone` and `_.cloneDeep` which tracks - * traversed objects. - * - * @private - * @param {*} value The value to clone. - * @param {boolean} bitmask The bitmask flags. - * 1 - Deep clone - * 2 - Flatten inherited properties - * 4 - Clone symbols - * @param {Function} [customizer] The function to customize cloning. - * @param {string} [key] The key of `value`. - * @param {Object} [object] The parent object of `value`. - * @param {Object} [stack] Tracks traversed objects and their clone counterparts. - * @returns {*} Returns the cloned value. - */ - function baseClone(value, bitmask, customizer, key, object, stack) { - var result, - isDeep = bitmask & CLONE_DEEP_FLAG, - isFlat = bitmask & CLONE_FLAT_FLAG, - isFull = bitmask & CLONE_SYMBOLS_FLAG; - - if (customizer) { - result = object ? customizer(value, key, object, stack) : customizer(value); - } - if (result !== undefined) { - return result; - } - if (!isObject(value)) { - return value; - } - var isArr = isArray(value); - if (isArr) { - result = initCloneArray(value); - if (!isDeep) { - return copyArray(value, result); - } - } else { - var tag = getTag(value), - isFunc = tag == funcTag || tag == genTag; - - if (isBuffer(value)) { - return cloneBuffer(value, isDeep); - } - if (tag == objectTag || tag == argsTag || (isFunc && !object)) { - result = (isFlat || isFunc) ? {} : initCloneObject(value); - if (!isDeep) { - return isFlat - ? copySymbolsIn(value, baseAssignIn(result, value)) - : copySymbols(value, baseAssign(result, value)); - } - } else { - if (!cloneableTags[tag]) { - return object ? value : {}; - } - result = initCloneByTag(value, tag, isDeep); - } - } - // Check for circular references and return its corresponding clone. - stack || (stack = new Stack); - var stacked = stack.get(value); - if (stacked) { - return stacked; - } - stack.set(value, result); - - if (isSet(value)) { - value.forEach(function(subValue) { - result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack)); - }); - - return result; - } - - if (isMap(value)) { - value.forEach(function(subValue, key) { - result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack)); - }); - - return result; - } - - var keysFunc = isFull - ? (isFlat ? getAllKeysIn : getAllKeys) - : (isFlat ? keysIn : keys); - - var props = isArr ? undefined : keysFunc(value); - arrayEach(props || value, function(subValue, key) { - if (props) { - key = subValue; - subValue = value[key]; - } - // Recursively populate clone (susceptible to call stack limits). - assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack)); - }); - return result; - } - - /** - * The base implementation of `_.conforms` which doesn't clone `source`. - * - * @private - * @param {Object} source The object of property predicates to conform to. - * @returns {Function} Returns the new spec function. - */ - function baseConforms(source) { - var props = keys(source); - return function(object) { - return baseConformsTo(object, source, props); - }; - } - - /** - * The base implementation of `_.conformsTo` which accepts `props` to check. - * - * @private - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - */ - function baseConformsTo(object, source, props) { - var length = props.length; - if (object == null) { - return !length; - } - object = Object(object); - while (length--) { - var key = props[length], - predicate = source[key], - value = object[key]; - - if ((value === undefined && !(key in object)) || !predicate(value)) { - return false; - } - } - return true; - } - - /** - * The base implementation of `_.delay` and `_.defer` which accepts `args` - * to provide to `func`. - * - * @private - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @param {Array} args The arguments to provide to `func`. - * @returns {number|Object} Returns the timer id or timeout object. - */ - function baseDelay(func, wait, args) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - return setTimeout(function() { func.apply(undefined, args); }, wait); - } - - /** - * The base implementation of methods like `_.difference` without support - * for excluding multiple arrays or iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Array} values The values to exclude. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - */ - function baseDifference(array, values, iteratee, comparator) { - var index = -1, - includes = arrayIncludes, - isCommon = true, - length = array.length, - result = [], - valuesLength = values.length; - - if (!length) { - return result; - } - if (iteratee) { - values = arrayMap(values, baseUnary(iteratee)); - } - if (comparator) { - includes = arrayIncludesWith; - isCommon = false; - } - else if (values.length >= LARGE_ARRAY_SIZE) { - includes = cacheHas; - isCommon = false; - values = new SetCache(values); - } - outer: - while (++index < length) { - var value = array[index], - computed = iteratee == null ? value : iteratee(value); - - value = (comparator || value !== 0) ? value : 0; - if (isCommon && computed === computed) { - var valuesIndex = valuesLength; - while (valuesIndex--) { - if (values[valuesIndex] === computed) { - continue outer; - } - } - result.push(value); - } - else if (!includes(values, computed, comparator)) { - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.forEach` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - */ - var baseEach = createBaseEach(baseForOwn); - - /** - * The base implementation of `_.forEachRight` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - */ - var baseEachRight = createBaseEach(baseForOwnRight, true); - - /** - * The base implementation of `_.every` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false` - */ - function baseEvery(collection, predicate) { - var result = true; - baseEach(collection, function(value, index, collection) { - result = !!predicate(value, index, collection); - return result; - }); - return result; - } - - /** - * The base implementation of methods like `_.max` and `_.min` which accepts a - * `comparator` to determine the extremum value. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The iteratee invoked per iteration. - * @param {Function} comparator The comparator used to compare values. - * @returns {*} Returns the extremum value. - */ - function baseExtremum(array, iteratee, comparator) { - var index = -1, - length = array.length; - - while (++index < length) { - var value = array[index], - current = iteratee(value); - - if (current != null && (computed === undefined - ? (current === current && !isSymbol(current)) - : comparator(current, computed) - )) { - var computed = current, - result = value; - } - } - return result; - } - - /** - * The base implementation of `_.fill` without an iteratee call guard. - * - * @private - * @param {Array} array The array to fill. - * @param {*} value The value to fill `array` with. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns `array`. - */ - function baseFill(array, value, start, end) { - var length = array.length; - - start = toInteger(start); - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = (end === undefined || end > length) ? length : toInteger(end); - if (end < 0) { - end += length; - } - end = start > end ? 0 : toLength(end); - while (start < end) { - array[start++] = value; - } - return array; - } - - /** - * The base implementation of `_.filter` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - */ - function baseFilter(collection, predicate) { - var result = []; - baseEach(collection, function(value, index, collection) { - if (predicate(value, index, collection)) { - result.push(value); - } - }); - return result; - } - - /** - * The base implementation of `_.flatten` with support for restricting flattening. - * - * @private - * @param {Array} array The array to flatten. - * @param {number} depth The maximum recursion depth. - * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. - * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. - * @param {Array} [result=[]] The initial result value. - * @returns {Array} Returns the new flattened array. - */ - function baseFlatten(array, depth, predicate, isStrict, result) { - var index = -1, - length = array.length; - - predicate || (predicate = isFlattenable); - result || (result = []); - - while (++index < length) { - var value = array[index]; - if (depth > 0 && predicate(value)) { - if (depth > 1) { - // Recursively flatten arrays (susceptible to call stack limits). - baseFlatten(value, depth - 1, predicate, isStrict, result); - } else { - arrayPush(result, value); - } - } else if (!isStrict) { - result[result.length] = value; - } - } - return result; - } - - /** - * The base implementation of `baseForOwn` which iterates over `object` - * properties returned by `keysFunc` and invokes `iteratee` for each property. - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. - */ - var baseFor = createBaseFor(); - - /** - * This function is like `baseFor` except that it iterates over properties - * in the opposite order. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. - */ - var baseForRight = createBaseFor(true); - - /** - * The base implementation of `_.forOwn` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ - function baseForOwn(object, iteratee) { - return object && baseFor(object, iteratee, keys); - } - - /** - * The base implementation of `_.forOwnRight` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ - function baseForOwnRight(object, iteratee) { - return object && baseForRight(object, iteratee, keys); - } - - /** - * The base implementation of `_.functions` which creates an array of - * `object` function property names filtered from `props`. - * - * @private - * @param {Object} object The object to inspect. - * @param {Array} props The property names to filter. - * @returns {Array} Returns the function names. - */ - function baseFunctions(object, props) { - return arrayFilter(props, function(key) { - return isFunction(object[key]); - }); - } - - /** - * The base implementation of `_.get` without support for default values. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @returns {*} Returns the resolved value. - */ - function baseGet(object, path) { - path = castPath(path, object); - - var index = 0, - length = path.length; - - while (object != null && index < length) { - object = object[toKey(path[index++])]; - } - return (index && index == length) ? object : undefined; - } - - /** - * The base implementation of `getAllKeys` and `getAllKeysIn` which uses - * `keysFunc` and `symbolsFunc` to get the enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Function} keysFunc The function to get the keys of `object`. - * @param {Function} symbolsFunc The function to get the symbols of `object`. - * @returns {Array} Returns the array of property names and symbols. - */ - function baseGetAllKeys(object, keysFunc, symbolsFunc) { - var result = keysFunc(object); - return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); - } - - /** - * The base implementation of `getTag` without fallbacks for buggy environments. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - function baseGetTag(value) { - if (value == null) { - return value === undefined ? undefinedTag : nullTag; - } - return (symToStringTag && symToStringTag in Object(value)) - ? getRawTag(value) - : objectToString(value); - } - - /** - * The base implementation of `_.gt` which doesn't coerce arguments. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - */ - function baseGt(value, other) { - return value > other; - } - - /** - * The base implementation of `_.has` without support for deep paths. - * - * @private - * @param {Object} [object] The object to query. - * @param {Array|string} key The key to check. - * @returns {boolean} Returns `true` if `key` exists, else `false`. - */ - function baseHas(object, key) { - return object != null && hasOwnProperty.call(object, key); - } - - /** - * The base implementation of `_.hasIn` without support for deep paths. - * - * @private - * @param {Object} [object] The object to query. - * @param {Array|string} key The key to check. - * @returns {boolean} Returns `true` if `key` exists, else `false`. - */ - function baseHasIn(object, key) { - return object != null && key in Object(object); - } - - /** - * The base implementation of `_.inRange` which doesn't coerce arguments. - * - * @private - * @param {number} number The number to check. - * @param {number} start The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - */ - function baseInRange(number, start, end) { - return number >= nativeMin(start, end) && number < nativeMax(start, end); - } - - /** - * The base implementation of methods like `_.intersection`, without support - * for iteratee shorthands, that accepts an array of arrays to inspect. - * - * @private - * @param {Array} arrays The arrays to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of shared values. - */ - function baseIntersection(arrays, iteratee, comparator) { - var includes = comparator ? arrayIncludesWith : arrayIncludes, - length = arrays[0].length, - othLength = arrays.length, - othIndex = othLength, - caches = Array(othLength), - maxLength = Infinity, - result = []; - - while (othIndex--) { - var array = arrays[othIndex]; - if (othIndex && iteratee) { - array = arrayMap(array, baseUnary(iteratee)); - } - maxLength = nativeMin(array.length, maxLength); - caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) - ? new SetCache(othIndex && array) - : undefined; - } - array = arrays[0]; - - var index = -1, - seen = caches[0]; - - outer: - while (++index < length && result.length < maxLength) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - value = (comparator || value !== 0) ? value : 0; - if (!(seen - ? cacheHas(seen, computed) - : includes(result, computed, comparator) - )) { - othIndex = othLength; - while (--othIndex) { - var cache = caches[othIndex]; - if (!(cache - ? cacheHas(cache, computed) - : includes(arrays[othIndex], computed, comparator)) - ) { - continue outer; - } - } - if (seen) { - seen.push(computed); - } - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.invert` and `_.invertBy` which inverts - * `object` with values transformed by `iteratee` and set by `setter`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform values. - * @param {Object} accumulator The initial inverted object. - * @returns {Function} Returns `accumulator`. - */ - function baseInverter(object, setter, iteratee, accumulator) { - baseForOwn(object, function(value, key, object) { - setter(accumulator, iteratee(value), key, object); - }); - return accumulator; - } - - /** - * The base implementation of `_.invoke` without support for individual - * method arguments. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {Array} args The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - */ - function baseInvoke(object, path, args) { - path = castPath(path, object); - object = parent(object, path); - var func = object == null ? object : object[toKey(last(path))]; - return func == null ? undefined : apply(func, object, args); - } - - /** - * The base implementation of `_.isArguments`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - */ - function baseIsArguments(value) { - return isObjectLike(value) && baseGetTag(value) == argsTag; - } - - /** - * The base implementation of `_.isArrayBuffer` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - */ - function baseIsArrayBuffer(value) { - return isObjectLike(value) && baseGetTag(value) == arrayBufferTag; - } - - /** - * The base implementation of `_.isDate` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - */ - function baseIsDate(value) { - return isObjectLike(value) && baseGetTag(value) == dateTag; - } - - /** - * The base implementation of `_.isEqual` which supports partial comparisons - * and tracks traversed objects. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {boolean} bitmask The bitmask flags. - * 1 - Unordered comparison - * 2 - Partial comparison - * @param {Function} [customizer] The function to customize comparisons. - * @param {Object} [stack] Tracks traversed `value` and `other` objects. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - */ - function baseIsEqual(value, other, bitmask, customizer, stack) { - if (value === other) { - return true; - } - if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { - return value !== value && other !== other; - } - return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); - } - - /** - * A specialized version of `baseIsEqual` for arrays and objects which performs - * deep comparisons and tracks traversed objects enabling objects with circular - * references to be compared. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} [stack] Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { - var objIsArr = isArray(object), - othIsArr = isArray(other), - objTag = objIsArr ? arrayTag : getTag(object), - othTag = othIsArr ? arrayTag : getTag(other); - - objTag = objTag == argsTag ? objectTag : objTag; - othTag = othTag == argsTag ? objectTag : othTag; - - var objIsObj = objTag == objectTag, - othIsObj = othTag == objectTag, - isSameTag = objTag == othTag; - - if (isSameTag && isBuffer(object)) { - if (!isBuffer(other)) { - return false; - } - objIsArr = true; - objIsObj = false; - } - if (isSameTag && !objIsObj) { - stack || (stack = new Stack); - return (objIsArr || isTypedArray(object)) - ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) - : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); - } - if (!(bitmask & COMPARE_PARTIAL_FLAG)) { - var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), - othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); - - if (objIsWrapped || othIsWrapped) { - var objUnwrapped = objIsWrapped ? object.value() : object, - othUnwrapped = othIsWrapped ? other.value() : other; - - stack || (stack = new Stack); - return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); - } - } - if (!isSameTag) { - return false; - } - stack || (stack = new Stack); - return equalObjects(object, other, bitmask, customizer, equalFunc, stack); - } - - /** - * The base implementation of `_.isMap` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - */ - function baseIsMap(value) { - return isObjectLike(value) && getTag(value) == mapTag; - } - - /** - * The base implementation of `_.isMatch` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Array} matchData The property names, values, and compare flags to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - */ - function baseIsMatch(object, source, matchData, customizer) { - var index = matchData.length, - length = index, - noCustomizer = !customizer; - - if (object == null) { - return !length; - } - object = Object(object); - while (index--) { - var data = matchData[index]; - if ((noCustomizer && data[2]) - ? data[1] !== object[data[0]] - : !(data[0] in object) - ) { - return false; - } - } - while (++index < length) { - data = matchData[index]; - var key = data[0], - objValue = object[key], - srcValue = data[1]; - - if (noCustomizer && data[2]) { - if (objValue === undefined && !(key in object)) { - return false; - } - } else { - var stack = new Stack; - if (customizer) { - var result = customizer(objValue, srcValue, key, object, source, stack); - } - if (!(result === undefined - ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) - : result - )) { - return false; - } - } - } - return true; - } - - /** - * The base implementation of `_.isNative` without bad shim checks. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - */ - function baseIsNative(value) { - if (!isObject(value) || isMasked(value)) { - return false; - } - var pattern = isFunction(value) ? reIsNative : reIsHostCtor; - return pattern.test(toSource(value)); - } - - /** - * The base implementation of `_.isRegExp` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - */ - function baseIsRegExp(value) { - return isObjectLike(value) && baseGetTag(value) == regexpTag; - } - - /** - * The base implementation of `_.isSet` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - */ - function baseIsSet(value) { - return isObjectLike(value) && getTag(value) == setTag; - } - - /** - * The base implementation of `_.isTypedArray` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - */ - function baseIsTypedArray(value) { - return isObjectLike(value) && - isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; - } - - /** - * The base implementation of `_.iteratee`. - * - * @private - * @param {*} [value=_.identity] The value to convert to an iteratee. - * @returns {Function} Returns the iteratee. - */ - function baseIteratee(value) { - // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. - // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. - if (typeof value == 'function') { - return value; - } - if (value == null) { - return identity; - } - if (typeof value == 'object') { - return isArray(value) - ? baseMatchesProperty(value[0], value[1]) - : baseMatches(value); - } - return property(value); - } - - /** - * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeys(object) { - if (!isPrototype(object)) { - return nativeKeys(object); - } - var result = []; - for (var key in Object(object)) { - if (hasOwnProperty.call(object, key) && key != 'constructor') { - result.push(key); - } - } - return result; - } - - /** - * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeysIn(object) { - if (!isObject(object)) { - return nativeKeysIn(object); - } - var isProto = isPrototype(object), - result = []; - - for (var key in object) { - if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { - result.push(key); - } - } - return result; - } - - /** - * The base implementation of `_.lt` which doesn't coerce arguments. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - */ - function baseLt(value, other) { - return value < other; - } - - /** - * The base implementation of `_.map` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ - function baseMap(collection, iteratee) { - var index = -1, - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value, key, collection) { - result[++index] = iteratee(value, key, collection); - }); - return result; - } - - /** - * The base implementation of `_.matches` which doesn't clone `source`. - * - * @private - * @param {Object} source The object of property values to match. - * @returns {Function} Returns the new spec function. - */ - function baseMatches(source) { - var matchData = getMatchData(source); - if (matchData.length == 1 && matchData[0][2]) { - return matchesStrictComparable(matchData[0][0], matchData[0][1]); - } - return function(object) { - return object === source || baseIsMatch(object, source, matchData); - }; - } - - /** - * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. - * - * @private - * @param {string} path The path of the property to get. - * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. - */ - function baseMatchesProperty(path, srcValue) { - if (isKey(path) && isStrictComparable(srcValue)) { - return matchesStrictComparable(toKey(path), srcValue); - } - return function(object) { - var objValue = get(object, path); - return (objValue === undefined && objValue === srcValue) - ? hasIn(object, path) - : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); - }; - } - - /** - * The base implementation of `_.merge` without support for multiple sources. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @param {number} srcIndex The index of `source`. - * @param {Function} [customizer] The function to customize merged values. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - */ - function baseMerge(object, source, srcIndex, customizer, stack) { - if (object === source) { - return; - } - baseFor(source, function(srcValue, key) { - if (isObject(srcValue)) { - stack || (stack = new Stack); - baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); - } - else { - var newValue = customizer - ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack) - : undefined; - - if (newValue === undefined) { - newValue = srcValue; - } - assignMergeValue(object, key, newValue); - } - }, keysIn); - } - - /** - * A specialized version of `baseMerge` for arrays and objects which performs - * deep merges and tracks traversed objects enabling objects with circular - * references to be merged. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @param {string} key The key of the value to merge. - * @param {number} srcIndex The index of `source`. - * @param {Function} mergeFunc The function to merge values. - * @param {Function} [customizer] The function to customize assigned values. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - */ - function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { - var objValue = safeGet(object, key), - srcValue = safeGet(source, key), - stacked = stack.get(srcValue); - - if (stacked) { - assignMergeValue(object, key, stacked); - return; - } - var newValue = customizer - ? customizer(objValue, srcValue, (key + ''), object, source, stack) - : undefined; - - var isCommon = newValue === undefined; - - if (isCommon) { - var isArr = isArray(srcValue), - isBuff = !isArr && isBuffer(srcValue), - isTyped = !isArr && !isBuff && isTypedArray(srcValue); - - newValue = srcValue; - if (isArr || isBuff || isTyped) { - if (isArray(objValue)) { - newValue = objValue; - } - else if (isArrayLikeObject(objValue)) { - newValue = copyArray(objValue); - } - else if (isBuff) { - isCommon = false; - newValue = cloneBuffer(srcValue, true); - } - else if (isTyped) { - isCommon = false; - newValue = cloneTypedArray(srcValue, true); - } - else { - newValue = []; - } - } - else if (isPlainObject(srcValue) || isArguments(srcValue)) { - newValue = objValue; - if (isArguments(objValue)) { - newValue = toPlainObject(objValue); - } - else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { - newValue = initCloneObject(srcValue); - } - } - else { - isCommon = false; - } - } - if (isCommon) { - // Recursively merge objects and arrays (susceptible to call stack limits). - stack.set(srcValue, newValue); - mergeFunc(newValue, srcValue, srcIndex, customizer, stack); - stack['delete'](srcValue); - } - assignMergeValue(object, key, newValue); - } - - /** - * The base implementation of `_.nth` which doesn't coerce arguments. - * - * @private - * @param {Array} array The array to query. - * @param {number} n The index of the element to return. - * @returns {*} Returns the nth element of `array`. - */ - function baseNth(array, n) { - var length = array.length; - if (!length) { - return; - } - n += n < 0 ? length : 0; - return isIndex(n, length) ? array[n] : undefined; - } - - /** - * The base implementation of `_.orderBy` without param guards. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. - * @param {string[]} orders The sort orders of `iteratees`. - * @returns {Array} Returns the new sorted array. - */ - function baseOrderBy(collection, iteratees, orders) { - var index = -1; - iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee())); - - var result = baseMap(collection, function(value, key, collection) { - var criteria = arrayMap(iteratees, function(iteratee) { - return iteratee(value); - }); - return { 'criteria': criteria, 'index': ++index, 'value': value }; - }); - - return baseSortBy(result, function(object, other) { - return compareMultiple(object, other, orders); - }); - } - - /** - * The base implementation of `_.pick` without support for individual - * property identifiers. - * - * @private - * @param {Object} object The source object. - * @param {string[]} paths The property paths to pick. - * @returns {Object} Returns the new object. - */ - function basePick(object, paths) { - return basePickBy(object, paths, function(value, path) { - return hasIn(object, path); - }); - } - - /** - * The base implementation of `_.pickBy` without support for iteratee shorthands. - * - * @private - * @param {Object} object The source object. - * @param {string[]} paths The property paths to pick. - * @param {Function} predicate The function invoked per property. - * @returns {Object} Returns the new object. - */ - function basePickBy(object, paths, predicate) { - var index = -1, - length = paths.length, - result = {}; - - while (++index < length) { - var path = paths[index], - value = baseGet(object, path); - - if (predicate(value, path)) { - baseSet(result, castPath(path, object), value); - } - } - return result; - } - - /** - * A specialized version of `baseProperty` which supports deep paths. - * - * @private - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new accessor function. - */ - function basePropertyDeep(path) { - return function(object) { - return baseGet(object, path); - }; - } - - /** - * The base implementation of `_.pullAllBy` without support for iteratee - * shorthands. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns `array`. - */ - function basePullAll(array, values, iteratee, comparator) { - var indexOf = comparator ? baseIndexOfWith : baseIndexOf, - index = -1, - length = values.length, - seen = array; - - if (array === values) { - values = copyArray(values); - } - if (iteratee) { - seen = arrayMap(array, baseUnary(iteratee)); - } - while (++index < length) { - var fromIndex = 0, - value = values[index], - computed = iteratee ? iteratee(value) : value; - - while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) { - if (seen !== array) { - splice.call(seen, fromIndex, 1); - } - splice.call(array, fromIndex, 1); - } - } - return array; - } - - /** - * The base implementation of `_.pullAt` without support for individual - * indexes or capturing the removed elements. - * - * @private - * @param {Array} array The array to modify. - * @param {number[]} indexes The indexes of elements to remove. - * @returns {Array} Returns `array`. - */ - function basePullAt(array, indexes) { - var length = array ? indexes.length : 0, - lastIndex = length - 1; - - while (length--) { - var index = indexes[length]; - if (length == lastIndex || index !== previous) { - var previous = index; - if (isIndex(index)) { - splice.call(array, index, 1); - } else { - baseUnset(array, index); - } - } - } - return array; - } - - /** - * The base implementation of `_.random` without support for returning - * floating-point numbers. - * - * @private - * @param {number} lower The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the random number. - */ - function baseRandom(lower, upper) { - return lower + nativeFloor(nativeRandom() * (upper - lower + 1)); - } - - /** - * The base implementation of `_.range` and `_.rangeRight` which doesn't - * coerce arguments. - * - * @private - * @param {number} start The start of the range. - * @param {number} end The end of the range. - * @param {number} step The value to increment or decrement by. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the range of numbers. - */ - function baseRange(start, end, step, fromRight) { - var index = -1, - length = nativeMax(nativeCeil((end - start) / (step || 1)), 0), - result = Array(length); - - while (length--) { - result[fromRight ? length : ++index] = start; - start += step; - } - return result; - } - - /** - * The base implementation of `_.repeat` which doesn't coerce arguments. - * - * @private - * @param {string} string The string to repeat. - * @param {number} n The number of times to repeat the string. - * @returns {string} Returns the repeated string. - */ - function baseRepeat(string, n) { - var result = ''; - if (!string || n < 1 || n > MAX_SAFE_INTEGER) { - return result; - } - // Leverage the exponentiation by squaring algorithm for a faster repeat. - // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details. - do { - if (n % 2) { - result += string; - } - n = nativeFloor(n / 2); - if (n) { - string += string; - } - } while (n); - - return result; - } - - /** - * The base implementation of `_.rest` which doesn't validate or coerce arguments. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @returns {Function} Returns the new function. - */ - function baseRest(func, start) { - return setToString(overRest(func, start, identity), func + ''); - } - - /** - * The base implementation of `_.sample`. - * - * @private - * @param {Array|Object} collection The collection to sample. - * @returns {*} Returns the random element. - */ - function baseSample(collection) { - return arraySample(values(collection)); - } - - /** - * The base implementation of `_.sampleSize` without param guards. - * - * @private - * @param {Array|Object} collection The collection to sample. - * @param {number} n The number of elements to sample. - * @returns {Array} Returns the random elements. - */ - function baseSampleSize(collection, n) { - var array = values(collection); - return shuffleSelf(array, baseClamp(n, 0, array.length)); - } - - /** - * The base implementation of `_.set`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ - function baseSet(object, path, value, customizer) { - if (!isObject(object)) { - return object; - } - path = castPath(path, object); - - var index = -1, - length = path.length, - lastIndex = length - 1, - nested = object; - - while (nested != null && ++index < length) { - var key = toKey(path[index]), - newValue = value; - - if (index != lastIndex) { - var objValue = nested[key]; - newValue = customizer ? customizer(objValue, key, nested) : undefined; - if (newValue === undefined) { - newValue = isObject(objValue) - ? objValue - : (isIndex(path[index + 1]) ? [] : {}); - } - } - assignValue(nested, key, newValue); - nested = nested[key]; - } - return object; - } - - /** - * The base implementation of `setData` without support for hot loop shorting. - * - * @private - * @param {Function} func The function to associate metadata with. - * @param {*} data The metadata. - * @returns {Function} Returns `func`. - */ - var baseSetData = !metaMap ? identity : function(func, data) { - metaMap.set(func, data); - return func; - }; - - /** - * The base implementation of `setToString` without support for hot loop shorting. - * - * @private - * @param {Function} func The function to modify. - * @param {Function} string The `toString` result. - * @returns {Function} Returns `func`. - */ - var baseSetToString = !defineProperty ? identity : function(func, string) { - return defineProperty(func, 'toString', { - 'configurable': true, - 'enumerable': false, - 'value': constant(string), - 'writable': true - }); - }; - - /** - * The base implementation of `_.shuffle`. - * - * @private - * @param {Array|Object} collection The collection to shuffle. - * @returns {Array} Returns the new shuffled array. - */ - function baseShuffle(collection) { - return shuffleSelf(values(collection)); - } - - /** - * The base implementation of `_.slice` without an iteratee call guard. - * - * @private - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. - */ - function baseSlice(array, start, end) { - var index = -1, - length = array.length; - - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = end > length ? length : end; - if (end < 0) { - end += length; - } - length = start > end ? 0 : ((end - start) >>> 0); - start >>>= 0; - - var result = Array(length); - while (++index < length) { - result[index] = array[index + start]; - } - return result; - } - - /** - * The base implementation of `_.some` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - */ - function baseSome(collection, predicate) { - var result; - - baseEach(collection, function(value, index, collection) { - result = predicate(value, index, collection); - return !result; - }); - return !!result; - } - - /** - * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which - * performs a binary search of `array` to determine the index at which `value` - * should be inserted into `array` in order to maintain its sort order. - * - * @private - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {boolean} [retHighest] Specify returning the highest qualified index. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - */ - function baseSortedIndex(array, value, retHighest) { - var low = 0, - high = array == null ? low : array.length; - - if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) { - while (low < high) { - var mid = (low + high) >>> 1, - computed = array[mid]; - - if (computed !== null && !isSymbol(computed) && - (retHighest ? (computed <= value) : (computed < value))) { - low = mid + 1; - } else { - high = mid; - } - } - return high; - } - return baseSortedIndexBy(array, value, identity, retHighest); - } - - /** - * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy` - * which invokes `iteratee` for `value` and each element of `array` to compute - * their sort ranking. The iteratee is invoked with one argument; (value). - * - * @private - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} iteratee The iteratee invoked per element. - * @param {boolean} [retHighest] Specify returning the highest qualified index. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - */ - function baseSortedIndexBy(array, value, iteratee, retHighest) { - value = iteratee(value); - - var low = 0, - high = array == null ? 0 : array.length, - valIsNaN = value !== value, - valIsNull = value === null, - valIsSymbol = isSymbol(value), - valIsUndefined = value === undefined; - - while (low < high) { - var mid = nativeFloor((low + high) / 2), - computed = iteratee(array[mid]), - othIsDefined = computed !== undefined, - othIsNull = computed === null, - othIsReflexive = computed === computed, - othIsSymbol = isSymbol(computed); - - if (valIsNaN) { - var setLow = retHighest || othIsReflexive; - } else if (valIsUndefined) { - setLow = othIsReflexive && (retHighest || othIsDefined); - } else if (valIsNull) { - setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull); - } else if (valIsSymbol) { - setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol); - } else if (othIsNull || othIsSymbol) { - setLow = false; - } else { - setLow = retHighest ? (computed <= value) : (computed < value); - } - if (setLow) { - low = mid + 1; - } else { - high = mid; - } - } - return nativeMin(high, MAX_ARRAY_INDEX); - } - - /** - * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without - * support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - */ - function baseSortedUniq(array, iteratee) { - var index = -1, - length = array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - if (!index || !eq(computed, seen)) { - var seen = computed; - result[resIndex++] = value === 0 ? 0 : value; - } - } - return result; - } - - /** - * The base implementation of `_.toNumber` which doesn't ensure correct - * conversions of binary, hexadecimal, or octal string values. - * - * @private - * @param {*} value The value to process. - * @returns {number} Returns the number. - */ - function baseToNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - return +value; - } - - /** - * The base implementation of `_.toString` which doesn't convert nullish - * values to empty strings. - * - * @private - * @param {*} value The value to process. - * @returns {string} Returns the string. - */ - function baseToString(value) { - // Exit early for strings to avoid a performance hit in some environments. - if (typeof value == 'string') { - return value; - } - if (isArray(value)) { - // Recursively convert values (susceptible to call stack limits). - return arrayMap(value, baseToString) + ''; - } - if (isSymbol(value)) { - return symbolToString ? symbolToString.call(value) : ''; - } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; - } - - /** - * The base implementation of `_.uniqBy` without support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new duplicate free array. - */ - function baseUniq(array, iteratee, comparator) { - var index = -1, - includes = arrayIncludes, - length = array.length, - isCommon = true, - result = [], - seen = result; - - if (comparator) { - isCommon = false; - includes = arrayIncludesWith; - } - else if (length >= LARGE_ARRAY_SIZE) { - var set = iteratee ? null : createSet(array); - if (set) { - return setToArray(set); - } - isCommon = false; - includes = cacheHas; - seen = new SetCache; - } - else { - seen = iteratee ? [] : result; - } - outer: - while (++index < length) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - value = (comparator || value !== 0) ? value : 0; - if (isCommon && computed === computed) { - var seenIndex = seen.length; - while (seenIndex--) { - if (seen[seenIndex] === computed) { - continue outer; - } - } - if (iteratee) { - seen.push(computed); - } - result.push(value); - } - else if (!includes(seen, computed, comparator)) { - if (seen !== result) { - seen.push(computed); - } - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.unset`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The property path to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - */ - function baseUnset(object, path) { - path = castPath(path, object); - object = parent(object, path); - return object == null || delete object[toKey(last(path))]; - } - - /** - * The base implementation of `_.update`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to update. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ - function baseUpdate(object, path, updater, customizer) { - return baseSet(object, path, updater(baseGet(object, path)), customizer); - } - - /** - * The base implementation of methods like `_.dropWhile` and `_.takeWhile` - * without support for iteratee shorthands. - * - * @private - * @param {Array} array The array to query. - * @param {Function} predicate The function invoked per iteration. - * @param {boolean} [isDrop] Specify dropping elements instead of taking them. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the slice of `array`. - */ - function baseWhile(array, predicate, isDrop, fromRight) { - var length = array.length, - index = fromRight ? length : -1; - - while ((fromRight ? index-- : ++index < length) && - predicate(array[index], index, array)) {} - - return isDrop - ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length)) - : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index)); - } - - /** - * The base implementation of `wrapperValue` which returns the result of - * performing a sequence of actions on the unwrapped `value`, where each - * successive action is supplied the return value of the previous. - * - * @private - * @param {*} value The unwrapped value. - * @param {Array} actions Actions to perform to resolve the unwrapped value. - * @returns {*} Returns the resolved value. - */ - function baseWrapperValue(value, actions) { - var result = value; - if (result instanceof LazyWrapper) { - result = result.value(); - } - return arrayReduce(actions, function(result, action) { - return action.func.apply(action.thisArg, arrayPush([result], action.args)); - }, result); - } - - /** - * The base implementation of methods like `_.xor`, without support for - * iteratee shorthands, that accepts an array of arrays to inspect. - * - * @private - * @param {Array} arrays The arrays to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of values. - */ - function baseXor(arrays, iteratee, comparator) { - var length = arrays.length; - if (length < 2) { - return length ? baseUniq(arrays[0]) : []; - } - var index = -1, - result = Array(length); - - while (++index < length) { - var array = arrays[index], - othIndex = -1; - - while (++othIndex < length) { - if (othIndex != index) { - result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator); - } - } - } - return baseUniq(baseFlatten(result, 1), iteratee, comparator); - } - - /** - * This base implementation of `_.zipObject` which assigns values using `assignFunc`. - * - * @private - * @param {Array} props The property identifiers. - * @param {Array} values The property values. - * @param {Function} assignFunc The function to assign values. - * @returns {Object} Returns the new object. - */ - function baseZipObject(props, values, assignFunc) { - var index = -1, - length = props.length, - valsLength = values.length, - result = {}; - - while (++index < length) { - var value = index < valsLength ? values[index] : undefined; - assignFunc(result, props[index], value); - } - return result; - } - - /** - * Casts `value` to an empty array if it's not an array like object. - * - * @private - * @param {*} value The value to inspect. - * @returns {Array|Object} Returns the cast array-like object. - */ - function castArrayLikeObject(value) { - return isArrayLikeObject(value) ? value : []; - } - - /** - * Casts `value` to `identity` if it's not a function. - * - * @private - * @param {*} value The value to inspect. - * @returns {Function} Returns cast function. - */ - function castFunction(value) { - return typeof value == 'function' ? value : identity; - } - - /** - * Casts `value` to a path array if it's not one. - * - * @private - * @param {*} value The value to inspect. - * @param {Object} [object] The object to query keys on. - * @returns {Array} Returns the cast property path array. - */ - function castPath(value, object) { - if (isArray(value)) { - return value; - } - return isKey(value, object) ? [value] : stringToPath(toString(value)); - } - - /** - * A `baseRest` alias which can be replaced with `identity` by module - * replacement plugins. - * - * @private - * @type {Function} - * @param {Function} func The function to apply a rest parameter to. - * @returns {Function} Returns the new function. - */ - var castRest = baseRest; - - /** - * Casts `array` to a slice if it's needed. - * - * @private - * @param {Array} array The array to inspect. - * @param {number} start The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the cast slice. - */ - function castSlice(array, start, end) { - var length = array.length; - end = end === undefined ? length : end; - return (!start && end >= length) ? array : baseSlice(array, start, end); - } - - /** - * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout). - * - * @private - * @param {number|Object} id The timer id or timeout object of the timer to clear. - */ - var clearTimeout = ctxClearTimeout || function(id) { - return root.clearTimeout(id); - }; - - /** - * Creates a clone of `buffer`. - * - * @private - * @param {Buffer} buffer The buffer to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Buffer} Returns the cloned buffer. - */ - function cloneBuffer(buffer, isDeep) { - if (isDeep) { - return buffer.slice(); - } - var length = buffer.length, - result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); - - buffer.copy(result); - return result; - } - - /** - * Creates a clone of `arrayBuffer`. - * - * @private - * @param {ArrayBuffer} arrayBuffer The array buffer to clone. - * @returns {ArrayBuffer} Returns the cloned array buffer. - */ - function cloneArrayBuffer(arrayBuffer) { - var result = new arrayBuffer.constructor(arrayBuffer.byteLength); - new Uint8Array(result).set(new Uint8Array(arrayBuffer)); - return result; - } - - /** - * Creates a clone of `dataView`. - * - * @private - * @param {Object} dataView The data view to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned data view. - */ - function cloneDataView(dataView, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; - return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); - } - - /** - * Creates a clone of `regexp`. - * - * @private - * @param {Object} regexp The regexp to clone. - * @returns {Object} Returns the cloned regexp. - */ - function cloneRegExp(regexp) { - var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); - result.lastIndex = regexp.lastIndex; - return result; - } - - /** - * Creates a clone of the `symbol` object. - * - * @private - * @param {Object} symbol The symbol object to clone. - * @returns {Object} Returns the cloned symbol object. - */ - function cloneSymbol(symbol) { - return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; - } - - /** - * Creates a clone of `typedArray`. - * - * @private - * @param {Object} typedArray The typed array to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned typed array. - */ - function cloneTypedArray(typedArray, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; - return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); - } - - /** - * Compares values to sort them in ascending order. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {number} Returns the sort order indicator for `value`. - */ - function compareAscending(value, other) { - if (value !== other) { - var valIsDefined = value !== undefined, - valIsNull = value === null, - valIsReflexive = value === value, - valIsSymbol = isSymbol(value); - - var othIsDefined = other !== undefined, - othIsNull = other === null, - othIsReflexive = other === other, - othIsSymbol = isSymbol(other); - - if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || - (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || - (valIsNull && othIsDefined && othIsReflexive) || - (!valIsDefined && othIsReflexive) || - !valIsReflexive) { - return 1; - } - if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || - (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || - (othIsNull && valIsDefined && valIsReflexive) || - (!othIsDefined && valIsReflexive) || - !othIsReflexive) { - return -1; - } - } - return 0; - } - - /** - * Used by `_.orderBy` to compare multiple properties of a value to another - * and stable sort them. - * - * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, - * specify an order of "desc" for descending or "asc" for ascending sort order - * of corresponding values. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {boolean[]|string[]} orders The order to sort by for each property. - * @returns {number} Returns the sort order indicator for `object`. - */ - function compareMultiple(object, other, orders) { - var index = -1, - objCriteria = object.criteria, - othCriteria = other.criteria, - length = objCriteria.length, - ordersLength = orders.length; - - while (++index < length) { - var result = compareAscending(objCriteria[index], othCriteria[index]); - if (result) { - if (index >= ordersLength) { - return result; - } - var order = orders[index]; - return result * (order == 'desc' ? -1 : 1); - } - } - // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications - // that causes it, under certain circumstances, to provide the same value for - // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 - // for more details. - // - // This also ensures a stable sort in V8 and other engines. - // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. - return object.index - other.index; - } - - /** - * Creates an array that is the composition of partially applied arguments, - * placeholders, and provided arguments into a single array of arguments. - * - * @private - * @param {Array} args The provided arguments. - * @param {Array} partials The arguments to prepend to those provided. - * @param {Array} holders The `partials` placeholder indexes. - * @params {boolean} [isCurried] Specify composing for a curried function. - * @returns {Array} Returns the new array of composed arguments. - */ - function composeArgs(args, partials, holders, isCurried) { - var argsIndex = -1, - argsLength = args.length, - holdersLength = holders.length, - leftIndex = -1, - leftLength = partials.length, - rangeLength = nativeMax(argsLength - holdersLength, 0), - result = Array(leftLength + rangeLength), - isUncurried = !isCurried; - - while (++leftIndex < leftLength) { - result[leftIndex] = partials[leftIndex]; - } - while (++argsIndex < holdersLength) { - if (isUncurried || argsIndex < argsLength) { - result[holders[argsIndex]] = args[argsIndex]; - } - } - while (rangeLength--) { - result[leftIndex++] = args[argsIndex++]; - } - return result; - } - - /** - * This function is like `composeArgs` except that the arguments composition - * is tailored for `_.partialRight`. - * - * @private - * @param {Array} args The provided arguments. - * @param {Array} partials The arguments to append to those provided. - * @param {Array} holders The `partials` placeholder indexes. - * @params {boolean} [isCurried] Specify composing for a curried function. - * @returns {Array} Returns the new array of composed arguments. - */ - function composeArgsRight(args, partials, holders, isCurried) { - var argsIndex = -1, - argsLength = args.length, - holdersIndex = -1, - holdersLength = holders.length, - rightIndex = -1, - rightLength = partials.length, - rangeLength = nativeMax(argsLength - holdersLength, 0), - result = Array(rangeLength + rightLength), - isUncurried = !isCurried; - - while (++argsIndex < rangeLength) { - result[argsIndex] = args[argsIndex]; - } - var offset = argsIndex; - while (++rightIndex < rightLength) { - result[offset + rightIndex] = partials[rightIndex]; - } - while (++holdersIndex < holdersLength) { - if (isUncurried || argsIndex < argsLength) { - result[offset + holders[holdersIndex]] = args[argsIndex++]; - } - } - return result; - } - - /** - * Copies the values of `source` to `array`. - * - * @private - * @param {Array} source The array to copy values from. - * @param {Array} [array=[]] The array to copy values to. - * @returns {Array} Returns `array`. - */ - function copyArray(source, array) { - var index = -1, - length = source.length; - - array || (array = Array(length)); - while (++index < length) { - array[index] = source[index]; - } - return array; - } - - /** - * Copies properties of `source` to `object`. - * - * @private - * @param {Object} source The object to copy properties from. - * @param {Array} props The property identifiers to copy. - * @param {Object} [object={}] The object to copy properties to. - * @param {Function} [customizer] The function to customize copied values. - * @returns {Object} Returns `object`. - */ - function copyObject(source, props, object, customizer) { - var isNew = !object; - object || (object = {}); - - var index = -1, - length = props.length; - - while (++index < length) { - var key = props[index]; - - var newValue = customizer - ? customizer(object[key], source[key], key, object, source) - : undefined; - - if (newValue === undefined) { - newValue = source[key]; - } - if (isNew) { - baseAssignValue(object, key, newValue); - } else { - assignValue(object, key, newValue); - } - } - return object; - } - - /** - * Copies own symbols of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbols(source, object) { - return copyObject(source, getSymbols(source), object); - } - - /** - * Copies own and inherited symbols of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbolsIn(source, object) { - return copyObject(source, getSymbolsIn(source), object); - } - - /** - * Creates a function like `_.groupBy`. - * - * @private - * @param {Function} setter The function to set accumulator values. - * @param {Function} [initializer] The accumulator object initializer. - * @returns {Function} Returns the new aggregator function. - */ - function createAggregator(setter, initializer) { - return function(collection, iteratee) { - var func = isArray(collection) ? arrayAggregator : baseAggregator, - accumulator = initializer ? initializer() : {}; - - return func(collection, setter, getIteratee(iteratee, 2), accumulator); - }; - } - - /** - * Creates a function like `_.assign`. - * - * @private - * @param {Function} assigner The function to assign values. - * @returns {Function} Returns the new assigner function. - */ - function createAssigner(assigner) { - return baseRest(function(object, sources) { - var index = -1, - length = sources.length, - customizer = length > 1 ? sources[length - 1] : undefined, - guard = length > 2 ? sources[2] : undefined; - - customizer = (assigner.length > 3 && typeof customizer == 'function') - ? (length--, customizer) - : undefined; - - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - customizer = length < 3 ? undefined : customizer; - length = 1; - } - object = Object(object); - while (++index < length) { - var source = sources[index]; - if (source) { - assigner(object, source, index, customizer); - } - } - return object; - }); - } - - /** - * Creates a `baseEach` or `baseEachRight` function. - * - * @private - * @param {Function} eachFunc The function to iterate over a collection. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. - */ - function createBaseEach(eachFunc, fromRight) { - return function(collection, iteratee) { - if (collection == null) { - return collection; - } - if (!isArrayLike(collection)) { - return eachFunc(collection, iteratee); - } - var length = collection.length, - index = fromRight ? length : -1, - iterable = Object(collection); - - while ((fromRight ? index-- : ++index < length)) { - if (iteratee(iterable[index], index, iterable) === false) { - break; - } - } - return collection; - }; - } - - /** - * Creates a base function for methods like `_.forIn` and `_.forOwn`. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. - */ - function createBaseFor(fromRight) { - return function(object, iteratee, keysFunc) { - var index = -1, - iterable = Object(object), - props = keysFunc(object), - length = props.length; - - while (length--) { - var key = props[fromRight ? length : ++index]; - if (iteratee(iterable[key], key, iterable) === false) { - break; - } - } - return object; - }; - } - - /** - * Creates a function that wraps `func` to invoke it with the optional `this` - * binding of `thisArg`. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} [thisArg] The `this` binding of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createBind(func, bitmask, thisArg) { - var isBind = bitmask & WRAP_BIND_FLAG, - Ctor = createCtor(func); - - function wrapper() { - var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - return fn.apply(isBind ? thisArg : this, arguments); - } - return wrapper; - } - - /** - * Creates a function like `_.lowerFirst`. - * - * @private - * @param {string} methodName The name of the `String` case method to use. - * @returns {Function} Returns the new case function. - */ - function createCaseFirst(methodName) { - return function(string) { - string = toString(string); - - var strSymbols = hasUnicode(string) - ? stringToArray(string) - : undefined; - - var chr = strSymbols - ? strSymbols[0] - : string.charAt(0); - - var trailing = strSymbols - ? castSlice(strSymbols, 1).join('') - : string.slice(1); - - return chr[methodName]() + trailing; - }; - } - - /** - * Creates a function like `_.camelCase`. - * - * @private - * @param {Function} callback The function to combine each word. - * @returns {Function} Returns the new compounder function. - */ - function createCompounder(callback) { - return function(string) { - return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); - }; - } - - /** - * Creates a function that produces an instance of `Ctor` regardless of - * whether it was invoked as part of a `new` expression or by `call` or `apply`. - * - * @private - * @param {Function} Ctor The constructor to wrap. - * @returns {Function} Returns the new wrapped function. - */ - function createCtor(Ctor) { - return function() { - // Use a `switch` statement to work with class constructors. See - // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist - // for more details. - var args = arguments; - switch (args.length) { - case 0: return new Ctor; - case 1: return new Ctor(args[0]); - case 2: return new Ctor(args[0], args[1]); - case 3: return new Ctor(args[0], args[1], args[2]); - case 4: return new Ctor(args[0], args[1], args[2], args[3]); - case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); - case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); - case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - } - var thisBinding = baseCreate(Ctor.prototype), - result = Ctor.apply(thisBinding, args); - - // Mimic the constructor's `return` behavior. - // See https://es5.github.io/#x13.2.2 for more details. - return isObject(result) ? result : thisBinding; - }; - } - - /** - * Creates a function that wraps `func` to enable currying. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {number} arity The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createCurry(func, bitmask, arity) { - var Ctor = createCtor(func); - - function wrapper() { - var length = arguments.length, - args = Array(length), - index = length, - placeholder = getHolder(wrapper); - - while (index--) { - args[index] = arguments[index]; - } - var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) - ? [] - : replaceHolders(args, placeholder); - - length -= holders.length; - if (length < arity) { - return createRecurry( - func, bitmask, createHybrid, wrapper.placeholder, undefined, - args, holders, undefined, undefined, arity - length); - } - var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - return apply(fn, this, args); - } - return wrapper; - } - - /** - * Creates a `_.find` or `_.findLast` function. - * - * @private - * @param {Function} findIndexFunc The function to find the collection index. - * @returns {Function} Returns the new find function. - */ - function createFind(findIndexFunc) { - return function(collection, predicate, fromIndex) { - var iterable = Object(collection); - if (!isArrayLike(collection)) { - var iteratee = getIteratee(predicate, 3); - collection = keys(collection); - predicate = function(key) { return iteratee(iterable[key], key, iterable); }; - } - var index = findIndexFunc(collection, predicate, fromIndex); - return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined; - }; - } - - /** - * Creates a `_.flow` or `_.flowRight` function. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new flow function. - */ - function createFlow(fromRight) { - return flatRest(function(funcs) { - var length = funcs.length, - index = length, - prereq = LodashWrapper.prototype.thru; - - if (fromRight) { - funcs.reverse(); - } - while (index--) { - var func = funcs[index]; - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (prereq && !wrapper && getFuncName(func) == 'wrapper') { - var wrapper = new LodashWrapper([], true); - } - } - index = wrapper ? index : length; - while (++index < length) { - func = funcs[index]; - - var funcName = getFuncName(func), - data = funcName == 'wrapper' ? getData(func) : undefined; - - if (data && isLaziable(data[0]) && - data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && - !data[4].length && data[9] == 1 - ) { - wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); - } else { - wrapper = (func.length == 1 && isLaziable(func)) - ? wrapper[funcName]() - : wrapper.thru(func); - } - } - return function() { - var args = arguments, - value = args[0]; - - if (wrapper && args.length == 1 && isArray(value)) { - return wrapper.plant(value).value(); - } - var index = 0, - result = length ? funcs[index].apply(this, args) : value; - - while (++index < length) { - result = funcs[index].call(this, result); - } - return result; - }; - }); - } - - /** - * Creates a function that wraps `func` to invoke it with optional `this` - * binding of `thisArg`, partial application, and currying. - * - * @private - * @param {Function|string} func The function or method name to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to prepend to those provided to - * the new function. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [partialsRight] The arguments to append to those provided - * to the new function. - * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { - var isAry = bitmask & WRAP_ARY_FLAG, - isBind = bitmask & WRAP_BIND_FLAG, - isBindKey = bitmask & WRAP_BIND_KEY_FLAG, - isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG), - isFlip = bitmask & WRAP_FLIP_FLAG, - Ctor = isBindKey ? undefined : createCtor(func); - - function wrapper() { - var length = arguments.length, - args = Array(length), - index = length; - - while (index--) { - args[index] = arguments[index]; - } - if (isCurried) { - var placeholder = getHolder(wrapper), - holdersCount = countHolders(args, placeholder); - } - if (partials) { - args = composeArgs(args, partials, holders, isCurried); - } - if (partialsRight) { - args = composeArgsRight(args, partialsRight, holdersRight, isCurried); - } - length -= holdersCount; - if (isCurried && length < arity) { - var newHolders = replaceHolders(args, placeholder); - return createRecurry( - func, bitmask, createHybrid, wrapper.placeholder, thisArg, - args, newHolders, argPos, ary, arity - length - ); - } - var thisBinding = isBind ? thisArg : this, - fn = isBindKey ? thisBinding[func] : func; - - length = args.length; - if (argPos) { - args = reorder(args, argPos); - } else if (isFlip && length > 1) { - args.reverse(); - } - if (isAry && ary < length) { - args.length = ary; - } - if (this && this !== root && this instanceof wrapper) { - fn = Ctor || createCtor(fn); - } - return fn.apply(thisBinding, args); - } - return wrapper; - } - - /** - * Creates a function like `_.invertBy`. - * - * @private - * @param {Function} setter The function to set accumulator values. - * @param {Function} toIteratee The function to resolve iteratees. - * @returns {Function} Returns the new inverter function. - */ - function createInverter(setter, toIteratee) { - return function(object, iteratee) { - return baseInverter(object, setter, toIteratee(iteratee), {}); - }; - } - - /** - * Creates a function that performs a mathematical operation on two values. - * - * @private - * @param {Function} operator The function to perform the operation. - * @param {number} [defaultValue] The value used for `undefined` arguments. - * @returns {Function} Returns the new mathematical operation function. - */ - function createMathOperation(operator, defaultValue) { - return function(value, other) { - var result; - if (value === undefined && other === undefined) { - return defaultValue; - } - if (value !== undefined) { - result = value; - } - if (other !== undefined) { - if (result === undefined) { - return other; - } - if (typeof value == 'string' || typeof other == 'string') { - value = baseToString(value); - other = baseToString(other); - } else { - value = baseToNumber(value); - other = baseToNumber(other); - } - result = operator(value, other); - } - return result; - }; - } - - /** - * Creates a function like `_.over`. - * - * @private - * @param {Function} arrayFunc The function to iterate over iteratees. - * @returns {Function} Returns the new over function. - */ - function createOver(arrayFunc) { - return flatRest(function(iteratees) { - iteratees = arrayMap(iteratees, baseUnary(getIteratee())); - return baseRest(function(args) { - var thisArg = this; - return arrayFunc(iteratees, function(iteratee) { - return apply(iteratee, thisArg, args); - }); - }); - }); - } - - /** - * Creates the padding for `string` based on `length`. The `chars` string - * is truncated if the number of characters exceeds `length`. - * - * @private - * @param {number} length The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padding for `string`. - */ - function createPadding(length, chars) { - chars = chars === undefined ? ' ' : baseToString(chars); - - var charsLength = chars.length; - if (charsLength < 2) { - return charsLength ? baseRepeat(chars, length) : chars; - } - var result = baseRepeat(chars, nativeCeil(length / stringSize(chars))); - return hasUnicode(chars) - ? castSlice(stringToArray(result), 0, length).join('') - : result.slice(0, length); - } - - /** - * Creates a function that wraps `func` to invoke it with the `this` binding - * of `thisArg` and `partials` prepended to the arguments it receives. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} thisArg The `this` binding of `func`. - * @param {Array} partials The arguments to prepend to those provided to - * the new function. - * @returns {Function} Returns the new wrapped function. - */ - function createPartial(func, bitmask, thisArg, partials) { - var isBind = bitmask & WRAP_BIND_FLAG, - Ctor = createCtor(func); - - function wrapper() { - var argsIndex = -1, - argsLength = arguments.length, - leftIndex = -1, - leftLength = partials.length, - args = Array(leftLength + argsLength), - fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - - while (++leftIndex < leftLength) { - args[leftIndex] = partials[leftIndex]; - } - while (argsLength--) { - args[leftIndex++] = arguments[++argsIndex]; - } - return apply(fn, isBind ? thisArg : this, args); - } - return wrapper; - } - - /** - * Creates a `_.range` or `_.rangeRight` function. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new range function. - */ - function createRange(fromRight) { - return function(start, end, step) { - if (step && typeof step != 'number' && isIterateeCall(start, end, step)) { - end = step = undefined; - } - // Ensure the sign of `-0` is preserved. - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - step = step === undefined ? (start < end ? 1 : -1) : toFinite(step); - return baseRange(start, end, step, fromRight); - }; - } - - /** - * Creates a function that performs a relational operation on two values. - * - * @private - * @param {Function} operator The function to perform the operation. - * @returns {Function} Returns the new relational operation function. - */ - function createRelationalOperation(operator) { - return function(value, other) { - if (!(typeof value == 'string' && typeof other == 'string')) { - value = toNumber(value); - other = toNumber(other); - } - return operator(value, other); - }; - } - - /** - * Creates a function that wraps `func` to continue currying. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {Function} wrapFunc The function to create the `func` wrapper. - * @param {*} placeholder The placeholder value. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to prepend to those provided to - * the new function. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { - var isCurry = bitmask & WRAP_CURRY_FLAG, - newHolders = isCurry ? holders : undefined, - newHoldersRight = isCurry ? undefined : holders, - newPartials = isCurry ? partials : undefined, - newPartialsRight = isCurry ? undefined : partials; - - bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG); - bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG); - - if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) { - bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG); - } - var newData = [ - func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, - newHoldersRight, argPos, ary, arity - ]; - - var result = wrapFunc.apply(undefined, newData); - if (isLaziable(func)) { - setData(result, newData); - } - result.placeholder = placeholder; - return setWrapToString(result, func, bitmask); - } - - /** - * Creates a function like `_.round`. - * - * @private - * @param {string} methodName The name of the `Math` method to use when rounding. - * @returns {Function} Returns the new round function. - */ - function createRound(methodName) { - var func = Math[methodName]; - return function(number, precision) { - number = toNumber(number); - precision = precision == null ? 0 : nativeMin(toInteger(precision), 292); - if (precision) { - // Shift with exponential notation to avoid floating-point issues. - // See [MDN](https://mdn.io/round#Examples) for more details. - var pair = (toString(number) + 'e').split('e'), - value = func(pair[0] + 'e' + (+pair[1] + precision)); - - pair = (toString(value) + 'e').split('e'); - return +(pair[0] + 'e' + (+pair[1] - precision)); - } - return func(number); - }; - } - - /** - * Creates a set object of `values`. - * - * @private - * @param {Array} values The values to add to the set. - * @returns {Object} Returns the new set. - */ - var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) { - return new Set(values); - }; - - /** - * Creates a `_.toPairs` or `_.toPairsIn` function. - * - * @private - * @param {Function} keysFunc The function to get the keys of a given object. - * @returns {Function} Returns the new pairs function. - */ - function createToPairs(keysFunc) { - return function(object) { - var tag = getTag(object); - if (tag == mapTag) { - return mapToArray(object); - } - if (tag == setTag) { - return setToPairs(object); - } - return baseToPairs(object, keysFunc(object)); - }; - } - - /** - * Creates a function that either curries or invokes `func` with optional - * `this` binding and partially applied arguments. - * - * @private - * @param {Function|string} func The function or method name to wrap. - * @param {number} bitmask The bitmask flags. - * 1 - `_.bind` - * 2 - `_.bindKey` - * 4 - `_.curry` or `_.curryRight` of a bound function - * 8 - `_.curry` - * 16 - `_.curryRight` - * 32 - `_.partial` - * 64 - `_.partialRight` - * 128 - `_.rearg` - * 256 - `_.ary` - * 512 - `_.flip` - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to be partially applied. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { - var isBindKey = bitmask & WRAP_BIND_KEY_FLAG; - if (!isBindKey && typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - var length = partials ? partials.length : 0; - if (!length) { - bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG); - partials = holders = undefined; - } - ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); - arity = arity === undefined ? arity : toInteger(arity); - length -= holders ? holders.length : 0; - - if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) { - var partialsRight = partials, - holdersRight = holders; - - partials = holders = undefined; - } - var data = isBindKey ? undefined : getData(func); - - var newData = [ - func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, - argPos, ary, arity - ]; - - if (data) { - mergeData(newData, data); - } - func = newData[0]; - bitmask = newData[1]; - thisArg = newData[2]; - partials = newData[3]; - holders = newData[4]; - arity = newData[9] = newData[9] === undefined - ? (isBindKey ? 0 : func.length) - : nativeMax(newData[9] - length, 0); - - if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) { - bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG); - } - if (!bitmask || bitmask == WRAP_BIND_FLAG) { - var result = createBind(func, bitmask, thisArg); - } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) { - result = createCurry(func, bitmask, arity); - } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) { - result = createPartial(func, bitmask, thisArg, partials); - } else { - result = createHybrid.apply(undefined, newData); - } - var setter = data ? baseSetData : setData; - return setWrapToString(setter(result, newData), func, bitmask); - } - - /** - * Used by `_.defaults` to customize its `_.assignIn` use to assign properties - * of source objects to the destination object for all destination properties - * that resolve to `undefined`. - * - * @private - * @param {*} objValue The destination value. - * @param {*} srcValue The source value. - * @param {string} key The key of the property to assign. - * @param {Object} object The parent object of `objValue`. - * @returns {*} Returns the value to assign. - */ - function customDefaultsAssignIn(objValue, srcValue, key, object) { - if (objValue === undefined || - (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { - return srcValue; - } - return objValue; - } - - /** - * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source - * objects into destination objects that are passed thru. - * - * @private - * @param {*} objValue The destination value. - * @param {*} srcValue The source value. - * @param {string} key The key of the property to merge. - * @param {Object} object The parent object of `objValue`. - * @param {Object} source The parent object of `srcValue`. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - * @returns {*} Returns the value to assign. - */ - function customDefaultsMerge(objValue, srcValue, key, object, source, stack) { - if (isObject(objValue) && isObject(srcValue)) { - // Recursively merge objects and arrays (susceptible to call stack limits). - stack.set(srcValue, objValue); - baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack); - stack['delete'](srcValue); - } - return objValue; - } - - /** - * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain - * objects. - * - * @private - * @param {*} value The value to inspect. - * @param {string} key The key of the property to inspect. - * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`. - */ - function customOmitClone(value) { - return isPlainObject(value) ? undefined : value; - } - - /** - * A specialized version of `baseIsEqualDeep` for arrays with support for - * partial deep comparisons. - * - * @private - * @param {Array} array The array to compare. - * @param {Array} other The other array to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `array` and `other` objects. - * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. - */ - function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { - var isPartial = bitmask & COMPARE_PARTIAL_FLAG, - arrLength = array.length, - othLength = other.length; - - if (arrLength != othLength && !(isPartial && othLength > arrLength)) { - return false; - } - // Assume cyclic values are equal. - var stacked = stack.get(array); - if (stacked && stack.get(other)) { - return stacked == other; - } - var index = -1, - result = true, - seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined; - - stack.set(array, other); - stack.set(other, array); - - // Ignore non-index properties. - while (++index < arrLength) { - var arrValue = array[index], - othValue = other[index]; - - if (customizer) { - var compared = isPartial - ? customizer(othValue, arrValue, index, other, array, stack) - : customizer(arrValue, othValue, index, array, other, stack); - } - if (compared !== undefined) { - if (compared) { - continue; - } - result = false; - break; - } - // Recursively compare arrays (susceptible to call stack limits). - if (seen) { - if (!arraySome(other, function(othValue, othIndex) { - if (!cacheHas(seen, othIndex) && - (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { - return seen.push(othIndex); - } - })) { - result = false; - break; - } - } else if (!( - arrValue === othValue || - equalFunc(arrValue, othValue, bitmask, customizer, stack) - )) { - result = false; - break; - } - } - stack['delete'](array); - stack['delete'](other); - return result; - } - - /** - * A specialized version of `baseIsEqualDeep` for comparing objects of - * the same `toStringTag`. - * - * **Note:** This function only supports comparing values with tags of - * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {string} tag The `toStringTag` of the objects to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { - switch (tag) { - case dataViewTag: - if ((object.byteLength != other.byteLength) || - (object.byteOffset != other.byteOffset)) { - return false; - } - object = object.buffer; - other = other.buffer; - - case arrayBufferTag: - if ((object.byteLength != other.byteLength) || - !equalFunc(new Uint8Array(object), new Uint8Array(other))) { - return false; - } - return true; - - case boolTag: - case dateTag: - case numberTag: - // Coerce booleans to `1` or `0` and dates to milliseconds. - // Invalid dates are coerced to `NaN`. - return eq(+object, +other); - - case errorTag: - return object.name == other.name && object.message == other.message; - - case regexpTag: - case stringTag: - // Coerce regexes to strings and treat strings, primitives and objects, - // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring - // for more details. - return object == (other + ''); - - case mapTag: - var convert = mapToArray; - - case setTag: - var isPartial = bitmask & COMPARE_PARTIAL_FLAG; - convert || (convert = setToArray); - - if (object.size != other.size && !isPartial) { - return false; - } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked) { - return stacked == other; - } - bitmask |= COMPARE_UNORDERED_FLAG; - - // Recursively compare objects (susceptible to call stack limits). - stack.set(object, other); - var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); - stack['delete'](object); - return result; - - case symbolTag: - if (symbolValueOf) { - return symbolValueOf.call(object) == symbolValueOf.call(other); - } - } - return false; - } - - /** - * A specialized version of `baseIsEqualDeep` for objects with support for - * partial deep comparisons. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { - var isPartial = bitmask & COMPARE_PARTIAL_FLAG, - objProps = getAllKeys(object), - objLength = objProps.length, - othProps = getAllKeys(other), - othLength = othProps.length; - - if (objLength != othLength && !isPartial) { - return false; - } - var index = objLength; - while (index--) { - var key = objProps[index]; - if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { - return false; - } - } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked && stack.get(other)) { - return stacked == other; - } - var result = true; - stack.set(object, other); - stack.set(other, object); - - var skipCtor = isPartial; - while (++index < objLength) { - key = objProps[index]; - var objValue = object[key], - othValue = other[key]; - - if (customizer) { - var compared = isPartial - ? customizer(othValue, objValue, key, other, object, stack) - : customizer(objValue, othValue, key, object, other, stack); - } - // Recursively compare objects (susceptible to call stack limits). - if (!(compared === undefined - ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) - : compared - )) { - result = false; - break; - } - skipCtor || (skipCtor = key == 'constructor'); - } - if (result && !skipCtor) { - var objCtor = object.constructor, - othCtor = other.constructor; - - // Non `Object` object instances with different constructors are not equal. - if (objCtor != othCtor && - ('constructor' in object && 'constructor' in other) && - !(typeof objCtor == 'function' && objCtor instanceof objCtor && - typeof othCtor == 'function' && othCtor instanceof othCtor)) { - result = false; - } - } - stack['delete'](object); - stack['delete'](other); - return result; - } - - /** - * A specialized version of `baseRest` which flattens the rest array. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @returns {Function} Returns the new function. - */ - function flatRest(func) { - return setToString(overRest(func, undefined, flatten), func + ''); - } - - /** - * Creates an array of own enumerable property names and symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeys(object) { - return baseGetAllKeys(object, keys, getSymbols); - } - - /** - * Creates an array of own and inherited enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeysIn(object) { - return baseGetAllKeys(object, keysIn, getSymbolsIn); - } - - /** - * Gets metadata for `func`. - * - * @private - * @param {Function} func The function to query. - * @returns {*} Returns the metadata for `func`. - */ - var getData = !metaMap ? noop : function(func) { - return metaMap.get(func); - }; - - /** - * Gets the name of `func`. - * - * @private - * @param {Function} func The function to query. - * @returns {string} Returns the function name. - */ - function getFuncName(func) { - var result = (func.name + ''), - array = realNames[result], - length = hasOwnProperty.call(realNames, result) ? array.length : 0; - - while (length--) { - var data = array[length], - otherFunc = data.func; - if (otherFunc == null || otherFunc == func) { - return data.name; - } - } - return result; - } - - /** - * Gets the argument placeholder value for `func`. - * - * @private - * @param {Function} func The function to inspect. - * @returns {*} Returns the placeholder value. - */ - function getHolder(func) { - var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; - return object.placeholder; - } - - /** - * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, - * this function returns the custom method, otherwise it returns `baseIteratee`. - * If arguments are provided, the chosen function is invoked with them and - * its result is returned. - * - * @private - * @param {*} [value] The value to convert to an iteratee. - * @param {number} [arity] The arity of the created iteratee. - * @returns {Function} Returns the chosen function or its result. - */ - function getIteratee() { - var result = lodash.iteratee || iteratee; - result = result === iteratee ? baseIteratee : result; - return arguments.length ? result(arguments[0], arguments[1]) : result; - } - - /** - * Gets the data for `map`. - * - * @private - * @param {Object} map The map to query. - * @param {string} key The reference key. - * @returns {*} Returns the map data. - */ - function getMapData(map, key) { - var data = map.__data__; - return isKeyable(key) - ? data[typeof key == 'string' ? 'string' : 'hash'] - : data.map; - } - - /** - * Gets the property names, values, and compare flags of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the match data of `object`. - */ - function getMatchData(object) { - var result = keys(object), - length = result.length; - - while (length--) { - var key = result[length], - value = object[key]; - - result[length] = [key, value, isStrictComparable(value)]; - } - return result; - } - - /** - * Gets the native function at `key` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. - */ - function getNative(object, key) { - var value = getValue(object, key); - return baseIsNative(value) ? value : undefined; - } - - /** - * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the raw `toStringTag`. - */ - function getRawTag(value) { - var isOwn = hasOwnProperty.call(value, symToStringTag), - tag = value[symToStringTag]; - - try { - value[symToStringTag] = undefined; - var unmasked = true; - } catch (e) {} - - var result = nativeObjectToString.call(value); - if (unmasked) { - if (isOwn) { - value[symToStringTag] = tag; - } else { - delete value[symToStringTag]; - } - } - return result; - } - - /** - * Creates an array of the own enumerable symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbols = !nativeGetSymbols ? stubArray : function(object) { - if (object == null) { - return []; - } - object = Object(object); - return arrayFilter(nativeGetSymbols(object), function(symbol) { - return propertyIsEnumerable.call(object, symbol); - }); - }; - - /** - * Creates an array of the own and inherited enumerable symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) { - var result = []; - while (object) { - arrayPush(result, getSymbols(object)); - object = getPrototype(object); - } - return result; - }; - - /** - * Gets the `toStringTag` of `value`. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - var getTag = baseGetTag; - - // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. - if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || - (Map && getTag(new Map) != mapTag) || - (Promise && getTag(Promise.resolve()) != promiseTag) || - (Set && getTag(new Set) != setTag) || - (WeakMap && getTag(new WeakMap) != weakMapTag)) { - getTag = function(value) { - var result = baseGetTag(value), - Ctor = result == objectTag ? value.constructor : undefined, - ctorString = Ctor ? toSource(Ctor) : ''; - - if (ctorString) { - switch (ctorString) { - case dataViewCtorString: return dataViewTag; - case mapCtorString: return mapTag; - case promiseCtorString: return promiseTag; - case setCtorString: return setTag; - case weakMapCtorString: return weakMapTag; - } - } - return result; - }; - } - - /** - * Gets the view, applying any `transforms` to the `start` and `end` positions. - * - * @private - * @param {number} start The start of the view. - * @param {number} end The end of the view. - * @param {Array} transforms The transformations to apply to the view. - * @returns {Object} Returns an object containing the `start` and `end` - * positions of the view. - */ - function getView(start, end, transforms) { - var index = -1, - length = transforms.length; - - while (++index < length) { - var data = transforms[index], - size = data.size; - - switch (data.type) { - case 'drop': start += size; break; - case 'dropRight': end -= size; break; - case 'take': end = nativeMin(end, start + size); break; - case 'takeRight': start = nativeMax(start, end - size); break; - } - } - return { 'start': start, 'end': end }; - } - - /** - * Extracts wrapper details from the `source` body comment. - * - * @private - * @param {string} source The source to inspect. - * @returns {Array} Returns the wrapper details. - */ - function getWrapDetails(source) { - var match = source.match(reWrapDetails); - return match ? match[1].split(reSplitDetails) : []; - } - - /** - * Checks if `path` exists on `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @param {Function} hasFunc The function to check properties. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - */ - function hasPath(object, path, hasFunc) { - path = castPath(path, object); - - var index = -1, - length = path.length, - result = false; - - while (++index < length) { - var key = toKey(path[index]); - if (!(result = object != null && hasFunc(object, key))) { - break; - } - object = object[key]; - } - if (result || ++index != length) { - return result; - } - length = object == null ? 0 : object.length; - return !!length && isLength(length) && isIndex(key, length) && - (isArray(object) || isArguments(object)); - } - - /** - * Initializes an array clone. - * - * @private - * @param {Array} array The array to clone. - * @returns {Array} Returns the initialized clone. - */ - function initCloneArray(array) { - var length = array.length, - result = new array.constructor(length); - - // Add properties assigned by `RegExp#exec`. - if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { - result.index = array.index; - result.input = array.input; - } - return result; - } - - /** - * Initializes an object clone. - * - * @private - * @param {Object} object The object to clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneObject(object) { - return (typeof object.constructor == 'function' && !isPrototype(object)) - ? baseCreate(getPrototype(object)) - : {}; - } - - /** - * Initializes an object clone based on its `toStringTag`. - * - * **Note:** This function only supports cloning values with tags of - * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`. - * - * @private - * @param {Object} object The object to clone. - * @param {string} tag The `toStringTag` of the object to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneByTag(object, tag, isDeep) { - var Ctor = object.constructor; - switch (tag) { - case arrayBufferTag: - return cloneArrayBuffer(object); - - case boolTag: - case dateTag: - return new Ctor(+object); - - case dataViewTag: - return cloneDataView(object, isDeep); - - case float32Tag: case float64Tag: - case int8Tag: case int16Tag: case int32Tag: - case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: - return cloneTypedArray(object, isDeep); - - case mapTag: - return new Ctor; - - case numberTag: - case stringTag: - return new Ctor(object); - - case regexpTag: - return cloneRegExp(object); - - case setTag: - return new Ctor; - - case symbolTag: - return cloneSymbol(object); - } - } - - /** - * Inserts wrapper `details` in a comment at the top of the `source` body. - * - * @private - * @param {string} source The source to modify. - * @returns {Array} details The details to insert. - * @returns {string} Returns the modified source. - */ - function insertWrapDetails(source, details) { - var length = details.length; - if (!length) { - return source; - } - var lastIndex = length - 1; - details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; - details = details.join(length > 2 ? ', ' : ' '); - return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); - } - - /** - * Checks if `value` is a flattenable `arguments` object or array. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. - */ - function isFlattenable(value) { - return isArray(value) || isArguments(value) || - !!(spreadableSymbol && value && value[spreadableSymbol]); - } - - /** - * Checks if `value` is a valid array-like index. - * - * @private - * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. - */ - function isIndex(value, length) { - var type = typeof value; - length = length == null ? MAX_SAFE_INTEGER : length; - - return !!length && - (type == 'number' || - (type != 'symbol' && reIsUint.test(value))) && - (value > -1 && value % 1 == 0 && value < length); - } - - /** - * Checks if the given arguments are from an iteratee call. - * - * @private - * @param {*} value The potential iteratee value argument. - * @param {*} index The potential iteratee index or key argument. - * @param {*} object The potential iteratee object argument. - * @returns {boolean} Returns `true` if the arguments are from an iteratee call, - * else `false`. - */ - function isIterateeCall(value, index, object) { - if (!isObject(object)) { - return false; - } - var type = typeof index; - if (type == 'number' - ? (isArrayLike(object) && isIndex(index, object.length)) - : (type == 'string' && index in object) - ) { - return eq(object[index], value); - } - return false; - } - - /** - * Checks if `value` is a property name and not a property path. - * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. - */ - function isKey(value, object) { - if (isArray(value)) { - return false; - } - var type = typeof value; - if (type == 'number' || type == 'symbol' || type == 'boolean' || - value == null || isSymbol(value)) { - return true; - } - return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || - (object != null && value in Object(object)); - } - - /** - * Checks if `value` is suitable for use as unique object key. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is suitable, else `false`. - */ - function isKeyable(value) { - var type = typeof value; - return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') - ? (value !== '__proto__') - : (value === null); - } - - /** - * Checks if `func` has a lazy counterpart. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` has a lazy counterpart, - * else `false`. - */ - function isLaziable(func) { - var funcName = getFuncName(func), - other = lodash[funcName]; - - if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) { - return false; - } - if (func === other) { - return true; - } - var data = getData(other); - return !!data && func === data[0]; - } - - /** - * Checks if `func` has its source masked. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` is masked, else `false`. - */ - function isMasked(func) { - return !!maskSrcKey && (maskSrcKey in func); - } - - /** - * Checks if `func` is capable of being masked. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `func` is maskable, else `false`. - */ - var isMaskable = coreJsData ? isFunction : stubFalse; - - /** - * Checks if `value` is likely a prototype object. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. - */ - function isPrototype(value) { - var Ctor = value && value.constructor, - proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; - - return value === proto; - } - - /** - * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` if suitable for strict - * equality comparisons, else `false`. - */ - function isStrictComparable(value) { - return value === value && !isObject(value); - } - - /** - * A specialized version of `matchesProperty` for source values suitable - * for strict equality comparisons, i.e. `===`. - * - * @private - * @param {string} key The key of the property to get. - * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. - */ - function matchesStrictComparable(key, srcValue) { - return function(object) { - if (object == null) { - return false; - } - return object[key] === srcValue && - (srcValue !== undefined || (key in Object(object))); - }; - } - - /** - * A specialized version of `_.memoize` which clears the memoized function's - * cache when it exceeds `MAX_MEMOIZE_SIZE`. - * - * @private - * @param {Function} func The function to have its output memoized. - * @returns {Function} Returns the new memoized function. - */ - function memoizeCapped(func) { - var result = memoize(func, function(key) { - if (cache.size === MAX_MEMOIZE_SIZE) { - cache.clear(); - } - return key; - }); - - var cache = result.cache; - return result; - } - - /** - * Merges the function metadata of `source` into `data`. - * - * Merging metadata reduces the number of wrappers used to invoke a function. - * This is possible because methods like `_.bind`, `_.curry`, and `_.partial` - * may be applied regardless of execution order. Methods like `_.ary` and - * `_.rearg` modify function arguments, making the order in which they are - * executed important, preventing the merging of metadata. However, we make - * an exception for a safe combined case where curried functions have `_.ary` - * and or `_.rearg` applied. - * - * @private - * @param {Array} data The destination metadata. - * @param {Array} source The source metadata. - * @returns {Array} Returns `data`. - */ - function mergeData(data, source) { - var bitmask = data[1], - srcBitmask = source[1], - newBitmask = bitmask | srcBitmask, - isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG); - - var isCombo = - ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) || - ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) || - ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG)); - - // Exit early if metadata can't be merged. - if (!(isCommon || isCombo)) { - return data; - } - // Use source `thisArg` if available. - if (srcBitmask & WRAP_BIND_FLAG) { - data[2] = source[2]; - // Set when currying a bound function. - newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG; - } - // Compose partial arguments. - var value = source[3]; - if (value) { - var partials = data[3]; - data[3] = partials ? composeArgs(partials, value, source[4]) : value; - data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4]; - } - // Compose partial right arguments. - value = source[5]; - if (value) { - partials = data[5]; - data[5] = partials ? composeArgsRight(partials, value, source[6]) : value; - data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6]; - } - // Use source `argPos` if available. - value = source[7]; - if (value) { - data[7] = value; - } - // Use source `ary` if it's smaller. - if (srcBitmask & WRAP_ARY_FLAG) { - data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]); - } - // Use source `arity` if one is not provided. - if (data[9] == null) { - data[9] = source[9]; - } - // Use source `func` and merge bitmasks. - data[0] = source[0]; - data[1] = newBitmask; - - return data; - } - - /** - * This function is like - * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * except that it includes inherited enumerable properties. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function nativeKeysIn(object) { - var result = []; - if (object != null) { - for (var key in Object(object)) { - result.push(key); - } - } - return result; - } - - /** - * Converts `value` to a string using `Object.prototype.toString`. - * - * @private - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - */ - function objectToString(value) { - return nativeObjectToString.call(value); - } - - /** - * A specialized version of `baseRest` which transforms the rest array. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @param {Function} transform The rest array transform. - * @returns {Function} Returns the new function. - */ - function overRest(func, start, transform) { - start = nativeMax(start === undefined ? (func.length - 1) : start, 0); - return function() { - var args = arguments, - index = -1, - length = nativeMax(args.length - start, 0), - array = Array(length); - - while (++index < length) { - array[index] = args[start + index]; - } - index = -1; - var otherArgs = Array(start + 1); - while (++index < start) { - otherArgs[index] = args[index]; - } - otherArgs[start] = transform(array); - return apply(func, this, otherArgs); - }; - } - - /** - * Gets the parent value at `path` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} path The path to get the parent value of. - * @returns {*} Returns the parent value. - */ - function parent(object, path) { - return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1)); - } - - /** - * Reorder `array` according to the specified indexes where the element at - * the first index is assigned as the first element, the element at - * the second index is assigned as the second element, and so on. - * - * @private - * @param {Array} array The array to reorder. - * @param {Array} indexes The arranged array indexes. - * @returns {Array} Returns `array`. - */ - function reorder(array, indexes) { - var arrLength = array.length, - length = nativeMin(indexes.length, arrLength), - oldArray = copyArray(array); - - while (length--) { - var index = indexes[length]; - array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; - } - return array; - } - - /** - * Sets metadata for `func`. - * - * **Note:** If this function becomes hot, i.e. is invoked a lot in a short - * period of time, it will trip its breaker and transition to an identity - * function to avoid garbage collection pauses in V8. See - * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070) - * for more details. - * - * @private - * @param {Function} func The function to associate metadata with. - * @param {*} data The metadata. - * @returns {Function} Returns `func`. - */ - var setData = shortOut(baseSetData); - - /** - * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). - * - * @private - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @returns {number|Object} Returns the timer id or timeout object. - */ - var setTimeout = ctxSetTimeout || function(func, wait) { - return root.setTimeout(func, wait); - }; - - /** - * Sets the `toString` method of `func` to return `string`. - * - * @private - * @param {Function} func The function to modify. - * @param {Function} string The `toString` result. - * @returns {Function} Returns `func`. - */ - var setToString = shortOut(baseSetToString); - - /** - * Sets the `toString` method of `wrapper` to mimic the source of `reference` - * with wrapper details in a comment at the top of the source body. - * - * @private - * @param {Function} wrapper The function to modify. - * @param {Function} reference The reference function. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @returns {Function} Returns `wrapper`. - */ - function setWrapToString(wrapper, reference, bitmask) { - var source = (reference + ''); - return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); - } - - /** - * Creates a function that'll short out and invoke `identity` instead - * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` - * milliseconds. - * - * @private - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new shortable function. - */ - function shortOut(func) { - var count = 0, - lastCalled = 0; - - return function() { - var stamp = nativeNow(), - remaining = HOT_SPAN - (stamp - lastCalled); - - lastCalled = stamp; - if (remaining > 0) { - if (++count >= HOT_COUNT) { - return arguments[0]; - } - } else { - count = 0; - } - return func.apply(undefined, arguments); - }; - } - - /** - * A specialized version of `_.shuffle` which mutates and sets the size of `array`. - * - * @private - * @param {Array} array The array to shuffle. - * @param {number} [size=array.length] The size of `array`. - * @returns {Array} Returns `array`. - */ - function shuffleSelf(array, size) { - var index = -1, - length = array.length, - lastIndex = length - 1; - - size = size === undefined ? length : size; - while (++index < size) { - var rand = baseRandom(index, lastIndex), - value = array[rand]; - - array[rand] = array[index]; - array[index] = value; - } - array.length = size; - return array; - } - - /** - * Converts `string` to a property path array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the property path array. - */ - var stringToPath = memoizeCapped(function(string) { - var result = []; - if (string.charCodeAt(0) === 46 /* . */) { - result.push(''); - } - string.replace(rePropName, function(match, number, quote, subString) { - result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); - }); - return result; - }); - - /** - * Converts `value` to a string key if it's not a string or symbol. - * - * @private - * @param {*} value The value to inspect. - * @returns {string|symbol} Returns the key. - */ - function toKey(value) { - if (typeof value == 'string' || isSymbol(value)) { - return value; - } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; - } - - /** - * Converts `func` to its source code. - * - * @private - * @param {Function} func The function to convert. - * @returns {string} Returns the source code. - */ - function toSource(func) { - if (func != null) { - try { - return funcToString.call(func); - } catch (e) {} - try { - return (func + ''); - } catch (e) {} - } - return ''; - } - - /** - * Updates wrapper `details` based on `bitmask` flags. - * - * @private - * @returns {Array} details The details to modify. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @returns {Array} Returns `details`. - */ - function updateWrapDetails(details, bitmask) { - arrayEach(wrapFlags, function(pair) { - var value = '_.' + pair[0]; - if ((bitmask & pair[1]) && !arrayIncludes(details, value)) { - details.push(value); - } - }); - return details.sort(); - } - - /** - * Creates a clone of `wrapper`. - * - * @private - * @param {Object} wrapper The wrapper to clone. - * @returns {Object} Returns the cloned wrapper. - */ - function wrapperClone(wrapper) { - if (wrapper instanceof LazyWrapper) { - return wrapper.clone(); - } - var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__); - result.__actions__ = copyArray(wrapper.__actions__); - result.__index__ = wrapper.__index__; - result.__values__ = wrapper.__values__; - return result; - } - - /*------------------------------------------------------------------------*/ - - /** - * Creates an array of elements split into groups the length of `size`. - * If `array` can't be split evenly, the final chunk will be the remaining - * elements. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to process. - * @param {number} [size=1] The length of each chunk - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the new array of chunks. - * @example - * - * _.chunk(['a', 'b', 'c', 'd'], 2); - * // => [['a', 'b'], ['c', 'd']] - * - * _.chunk(['a', 'b', 'c', 'd'], 3); - * // => [['a', 'b', 'c'], ['d']] - */ - function chunk(array, size, guard) { - if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) { - size = 1; - } else { - size = nativeMax(toInteger(size), 0); - } - var length = array == null ? 0 : array.length; - if (!length || size < 1) { - return []; - } - var index = 0, - resIndex = 0, - result = Array(nativeCeil(length / size)); - - while (index < length) { - result[resIndex++] = baseSlice(array, index, (index += size)); - } - return result; - } - - /** - * Creates an array with all falsey values removed. The values `false`, `null`, - * `0`, `""`, `undefined`, and `NaN` are falsey. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to compact. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] - */ - function compact(array) { - var index = -1, - length = array == null ? 0 : array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (value) { - result[resIndex++] = value; - } - } - return result; - } - - /** - * Creates a new array concatenating `array` with any additional arrays - * and/or values. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to concatenate. - * @param {...*} [values] The values to concatenate. - * @returns {Array} Returns the new concatenated array. - * @example - * - * var array = [1]; - * var other = _.concat(array, 2, [3], [[4]]); - * - * console.log(other); - * // => [1, 2, 3, [4]] - * - * console.log(array); - * // => [1] - */ - function concat() { - var length = arguments.length; - if (!length) { - return []; - } - var args = Array(length - 1), - array = arguments[0], - index = length; - - while (index--) { - args[index - 1] = arguments[index]; - } - return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); - } - - /** - * Creates an array of `array` values not included in the other given arrays - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order and references of result values are - * determined by the first array. - * - * **Note:** Unlike `_.pullAll`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @returns {Array} Returns the new array of filtered values. - * @see _.without, _.xor - * @example - * - * _.difference([2, 1], [2, 3]); - * // => [1] - */ - var difference = baseRest(function(array, values) { - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) - : []; - }); - - /** - * This method is like `_.difference` except that it accepts `iteratee` which - * is invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. The order and references of result values are - * determined by the first array. The iteratee is invoked with one argument: - * (value). - * - * **Note:** Unlike `_.pullAllBy`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [1.2] - * - * // The `_.property` iteratee shorthand. - * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); - * // => [{ 'x': 2 }] - */ - var differenceBy = baseRest(function(array, values) { - var iteratee = last(values); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) - : []; - }); - - /** - * This method is like `_.difference` except that it accepts `comparator` - * which is invoked to compare elements of `array` to `values`. The order and - * references of result values are determined by the first array. The comparator - * is invoked with two arguments: (arrVal, othVal). - * - * **Note:** Unlike `_.pullAllWith`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * - * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual); - * // => [{ 'x': 2, 'y': 1 }] - */ - var differenceWith = baseRest(function(array, values) { - var comparator = last(values); - if (isArrayLikeObject(comparator)) { - comparator = undefined; - } - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) - : []; - }); - - /** - * Creates a slice of `array` with `n` elements dropped from the beginning. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to drop. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.drop([1, 2, 3]); - * // => [2, 3] - * - * _.drop([1, 2, 3], 2); - * // => [3] - * - * _.drop([1, 2, 3], 5); - * // => [] - * - * _.drop([1, 2, 3], 0); - * // => [1, 2, 3] - */ - function drop(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - return baseSlice(array, n < 0 ? 0 : n, length); - } - - /** - * Creates a slice of `array` with `n` elements dropped from the end. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to drop. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.dropRight([1, 2, 3]); - * // => [1, 2] - * - * _.dropRight([1, 2, 3], 2); - * // => [1] - * - * _.dropRight([1, 2, 3], 5); - * // => [] - * - * _.dropRight([1, 2, 3], 0); - * // => [1, 2, 3] - */ - function dropRight(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - n = length - n; - return baseSlice(array, 0, n < 0 ? 0 : n); - } - - /** - * Creates a slice of `array` excluding elements dropped from the end. - * Elements are dropped until `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.dropRightWhile(users, function(o) { return !o.active; }); - * // => objects for ['barney'] - * - * // The `_.matches` iteratee shorthand. - * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false }); - * // => objects for ['barney', 'fred'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.dropRightWhile(users, ['active', false]); - * // => objects for ['barney'] - * - * // The `_.property` iteratee shorthand. - * _.dropRightWhile(users, 'active'); - * // => objects for ['barney', 'fred', 'pebbles'] - */ - function dropRightWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), true, true) - : []; - } - - /** - * Creates a slice of `array` excluding elements dropped from the beginning. - * Elements are dropped until `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.dropWhile(users, function(o) { return !o.active; }); - * // => objects for ['pebbles'] - * - * // The `_.matches` iteratee shorthand. - * _.dropWhile(users, { 'user': 'barney', 'active': false }); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.dropWhile(users, ['active', false]); - * // => objects for ['pebbles'] - * - * // The `_.property` iteratee shorthand. - * _.dropWhile(users, 'active'); - * // => objects for ['barney', 'fred', 'pebbles'] - */ - function dropWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), true) - : []; - } - - /** - * Fills elements of `array` with `value` from `start` up to, but not - * including, `end`. - * - * **Note:** This method mutates `array`. - * - * @static - * @memberOf _ - * @since 3.2.0 - * @category Array - * @param {Array} array The array to fill. - * @param {*} value The value to fill `array` with. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns `array`. - * @example - * - * var array = [1, 2, 3]; - * - * _.fill(array, 'a'); - * console.log(array); - * // => ['a', 'a', 'a'] - * - * _.fill(Array(3), 2); - * // => [2, 2, 2] - * - * _.fill([4, 6, 8, 10], '*', 1, 3); - * // => [4, '*', '*', 10] - */ - function fill(array, value, start, end) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { - start = 0; - end = length; - } - return baseFill(array, value, start, end); - } - - /** - * This method is like `_.find` except that it returns the index of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the found element, else `-1`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.findIndex(users, function(o) { return o.user == 'barney'; }); - * // => 0 - * - * // The `_.matches` iteratee shorthand. - * _.findIndex(users, { 'user': 'fred', 'active': false }); - * // => 1 - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findIndex(users, ['active', false]); - * // => 0 - * - * // The `_.property` iteratee shorthand. - * _.findIndex(users, 'active'); - * // => 2 - */ - function findIndex(array, predicate, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = fromIndex == null ? 0 : toInteger(fromIndex); - if (index < 0) { - index = nativeMax(length + index, 0); - } - return baseFindIndex(array, getIteratee(predicate, 3), index); - } - - /** - * This method is like `_.findIndex` except that it iterates over elements - * of `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the found element, else `-1`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; }); - * // => 2 - * - * // The `_.matches` iteratee shorthand. - * _.findLastIndex(users, { 'user': 'barney', 'active': true }); - * // => 0 - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastIndex(users, ['active', false]); - * // => 2 - * - * // The `_.property` iteratee shorthand. - * _.findLastIndex(users, 'active'); - * // => 0 - */ - function findLastIndex(array, predicate, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = length - 1; - if (fromIndex !== undefined) { - index = toInteger(fromIndex); - index = fromIndex < 0 - ? nativeMax(length + index, 0) - : nativeMin(index, length - 1); - } - return baseFindIndex(array, getIteratee(predicate, 3), index, true); - } - - /** - * Flattens `array` a single level deep. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to flatten. - * @returns {Array} Returns the new flattened array. - * @example - * - * _.flatten([1, [2, [3, [4]], 5]]); - * // => [1, 2, [3, [4]], 5] - */ - function flatten(array) { - var length = array == null ? 0 : array.length; - return length ? baseFlatten(array, 1) : []; - } - - /** - * Recursively flattens `array`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to flatten. - * @returns {Array} Returns the new flattened array. - * @example - * - * _.flattenDeep([1, [2, [3, [4]], 5]]); - * // => [1, 2, 3, 4, 5] - */ - function flattenDeep(array) { - var length = array == null ? 0 : array.length; - return length ? baseFlatten(array, INFINITY) : []; - } - - /** - * Recursively flatten `array` up to `depth` times. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Array - * @param {Array} array The array to flatten. - * @param {number} [depth=1] The maximum recursion depth. - * @returns {Array} Returns the new flattened array. - * @example - * - * var array = [1, [2, [3, [4]], 5]]; - * - * _.flattenDepth(array, 1); - * // => [1, 2, [3, [4]], 5] - * - * _.flattenDepth(array, 2); - * // => [1, 2, 3, [4], 5] - */ - function flattenDepth(array, depth) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - depth = depth === undefined ? 1 : toInteger(depth); - return baseFlatten(array, depth); - } - - /** - * The inverse of `_.toPairs`; this method returns an object composed - * from key-value `pairs`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} pairs The key-value pairs. - * @returns {Object} Returns the new object. - * @example - * - * _.fromPairs([['a', 1], ['b', 2]]); - * // => { 'a': 1, 'b': 2 } - */ - function fromPairs(pairs) { - var index = -1, - length = pairs == null ? 0 : pairs.length, - result = {}; - - while (++index < length) { - var pair = pairs[index]; - result[pair[0]] = pair[1]; - } - return result; - } - - /** - * Gets the first element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @alias first - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the first element of `array`. - * @example - * - * _.head([1, 2, 3]); - * // => 1 - * - * _.head([]); - * // => undefined - */ - function head(array) { - return (array && array.length) ? array[0] : undefined; - } - - /** - * Gets the index at which the first occurrence of `value` is found in `array` - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. If `fromIndex` is negative, it's used as the - * offset from the end of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.indexOf([1, 2, 1, 2], 2); - * // => 1 - * - * // Search from the `fromIndex`. - * _.indexOf([1, 2, 1, 2], 2, 2); - * // => 3 - */ - function indexOf(array, value, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = fromIndex == null ? 0 : toInteger(fromIndex); - if (index < 0) { - index = nativeMax(length + index, 0); - } - return baseIndexOf(array, value, index); - } - - /** - * Gets all but the last element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.initial([1, 2, 3]); - * // => [1, 2] - */ - function initial(array) { - var length = array == null ? 0 : array.length; - return length ? baseSlice(array, 0, -1) : []; - } - - /** - * Creates an array of unique values that are included in all given arrays - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order and references of result values are - * determined by the first array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * _.intersection([2, 1], [2, 3]); - * // => [2] - */ - var intersection = baseRest(function(arrays) { - var mapped = arrayMap(arrays, castArrayLikeObject); - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped) - : []; - }); - - /** - * This method is like `_.intersection` except that it accepts `iteratee` - * which is invoked for each element of each `arrays` to generate the criterion - * by which they're compared. The order and references of result values are - * determined by the first array. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [2.1] - * - * // The `_.property` iteratee shorthand. - * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }] - */ - var intersectionBy = baseRest(function(arrays) { - var iteratee = last(arrays), - mapped = arrayMap(arrays, castArrayLikeObject); - - if (iteratee === last(mapped)) { - iteratee = undefined; - } else { - mapped.pop(); - } - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped, getIteratee(iteratee, 2)) - : []; - }); - - /** - * This method is like `_.intersection` except that it accepts `comparator` - * which is invoked to compare elements of `arrays`. The order and references - * of result values are determined by the first array. The comparator is - * invoked with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.intersectionWith(objects, others, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }] - */ - var intersectionWith = baseRest(function(arrays) { - var comparator = last(arrays), - mapped = arrayMap(arrays, castArrayLikeObject); - - comparator = typeof comparator == 'function' ? comparator : undefined; - if (comparator) { - mapped.pop(); - } - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped, undefined, comparator) - : []; - }); - - /** - * Converts all elements in `array` into a string separated by `separator`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to convert. - * @param {string} [separator=','] The element separator. - * @returns {string} Returns the joined string. - * @example - * - * _.join(['a', 'b', 'c'], '~'); - * // => 'a~b~c' - */ - function join(array, separator) { - return array == null ? '' : nativeJoin.call(array, separator); - } - - /** - * Gets the last element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the last element of `array`. - * @example - * - * _.last([1, 2, 3]); - * // => 3 - */ - function last(array) { - var length = array == null ? 0 : array.length; - return length ? array[length - 1] : undefined; - } - - /** - * This method is like `_.indexOf` except that it iterates over elements of - * `array` from right to left. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.lastIndexOf([1, 2, 1, 2], 2); - * // => 3 - * - * // Search from the `fromIndex`. - * _.lastIndexOf([1, 2, 1, 2], 2, 2); - * // => 1 - */ - function lastIndexOf(array, value, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = length; - if (fromIndex !== undefined) { - index = toInteger(fromIndex); - index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1); - } - return value === value - ? strictLastIndexOf(array, value, index) - : baseFindIndex(array, baseIsNaN, index, true); - } - - /** - * Gets the element at index `n` of `array`. If `n` is negative, the nth - * element from the end is returned. - * - * @static - * @memberOf _ - * @since 4.11.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=0] The index of the element to return. - * @returns {*} Returns the nth element of `array`. - * @example - * - * var array = ['a', 'b', 'c', 'd']; - * - * _.nth(array, 1); - * // => 'b' - * - * _.nth(array, -2); - * // => 'c'; - */ - function nth(array, n) { - return (array && array.length) ? baseNth(array, toInteger(n)) : undefined; - } - - /** - * Removes all given values from `array` using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove` - * to remove elements from an array by predicate. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {...*} [values] The values to remove. - * @returns {Array} Returns `array`. - * @example - * - * var array = ['a', 'b', 'c', 'a', 'b', 'c']; - * - * _.pull(array, 'a', 'c'); - * console.log(array); - * // => ['b', 'b'] - */ - var pull = baseRest(pullAll); - - /** - * This method is like `_.pull` except that it accepts an array of values to remove. - * - * **Note:** Unlike `_.difference`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @returns {Array} Returns `array`. - * @example - * - * var array = ['a', 'b', 'c', 'a', 'b', 'c']; - * - * _.pullAll(array, ['a', 'c']); - * console.log(array); - * // => ['b', 'b'] - */ - function pullAll(array, values) { - return (array && array.length && values && values.length) - ? basePullAll(array, values) - : array; - } - - /** - * This method is like `_.pullAll` except that it accepts `iteratee` which is - * invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. The iteratee is invoked with one argument: (value). - * - * **Note:** Unlike `_.differenceBy`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns `array`. - * @example - * - * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; - * - * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x'); - * console.log(array); - * // => [{ 'x': 2 }] - */ - function pullAllBy(array, values, iteratee) { - return (array && array.length && values && values.length) - ? basePullAll(array, values, getIteratee(iteratee, 2)) - : array; - } - - /** - * This method is like `_.pullAll` except that it accepts `comparator` which - * is invoked to compare elements of `array` to `values`. The comparator is - * invoked with two arguments: (arrVal, othVal). - * - * **Note:** Unlike `_.differenceWith`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns `array`. - * @example - * - * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]; - * - * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual); - * console.log(array); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }] - */ - function pullAllWith(array, values, comparator) { - return (array && array.length && values && values.length) - ? basePullAll(array, values, undefined, comparator) - : array; - } - - /** - * Removes elements from `array` corresponding to `indexes` and returns an - * array of removed elements. - * - * **Note:** Unlike `_.at`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {...(number|number[])} [indexes] The indexes of elements to remove. - * @returns {Array} Returns the new array of removed elements. - * @example - * - * var array = ['a', 'b', 'c', 'd']; - * var pulled = _.pullAt(array, [1, 3]); - * - * console.log(array); - * // => ['a', 'c'] - * - * console.log(pulled); - * // => ['b', 'd'] - */ - var pullAt = flatRest(function(array, indexes) { - var length = array == null ? 0 : array.length, - result = baseAt(array, indexes); - - basePullAt(array, arrayMap(indexes, function(index) { - return isIndex(index, length) ? +index : index; - }).sort(compareAscending)); - - return result; - }); - - /** - * Removes all elements from `array` that `predicate` returns truthy for - * and returns an array of the removed elements. The predicate is invoked - * with three arguments: (value, index, array). - * - * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull` - * to pull elements from an array by value. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new array of removed elements. - * @example - * - * var array = [1, 2, 3, 4]; - * var evens = _.remove(array, function(n) { - * return n % 2 == 0; - * }); - * - * console.log(array); - * // => [1, 3] - * - * console.log(evens); - * // => [2, 4] - */ - function remove(array, predicate) { - var result = []; - if (!(array && array.length)) { - return result; - } - var index = -1, - indexes = [], - length = array.length; - - predicate = getIteratee(predicate, 3); - while (++index < length) { - var value = array[index]; - if (predicate(value, index, array)) { - result.push(value); - indexes.push(index); - } - } - basePullAt(array, indexes); - return result; - } - - /** - * Reverses `array` so that the first element becomes the last, the second - * element becomes the second to last, and so on. - * - * **Note:** This method mutates `array` and is based on - * [`Array#reverse`](https://mdn.io/Array/reverse). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @returns {Array} Returns `array`. - * @example - * - * var array = [1, 2, 3]; - * - * _.reverse(array); - * // => [3, 2, 1] - * - * console.log(array); - * // => [3, 2, 1] - */ - function reverse(array) { - return array == null ? array : nativeReverse.call(array); - } - - /** - * Creates a slice of `array` from `start` up to, but not including, `end`. - * - * **Note:** This method is used instead of - * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are - * returned. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. - */ - function slice(array, start, end) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - if (end && typeof end != 'number' && isIterateeCall(array, start, end)) { - start = 0; - end = length; - } - else { - start = start == null ? 0 : toInteger(start); - end = end === undefined ? length : toInteger(end); - } - return baseSlice(array, start, end); - } - - /** - * Uses a binary search to determine the lowest index at which `value` - * should be inserted into `array` in order to maintain its sort order. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * _.sortedIndex([30, 50], 40); - * // => 1 - */ - function sortedIndex(array, value) { - return baseSortedIndex(array, value); - } - - /** - * This method is like `_.sortedIndex` except that it accepts `iteratee` - * which is invoked for `value` and each element of `array` to compute their - * sort ranking. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * var objects = [{ 'x': 4 }, { 'x': 5 }]; - * - * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); - * // => 0 - * - * // The `_.property` iteratee shorthand. - * _.sortedIndexBy(objects, { 'x': 4 }, 'x'); - * // => 0 - */ - function sortedIndexBy(array, value, iteratee) { - return baseSortedIndexBy(array, value, getIteratee(iteratee, 2)); - } - - /** - * This method is like `_.indexOf` except that it performs a binary - * search on a sorted `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.sortedIndexOf([4, 5, 5, 5, 6], 5); - * // => 1 - */ - function sortedIndexOf(array, value) { - var length = array == null ? 0 : array.length; - if (length) { - var index = baseSortedIndex(array, value); - if (index < length && eq(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * This method is like `_.sortedIndex` except that it returns the highest - * index at which `value` should be inserted into `array` in order to - * maintain its sort order. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * _.sortedLastIndex([4, 5, 5, 5, 6], 5); - * // => 4 - */ - function sortedLastIndex(array, value) { - return baseSortedIndex(array, value, true); - } - - /** - * This method is like `_.sortedLastIndex` except that it accepts `iteratee` - * which is invoked for `value` and each element of `array` to compute their - * sort ranking. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * var objects = [{ 'x': 4 }, { 'x': 5 }]; - * - * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); - * // => 1 - * - * // The `_.property` iteratee shorthand. - * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x'); - * // => 1 - */ - function sortedLastIndexBy(array, value, iteratee) { - return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true); - } - - /** - * This method is like `_.lastIndexOf` except that it performs a binary - * search on a sorted `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5); - * // => 3 - */ - function sortedLastIndexOf(array, value) { - var length = array == null ? 0 : array.length; - if (length) { - var index = baseSortedIndex(array, value, true) - 1; - if (eq(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * This method is like `_.uniq` except that it's designed and optimized - * for sorted arrays. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.sortedUniq([1, 1, 2]); - * // => [1, 2] - */ - function sortedUniq(array) { - return (array && array.length) - ? baseSortedUniq(array) - : []; - } - - /** - * This method is like `_.uniqBy` except that it's designed and optimized - * for sorted arrays. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor); - * // => [1.1, 2.3] - */ - function sortedUniqBy(array, iteratee) { - return (array && array.length) - ? baseSortedUniq(array, getIteratee(iteratee, 2)) - : []; - } - - /** - * Gets all but the first element of `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to query. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.tail([1, 2, 3]); - * // => [2, 3] - */ - function tail(array) { - var length = array == null ? 0 : array.length; - return length ? baseSlice(array, 1, length) : []; - } - - /** - * Creates a slice of `array` with `n` elements taken from the beginning. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to take. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.take([1, 2, 3]); - * // => [1] - * - * _.take([1, 2, 3], 2); - * // => [1, 2] - * - * _.take([1, 2, 3], 5); - * // => [1, 2, 3] - * - * _.take([1, 2, 3], 0); - * // => [] - */ - function take(array, n, guard) { - if (!(array && array.length)) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - return baseSlice(array, 0, n < 0 ? 0 : n); - } - - /** - * Creates a slice of `array` with `n` elements taken from the end. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to take. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.takeRight([1, 2, 3]); - * // => [3] - * - * _.takeRight([1, 2, 3], 2); - * // => [2, 3] - * - * _.takeRight([1, 2, 3], 5); - * // => [1, 2, 3] - * - * _.takeRight([1, 2, 3], 0); - * // => [] - */ - function takeRight(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - n = length - n; - return baseSlice(array, n < 0 ? 0 : n, length); - } - - /** - * Creates a slice of `array` with elements taken from the end. Elements are - * taken until `predicate` returns falsey. The predicate is invoked with - * three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.takeRightWhile(users, function(o) { return !o.active; }); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.matches` iteratee shorthand. - * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false }); - * // => objects for ['pebbles'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.takeRightWhile(users, ['active', false]); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.property` iteratee shorthand. - * _.takeRightWhile(users, 'active'); - * // => [] - */ - function takeRightWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), false, true) - : []; - } - - /** - * Creates a slice of `array` with elements taken from the beginning. Elements - * are taken until `predicate` returns falsey. The predicate is invoked with - * three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.takeWhile(users, function(o) { return !o.active; }); - * // => objects for ['barney', 'fred'] - * - * // The `_.matches` iteratee shorthand. - * _.takeWhile(users, { 'user': 'barney', 'active': false }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.takeWhile(users, ['active', false]); - * // => objects for ['barney', 'fred'] - * - * // The `_.property` iteratee shorthand. - * _.takeWhile(users, 'active'); - * // => [] - */ - function takeWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3)) - : []; - } - - /** - * Creates an array of unique values, in order, from all given arrays using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of combined values. - * @example - * - * _.union([2], [1, 2]); - * // => [2, 1] - */ - var union = baseRest(function(arrays) { - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true)); - }); - - /** - * This method is like `_.union` except that it accepts `iteratee` which is - * invoked for each element of each `arrays` to generate the criterion by - * which uniqueness is computed. Result values are chosen from the first - * array in which the value occurs. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of combined values. - * @example - * - * _.unionBy([2.1], [1.2, 2.3], Math.floor); - * // => [2.1, 1.2] - * - * // The `_.property` iteratee shorthand. - * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - var unionBy = baseRest(function(arrays) { - var iteratee = last(arrays); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)); - }); - - /** - * This method is like `_.union` except that it accepts `comparator` which - * is invoked to compare elements of `arrays`. Result values are chosen from - * the first array in which the value occurs. The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of combined values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.unionWith(objects, others, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] - */ - var unionWith = baseRest(function(arrays) { - var comparator = last(arrays); - comparator = typeof comparator == 'function' ? comparator : undefined; - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator); - }); - - /** - * Creates a duplicate-free version of an array, using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons, in which only the first occurrence of each element - * is kept. The order of result values is determined by the order they occur - * in the array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.uniq([2, 1, 2]); - * // => [2, 1] - */ - function uniq(array) { - return (array && array.length) ? baseUniq(array) : []; - } - - /** - * This method is like `_.uniq` except that it accepts `iteratee` which is - * invoked for each element in `array` to generate the criterion by which - * uniqueness is computed. The order of result values is determined by the - * order they occur in the array. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.uniqBy([2.1, 1.2, 2.3], Math.floor); - * // => [2.1, 1.2] - * - * // The `_.property` iteratee shorthand. - * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - function uniqBy(array, iteratee) { - return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : []; - } - - /** - * This method is like `_.uniq` except that it accepts `comparator` which - * is invoked to compare elements of `array`. The order of result values is - * determined by the order they occur in the array.The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.uniqWith(objects, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] - */ - function uniqWith(array, comparator) { - comparator = typeof comparator == 'function' ? comparator : undefined; - return (array && array.length) ? baseUniq(array, undefined, comparator) : []; - } - - /** - * This method is like `_.zip` except that it accepts an array of grouped - * elements and creates an array regrouping the elements to their pre-zip - * configuration. - * - * @static - * @memberOf _ - * @since 1.2.0 - * @category Array - * @param {Array} array The array of grouped elements to process. - * @returns {Array} Returns the new array of regrouped elements. - * @example - * - * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]); - * // => [['a', 1, true], ['b', 2, false]] - * - * _.unzip(zipped); - * // => [['a', 'b'], [1, 2], [true, false]] - */ - function unzip(array) { - if (!(array && array.length)) { - return []; - } - var length = 0; - array = arrayFilter(array, function(group) { - if (isArrayLikeObject(group)) { - length = nativeMax(group.length, length); - return true; - } - }); - return baseTimes(length, function(index) { - return arrayMap(array, baseProperty(index)); - }); - } - - /** - * This method is like `_.unzip` except that it accepts `iteratee` to specify - * how regrouped values should be combined. The iteratee is invoked with the - * elements of each group: (...group). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Array - * @param {Array} array The array of grouped elements to process. - * @param {Function} [iteratee=_.identity] The function to combine - * regrouped values. - * @returns {Array} Returns the new array of regrouped elements. - * @example - * - * var zipped = _.zip([1, 2], [10, 20], [100, 200]); - * // => [[1, 10, 100], [2, 20, 200]] - * - * _.unzipWith(zipped, _.add); - * // => [3, 30, 300] - */ - function unzipWith(array, iteratee) { - if (!(array && array.length)) { - return []; - } - var result = unzip(array); - if (iteratee == null) { - return result; - } - return arrayMap(result, function(group) { - return apply(iteratee, undefined, group); - }); - } - - /** - * Creates an array excluding all given values using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * **Note:** Unlike `_.pull`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...*} [values] The values to exclude. - * @returns {Array} Returns the new array of filtered values. - * @see _.difference, _.xor - * @example - * - * _.without([2, 1, 2, 3], 1, 2); - * // => [3] - */ - var without = baseRest(function(array, values) { - return isArrayLikeObject(array) - ? baseDifference(array, values) - : []; - }); - - /** - * Creates an array of unique values that is the - * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference) - * of the given arrays. The order of result values is determined by the order - * they occur in the arrays. - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of filtered values. - * @see _.difference, _.without - * @example - * - * _.xor([2, 1], [2, 3]); - * // => [1, 3] - */ - var xor = baseRest(function(arrays) { - return baseXor(arrayFilter(arrays, isArrayLikeObject)); - }); - - /** - * This method is like `_.xor` except that it accepts `iteratee` which is - * invoked for each element of each `arrays` to generate the criterion by - * which by which they're compared. The order of result values is determined - * by the order they occur in the arrays. The iteratee is invoked with one - * argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [1.2, 3.4] - * - * // The `_.property` iteratee shorthand. - * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 2 }] - */ - var xorBy = baseRest(function(arrays) { - var iteratee = last(arrays); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2)); - }); - - /** - * This method is like `_.xor` except that it accepts `comparator` which is - * invoked to compare elements of `arrays`. The order of result values is - * determined by the order they occur in the arrays. The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.xorWith(objects, others, _.isEqual); - * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] - */ - var xorWith = baseRest(function(arrays) { - var comparator = last(arrays); - comparator = typeof comparator == 'function' ? comparator : undefined; - return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator); - }); - - /** - * Creates an array of grouped elements, the first of which contains the - * first elements of the given arrays, the second of which contains the - * second elements of the given arrays, and so on. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to process. - * @returns {Array} Returns the new array of grouped elements. - * @example - * - * _.zip(['a', 'b'], [1, 2], [true, false]); - * // => [['a', 1, true], ['b', 2, false]] - */ - var zip = baseRest(unzip); - - /** - * This method is like `_.fromPairs` except that it accepts two arrays, - * one of property identifiers and one of corresponding values. - * - * @static - * @memberOf _ - * @since 0.4.0 - * @category Array - * @param {Array} [props=[]] The property identifiers. - * @param {Array} [values=[]] The property values. - * @returns {Object} Returns the new object. - * @example - * - * _.zipObject(['a', 'b'], [1, 2]); - * // => { 'a': 1, 'b': 2 } - */ - function zipObject(props, values) { - return baseZipObject(props || [], values || [], assignValue); - } - - /** - * This method is like `_.zipObject` except that it supports property paths. - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Array - * @param {Array} [props=[]] The property identifiers. - * @param {Array} [values=[]] The property values. - * @returns {Object} Returns the new object. - * @example - * - * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]); - * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } - */ - function zipObjectDeep(props, values) { - return baseZipObject(props || [], values || [], baseSet); - } - - /** - * This method is like `_.zip` except that it accepts `iteratee` to specify - * how grouped values should be combined. The iteratee is invoked with the - * elements of each group: (...group). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Array - * @param {...Array} [arrays] The arrays to process. - * @param {Function} [iteratee=_.identity] The function to combine - * grouped values. - * @returns {Array} Returns the new array of grouped elements. - * @example - * - * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) { - * return a + b + c; - * }); - * // => [111, 222] - */ - var zipWith = baseRest(function(arrays) { - var length = arrays.length, - iteratee = length > 1 ? arrays[length - 1] : undefined; - - iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined; - return unzipWith(arrays, iteratee); - }); - - /*------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` wrapper instance that wraps `value` with explicit method - * chain sequences enabled. The result of such sequences must be unwrapped - * with `_#value`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Seq - * @param {*} value The value to wrap. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'pebbles', 'age': 1 } - * ]; - * - * var youngest = _ - * .chain(users) - * .sortBy('age') - * .map(function(o) { - * return o.user + ' is ' + o.age; - * }) - * .head() - * .value(); - * // => 'pebbles is 1' - */ - function chain(value) { - var result = lodash(value); - result.__chain__ = true; - return result; - } - - /** - * This method invokes `interceptor` and returns `value`. The interceptor - * is invoked with one argument; (value). The purpose of this method is to - * "tap into" a method chain sequence in order to modify intermediate results. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @param {*} value The value to provide to `interceptor`. - * @param {Function} interceptor The function to invoke. - * @returns {*} Returns `value`. - * @example - * - * _([1, 2, 3]) - * .tap(function(array) { - * // Mutate input array. - * array.pop(); - * }) - * .reverse() - * .value(); - * // => [2, 1] - */ - function tap(value, interceptor) { - interceptor(value); - return value; - } - - /** - * This method is like `_.tap` except that it returns the result of `interceptor`. - * The purpose of this method is to "pass thru" values replacing intermediate - * results in a method chain sequence. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Seq - * @param {*} value The value to provide to `interceptor`. - * @param {Function} interceptor The function to invoke. - * @returns {*} Returns the result of `interceptor`. - * @example - * - * _(' abc ') - * .chain() - * .trim() - * .thru(function(value) { - * return [value]; - * }) - * .value(); - * // => ['abc'] - */ - function thru(value, interceptor) { - return interceptor(value); - } - - /** - * This method is the wrapper version of `_.at`. - * - * @name at - * @memberOf _ - * @since 1.0.0 - * @category Seq - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _(object).at(['a[0].b.c', 'a[1]']).value(); - * // => [3, 4] - */ - var wrapperAt = flatRest(function(paths) { - var length = paths.length, - start = length ? paths[0] : 0, - value = this.__wrapped__, - interceptor = function(object) { return baseAt(object, paths); }; - - if (length > 1 || this.__actions__.length || - !(value instanceof LazyWrapper) || !isIndex(start)) { - return this.thru(interceptor); - } - value = value.slice(start, +start + (length ? 1 : 0)); - value.__actions__.push({ - 'func': thru, - 'args': [interceptor], - 'thisArg': undefined - }); - return new LodashWrapper(value, this.__chain__).thru(function(array) { - if (length && !array.length) { - array.push(undefined); - } - return array; - }); - }); - - /** - * Creates a `lodash` wrapper instance with explicit method chain sequences enabled. - * - * @name chain - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 } - * ]; - * - * // A sequence without explicit chaining. - * _(users).head(); - * // => { 'user': 'barney', 'age': 36 } - * - * // A sequence with explicit chaining. - * _(users) - * .chain() - * .head() - * .pick('user') - * .value(); - * // => { 'user': 'barney' } - */ - function wrapperChain() { - return chain(this); - } - - /** - * Executes the chain sequence and returns the wrapped result. - * - * @name commit - * @memberOf _ - * @since 3.2.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var array = [1, 2]; - * var wrapped = _(array).push(3); - * - * console.log(array); - * // => [1, 2] - * - * wrapped = wrapped.commit(); - * console.log(array); - * // => [1, 2, 3] - * - * wrapped.last(); - * // => 3 - * - * console.log(array); - * // => [1, 2, 3] - */ - function wrapperCommit() { - return new LodashWrapper(this.value(), this.__chain__); - } - - /** - * Gets the next value on a wrapped object following the - * [iterator protocol](https://mdn.io/iteration_protocols#iterator). - * - * @name next - * @memberOf _ - * @since 4.0.0 - * @category Seq - * @returns {Object} Returns the next iterator value. - * @example - * - * var wrapped = _([1, 2]); - * - * wrapped.next(); - * // => { 'done': false, 'value': 1 } - * - * wrapped.next(); - * // => { 'done': false, 'value': 2 } - * - * wrapped.next(); - * // => { 'done': true, 'value': undefined } - */ - function wrapperNext() { - if (this.__values__ === undefined) { - this.__values__ = toArray(this.value()); - } - var done = this.__index__ >= this.__values__.length, - value = done ? undefined : this.__values__[this.__index__++]; - - return { 'done': done, 'value': value }; - } - - /** - * Enables the wrapper to be iterable. - * - * @name Symbol.iterator - * @memberOf _ - * @since 4.0.0 - * @category Seq - * @returns {Object} Returns the wrapper object. - * @example - * - * var wrapped = _([1, 2]); - * - * wrapped[Symbol.iterator]() === wrapped; - * // => true - * - * Array.from(wrapped); - * // => [1, 2] - */ - function wrapperToIterator() { - return this; - } - - /** - * Creates a clone of the chain sequence planting `value` as the wrapped value. - * - * @name plant - * @memberOf _ - * @since 3.2.0 - * @category Seq - * @param {*} value The value to plant. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * function square(n) { - * return n * n; - * } - * - * var wrapped = _([1, 2]).map(square); - * var other = wrapped.plant([3, 4]); - * - * other.value(); - * // => [9, 16] - * - * wrapped.value(); - * // => [1, 4] - */ - function wrapperPlant(value) { - var result, - parent = this; - - while (parent instanceof baseLodash) { - var clone = wrapperClone(parent); - clone.__index__ = 0; - clone.__values__ = undefined; - if (result) { - previous.__wrapped__ = clone; - } else { - result = clone; - } - var previous = clone; - parent = parent.__wrapped__; - } - previous.__wrapped__ = value; - return result; - } - - /** - * This method is the wrapper version of `_.reverse`. - * - * **Note:** This method mutates the wrapped array. - * - * @name reverse - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var array = [1, 2, 3]; - * - * _(array).reverse().value() - * // => [3, 2, 1] - * - * console.log(array); - * // => [3, 2, 1] - */ - function wrapperReverse() { - var value = this.__wrapped__; - if (value instanceof LazyWrapper) { - var wrapped = value; - if (this.__actions__.length) { - wrapped = new LazyWrapper(this); - } - wrapped = wrapped.reverse(); - wrapped.__actions__.push({ - 'func': thru, - 'args': [reverse], - 'thisArg': undefined - }); - return new LodashWrapper(wrapped, this.__chain__); - } - return this.thru(reverse); - } - - /** - * Executes the chain sequence to resolve the unwrapped value. - * - * @name value - * @memberOf _ - * @since 0.1.0 - * @alias toJSON, valueOf - * @category Seq - * @returns {*} Returns the resolved unwrapped value. - * @example - * - * _([1, 2, 3]).value(); - * // => [1, 2, 3] - */ - function wrapperValue() { - return baseWrapperValue(this.__wrapped__, this.__actions__); - } - - /*------------------------------------------------------------------------*/ - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the number of times the key was returned by `iteratee`. The - * iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.countBy([6.1, 4.2, 6.3], Math.floor); - * // => { '4': 1, '6': 2 } - * - * // The `_.property` iteratee shorthand. - * _.countBy(['one', 'two', 'three'], 'length'); - * // => { '3': 2, '5': 1 } - */ - var countBy = createAggregator(function(result, value, key) { - if (hasOwnProperty.call(result, key)) { - ++result[key]; - } else { - baseAssignValue(result, key, 1); - } - }); - - /** - * Checks if `predicate` returns truthy for **all** elements of `collection`. - * Iteration is stopped once `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index|key, collection). - * - * **Note:** This method returns `true` for - * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because - * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of - * elements of empty collections. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false`. - * @example - * - * _.every([true, 1, null, 'yes'], Boolean); - * // => false - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': false } - * ]; - * - * // The `_.matches` iteratee shorthand. - * _.every(users, { 'user': 'barney', 'active': false }); - * // => false - * - * // The `_.matchesProperty` iteratee shorthand. - * _.every(users, ['active', false]); - * // => true - * - * // The `_.property` iteratee shorthand. - * _.every(users, 'active'); - * // => false - */ - function every(collection, predicate, guard) { - var func = isArray(collection) ? arrayEvery : baseEvery; - if (guard && isIterateeCall(collection, predicate, guard)) { - predicate = undefined; - } - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Iterates over elements of `collection`, returning an array of all elements - * `predicate` returns truthy for. The predicate is invoked with three - * arguments: (value, index|key, collection). - * - * **Note:** Unlike `_.remove`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - * @see _.reject - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': true }, - * { 'user': 'fred', 'age': 40, 'active': false } - * ]; - * - * _.filter(users, function(o) { return !o.active; }); - * // => objects for ['fred'] - * - * // The `_.matches` iteratee shorthand. - * _.filter(users, { 'age': 36, 'active': true }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.filter(users, ['active', false]); - * // => objects for ['fred'] - * - * // The `_.property` iteratee shorthand. - * _.filter(users, 'active'); - * // => objects for ['barney'] - */ - function filter(collection, predicate) { - var func = isArray(collection) ? arrayFilter : baseFilter; - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Iterates over elements of `collection`, returning the first element - * `predicate` returns truthy for. The predicate is invoked with three - * arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=0] The index to search from. - * @returns {*} Returns the matched element, else `undefined`. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': true }, - * { 'user': 'fred', 'age': 40, 'active': false }, - * { 'user': 'pebbles', 'age': 1, 'active': true } - * ]; - * - * _.find(users, function(o) { return o.age < 40; }); - * // => object for 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.find(users, { 'age': 1, 'active': true }); - * // => object for 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.find(users, ['active', false]); - * // => object for 'fred' - * - * // The `_.property` iteratee shorthand. - * _.find(users, 'active'); - * // => object for 'barney' - */ - var find = createFind(findIndex); - - /** - * This method is like `_.find` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Collection - * @param {Array|Object} collection The collection to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=collection.length-1] The index to search from. - * @returns {*} Returns the matched element, else `undefined`. - * @example - * - * _.findLast([1, 2, 3, 4], function(n) { - * return n % 2 == 1; - * }); - * // => 3 - */ - var findLast = createFind(findLastIndex); - - /** - * Creates a flattened array of values by running each element in `collection` - * thru `iteratee` and flattening the mapped results. The iteratee is invoked - * with three arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [n, n]; - * } - * - * _.flatMap([1, 2], duplicate); - * // => [1, 1, 2, 2] - */ - function flatMap(collection, iteratee) { - return baseFlatten(map(collection, iteratee), 1); - } - - /** - * This method is like `_.flatMap` except that it recursively flattens the - * mapped results. - * - * @static - * @memberOf _ - * @since 4.7.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [[[n, n]]]; - * } - * - * _.flatMapDeep([1, 2], duplicate); - * // => [1, 1, 2, 2] - */ - function flatMapDeep(collection, iteratee) { - return baseFlatten(map(collection, iteratee), INFINITY); - } - - /** - * This method is like `_.flatMap` except that it recursively flattens the - * mapped results up to `depth` times. - * - * @static - * @memberOf _ - * @since 4.7.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {number} [depth=1] The maximum recursion depth. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [[[n, n]]]; - * } - * - * _.flatMapDepth([1, 2], duplicate, 2); - * // => [[1, 1], [2, 2]] - */ - function flatMapDepth(collection, iteratee, depth) { - depth = depth === undefined ? 1 : toInteger(depth); - return baseFlatten(map(collection, iteratee), depth); - } - - /** - * Iterates over elements of `collection` and invokes `iteratee` for each element. - * The iteratee is invoked with three arguments: (value, index|key, collection). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * **Note:** As with other "Collections" methods, objects with a "length" - * property are iterated like arrays. To avoid this behavior use `_.forIn` - * or `_.forOwn` for object iteration. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @alias each - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - * @see _.forEachRight - * @example - * - * _.forEach([1, 2], function(value) { - * console.log(value); - * }); - * // => Logs `1` then `2`. - * - * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forEach(collection, iteratee) { - var func = isArray(collection) ? arrayEach : baseEach; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forEach` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @alias eachRight - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - * @see _.forEach - * @example - * - * _.forEachRight([1, 2], function(value) { - * console.log(value); - * }); - * // => Logs `2` then `1`. - */ - function forEachRight(collection, iteratee) { - var func = isArray(collection) ? arrayEachRight : baseEachRight; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The order of grouped values - * is determined by the order they occur in `collection`. The corresponding - * value of each key is an array of elements responsible for generating the - * key. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.groupBy([6.1, 4.2, 6.3], Math.floor); - * // => { '4': [4.2], '6': [6.1, 6.3] } - * - * // The `_.property` iteratee shorthand. - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } - */ - var groupBy = createAggregator(function(result, value, key) { - if (hasOwnProperty.call(result, key)) { - result[key].push(value); - } else { - baseAssignValue(result, key, [value]); - } - }); - - /** - * Checks if `value` is in `collection`. If `collection` is a string, it's - * checked for a substring of `value`, otherwise - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * is used for equality comparisons. If `fromIndex` is negative, it's used as - * the offset from the end of `collection`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object|string} collection The collection to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=0] The index to search from. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. - * @returns {boolean} Returns `true` if `value` is found, else `false`. - * @example - * - * _.includes([1, 2, 3], 1); - * // => true - * - * _.includes([1, 2, 3], 1, 2); - * // => false - * - * _.includes({ 'a': 1, 'b': 2 }, 1); - * // => true - * - * _.includes('abcd', 'bc'); - * // => true - */ - function includes(collection, value, fromIndex, guard) { - collection = isArrayLike(collection) ? collection : values(collection); - fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0; - - var length = collection.length; - if (fromIndex < 0) { - fromIndex = nativeMax(length + fromIndex, 0); - } - return isString(collection) - ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1) - : (!!length && baseIndexOf(collection, value, fromIndex) > -1); - } - - /** - * Invokes the method at `path` of each element in `collection`, returning - * an array of the results of each invoked method. Any additional arguments - * are provided to each invoked method. If `path` is a function, it's invoked - * for, and `this` bound to, each element in `collection`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Array|Function|string} path The path of the method to invoke or - * the function invoked per iteration. - * @param {...*} [args] The arguments to invoke each method with. - * @returns {Array} Returns the array of results. - * @example - * - * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] - * - * _.invokeMap([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] - */ - var invokeMap = baseRest(function(collection, path, args) { - var index = -1, - isFunc = typeof path == 'function', - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value) { - result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args); - }); - return result; - }); - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the last element responsible for generating the key. The - * iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * var array = [ - * { 'dir': 'left', 'code': 97 }, - * { 'dir': 'right', 'code': 100 } - * ]; - * - * _.keyBy(array, function(o) { - * return String.fromCharCode(o.code); - * }); - * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } - * - * _.keyBy(array, 'dir'); - * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } - */ - var keyBy = createAggregator(function(result, value, key) { - baseAssignValue(result, key, value); - }); - - /** - * Creates an array of values by running each element in `collection` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, index|key, collection). - * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. - * - * The guarded methods are: - * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, - * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, - * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, - * `template`, `trim`, `trimEnd`, `trimStart`, and `words` - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - * @example - * - * function square(n) { - * return n * n; - * } - * - * _.map([4, 8], square); - * // => [16, 64] - * - * _.map({ 'a': 4, 'b': 8 }, square); - * // => [16, 64] (iteration order is not guaranteed) - * - * var users = [ - * { 'user': 'barney' }, - * { 'user': 'fred' } - * ]; - * - * // The `_.property` iteratee shorthand. - * _.map(users, 'user'); - * // => ['barney', 'fred'] - */ - function map(collection, iteratee) { - var func = isArray(collection) ? arrayMap : baseMap; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.sortBy` except that it allows specifying the sort - * orders of the iteratees to sort by. If `orders` is unspecified, all values - * are sorted in ascending order. Otherwise, specify an order of "desc" for - * descending or "asc" for ascending sort order of corresponding values. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]] - * The iteratees to sort by. - * @param {string[]} [orders] The sort orders of `iteratees`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. - * @returns {Array} Returns the new sorted array. - * @example - * - * var users = [ - * { 'user': 'fred', 'age': 48 }, - * { 'user': 'barney', 'age': 34 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'barney', 'age': 36 } - * ]; - * - * // Sort by `user` in ascending order and by `age` in descending order. - * _.orderBy(users, ['user', 'age'], ['asc', 'desc']); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] - */ - function orderBy(collection, iteratees, orders, guard) { - if (collection == null) { - return []; - } - if (!isArray(iteratees)) { - iteratees = iteratees == null ? [] : [iteratees]; - } - orders = guard ? undefined : orders; - if (!isArray(orders)) { - orders = orders == null ? [] : [orders]; - } - return baseOrderBy(collection, iteratees, orders); - } - - /** - * Creates an array of elements split into two groups, the first of which - * contains elements `predicate` returns truthy for, the second of which - * contains elements `predicate` returns falsey for. The predicate is - * invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the array of grouped elements. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': true }, - * { 'user': 'pebbles', 'age': 1, 'active': false } - * ]; - * - * _.partition(users, function(o) { return o.active; }); - * // => objects for [['fred'], ['barney', 'pebbles']] - * - * // The `_.matches` iteratee shorthand. - * _.partition(users, { 'age': 1, 'active': false }); - * // => objects for [['pebbles'], ['barney', 'fred']] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.partition(users, ['active', false]); - * // => objects for [['barney', 'pebbles'], ['fred']] - * - * // The `_.property` iteratee shorthand. - * _.partition(users, 'active'); - * // => objects for [['fred'], ['barney', 'pebbles']] - */ - var partition = createAggregator(function(result, value, key) { - result[key ? 0 : 1].push(value); - }, function() { return [[], []]; }); - - /** - * Reduces `collection` to a value which is the accumulated result of running - * each element in `collection` thru `iteratee`, where each successive - * invocation is supplied the return value of the previous. If `accumulator` - * is not given, the first element of `collection` is used as the initial - * value. The iteratee is invoked with four arguments: - * (accumulator, value, index|key, collection). - * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.reduce`, `_.reduceRight`, and `_.transform`. - * - * The guarded methods are: - * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, - * and `sortBy` - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @returns {*} Returns the accumulated value. - * @see _.reduceRight - * @example - * - * _.reduce([1, 2], function(sum, n) { - * return sum + n; - * }, 0); - * // => 3 - * - * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * return result; - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) - */ - function reduce(collection, iteratee, accumulator) { - var func = isArray(collection) ? arrayReduce : baseReduce, - initAccum = arguments.length < 3; - - return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach); - } - - /** - * This method is like `_.reduce` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @returns {*} Returns the accumulated value. - * @see _.reduce - * @example - * - * var array = [[0, 1], [2, 3], [4, 5]]; - * - * _.reduceRight(array, function(flattened, other) { - * return flattened.concat(other); - * }, []); - * // => [4, 5, 2, 3, 0, 1] - */ - function reduceRight(collection, iteratee, accumulator) { - var func = isArray(collection) ? arrayReduceRight : baseReduce, - initAccum = arguments.length < 3; - - return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight); - } - - /** - * The opposite of `_.filter`; this method returns the elements of `collection` - * that `predicate` does **not** return truthy for. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - * @see _.filter - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': true } - * ]; - * - * _.reject(users, function(o) { return !o.active; }); - * // => objects for ['fred'] - * - * // The `_.matches` iteratee shorthand. - * _.reject(users, { 'age': 40, 'active': true }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.reject(users, ['active', false]); - * // => objects for ['fred'] - * - * // The `_.property` iteratee shorthand. - * _.reject(users, 'active'); - * // => objects for ['barney'] - */ - function reject(collection, predicate) { - var func = isArray(collection) ? arrayFilter : baseFilter; - return func(collection, negate(getIteratee(predicate, 3))); - } - - /** - * Gets a random element from `collection`. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Collection - * @param {Array|Object} collection The collection to sample. - * @returns {*} Returns the random element. - * @example - * - * _.sample([1, 2, 3, 4]); - * // => 2 - */ - function sample(collection) { - var func = isArray(collection) ? arraySample : baseSample; - return func(collection); - } - - /** - * Gets `n` random elements at unique keys from `collection` up to the - * size of `collection`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to sample. - * @param {number} [n=1] The number of elements to sample. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the random elements. - * @example - * - * _.sampleSize([1, 2, 3], 2); - * // => [3, 1] - * - * _.sampleSize([1, 2, 3], 4); - * // => [2, 3, 1] - */ - function sampleSize(collection, n, guard) { - if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { - n = 1; - } else { - n = toInteger(n); - } - var func = isArray(collection) ? arraySampleSize : baseSampleSize; - return func(collection, n); - } - - /** - * Creates an array of shuffled values, using a version of the - * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to shuffle. - * @returns {Array} Returns the new shuffled array. - * @example - * - * _.shuffle([1, 2, 3, 4]); - * // => [4, 1, 3, 2] - */ - function shuffle(collection) { - var func = isArray(collection) ? arrayShuffle : baseShuffle; - return func(collection); - } - - /** - * Gets the size of `collection` by returning its length for array-like - * values or the number of own enumerable string keyed properties for objects. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object|string} collection The collection to inspect. - * @returns {number} Returns the collection size. - * @example - * - * _.size([1, 2, 3]); - * // => 3 - * - * _.size({ 'a': 1, 'b': 2 }); - * // => 2 - * - * _.size('pebbles'); - * // => 7 - */ - function size(collection) { - if (collection == null) { - return 0; - } - if (isArrayLike(collection)) { - return isString(collection) ? stringSize(collection) : collection.length; - } - var tag = getTag(collection); - if (tag == mapTag || tag == setTag) { - return collection.size; - } - return baseKeys(collection).length; - } - - /** - * Checks if `predicate` returns truthy for **any** element of `collection`. - * Iteration is stopped once `predicate` returns truthy. The predicate is - * invoked with three arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - * @example - * - * _.some([null, 0, 'yes', false], Boolean); - * // => true - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false } - * ]; - * - * // The `_.matches` iteratee shorthand. - * _.some(users, { 'user': 'barney', 'active': false }); - * // => false - * - * // The `_.matchesProperty` iteratee shorthand. - * _.some(users, ['active', false]); - * // => true - * - * // The `_.property` iteratee shorthand. - * _.some(users, 'active'); - * // => true - */ - function some(collection, predicate, guard) { - var func = isArray(collection) ? arraySome : baseSome; - if (guard && isIterateeCall(collection, predicate, guard)) { - predicate = undefined; - } - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Creates an array of elements, sorted in ascending order by the results of - * running each element in a collection thru each iteratee. This method - * performs a stable sort, that is, it preserves the original sort order of - * equal elements. The iteratees are invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {...(Function|Function[])} [iteratees=[_.identity]] - * The iteratees to sort by. - * @returns {Array} Returns the new sorted array. - * @example - * - * var users = [ - * { 'user': 'fred', 'age': 48 }, - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'barney', 'age': 34 } - * ]; - * - * _.sortBy(users, [function(o) { return o.user; }]); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] - * - * _.sortBy(users, ['user', 'age']); - * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] - */ - var sortBy = baseRest(function(collection, iteratees) { - if (collection == null) { - return []; - } - var length = iteratees.length; - if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) { - iteratees = []; - } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { - iteratees = [iteratees[0]]; - } - return baseOrderBy(collection, baseFlatten(iteratees, 1), []); - }); - - /*------------------------------------------------------------------------*/ - - /** - * Gets the timestamp of the number of milliseconds that have elapsed since - * the Unix epoch (1 January 1970 00:00:00 UTC). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Date - * @returns {number} Returns the timestamp. - * @example - * - * _.defer(function(stamp) { - * console.log(_.now() - stamp); - * }, _.now()); - * // => Logs the number of milliseconds it took for the deferred invocation. - */ - var now = ctxNow || function() { - return root.Date.now(); - }; - - /*------------------------------------------------------------------------*/ - - /** - * The opposite of `_.before`; this method creates a function that invokes - * `func` once it's called `n` or more times. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {number} n The number of calls before `func` is invoked. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var saves = ['profile', 'settings']; - * - * var done = _.after(saves.length, function() { - * console.log('done saving!'); - * }); - * - * _.forEach(saves, function(type) { - * asyncSave({ 'type': type, 'complete': done }); - * }); - * // => Logs 'done saving!' after the two async saves have completed. - */ - function after(n, func) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - n = toInteger(n); - return function() { - if (--n < 1) { - return func.apply(this, arguments); - } - }; - } - - /** - * Creates a function that invokes `func`, with up to `n` arguments, - * ignoring any additional arguments. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to cap arguments for. - * @param {number} [n=func.length] The arity cap. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new capped function. - * @example - * - * _.map(['6', '8', '10'], _.ary(parseInt, 1)); - * // => [6, 8, 10] - */ - function ary(func, n, guard) { - n = guard ? undefined : n; - n = (func && n == null) ? func.length : n; - return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n); - } - - /** - * Creates a function that invokes `func`, with the `this` binding and arguments - * of the created function, while it's called less than `n` times. Subsequent - * calls to the created function return the result of the last `func` invocation. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {number} n The number of calls at which `func` is no longer invoked. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * jQuery(element).on('click', _.before(5, addContactToList)); - * // => Allows adding up to 4 contacts to the list. - */ - function before(n, func) { - var result; - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - n = toInteger(n); - return function() { - if (--n > 0) { - result = func.apply(this, arguments); - } - if (n <= 1) { - func = undefined; - } - return result; - }; - } - - /** - * Creates a function that invokes `func` with the `this` binding of `thisArg` - * and `partials` prepended to the arguments it receives. - * - * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds, - * may be used as a placeholder for partially applied arguments. - * - * **Note:** Unlike native `Function#bind`, this method doesn't set the "length" - * property of bound functions. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to bind. - * @param {*} thisArg The `this` binding of `func`. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * function greet(greeting, punctuation) { - * return greeting + ' ' + this.user + punctuation; - * } - * - * var object = { 'user': 'fred' }; - * - * var bound = _.bind(greet, object, 'hi'); - * bound('!'); - * // => 'hi fred!' - * - * // Bound with placeholders. - * var bound = _.bind(greet, object, _, '!'); - * bound('hi'); - * // => 'hi fred!' - */ - var bind = baseRest(function(func, thisArg, partials) { - var bitmask = WRAP_BIND_FLAG; - if (partials.length) { - var holders = replaceHolders(partials, getHolder(bind)); - bitmask |= WRAP_PARTIAL_FLAG; - } - return createWrap(func, bitmask, thisArg, partials, holders); - }); - - /** - * Creates a function that invokes the method at `object[key]` with `partials` - * prepended to the arguments it receives. - * - * This method differs from `_.bind` by allowing bound functions to reference - * methods that may be redefined or don't yet exist. See - * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern) - * for more details. - * - * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Function - * @param {Object} object The object to invoke the method on. - * @param {string} key The key of the method. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var object = { - * 'user': 'fred', - * 'greet': function(greeting, punctuation) { - * return greeting + ' ' + this.user + punctuation; - * } - * }; - * - * var bound = _.bindKey(object, 'greet', 'hi'); - * bound('!'); - * // => 'hi fred!' - * - * object.greet = function(greeting, punctuation) { - * return greeting + 'ya ' + this.user + punctuation; - * }; - * - * bound('!'); - * // => 'hiya fred!' - * - * // Bound with placeholders. - * var bound = _.bindKey(object, 'greet', _, '!'); - * bound('hi'); - * // => 'hiya fred!' - */ - var bindKey = baseRest(function(object, key, partials) { - var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG; - if (partials.length) { - var holders = replaceHolders(partials, getHolder(bindKey)); - bitmask |= WRAP_PARTIAL_FLAG; - } - return createWrap(key, bitmask, object, partials, holders); - }); - - /** - * Creates a function that accepts arguments of `func` and either invokes - * `func` returning its result, if at least `arity` number of arguments have - * been provided, or returns a function that accepts the remaining `func` - * arguments, and so on. The arity of `func` may be specified if `func.length` - * is not sufficient. - * - * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, - * may be used as a placeholder for provided arguments. - * - * **Note:** This method doesn't set the "length" property of curried functions. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Function - * @param {Function} func The function to curry. - * @param {number} [arity=func.length] The arity of `func`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new curried function. - * @example - * - * var abc = function(a, b, c) { - * return [a, b, c]; - * }; - * - * var curried = _.curry(abc); - * - * curried(1)(2)(3); - * // => [1, 2, 3] - * - * curried(1, 2)(3); - * // => [1, 2, 3] - * - * curried(1, 2, 3); - * // => [1, 2, 3] - * - * // Curried with placeholders. - * curried(1)(_, 3)(2); - * // => [1, 2, 3] - */ - function curry(func, arity, guard) { - arity = guard ? undefined : arity; - var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity); - result.placeholder = curry.placeholder; - return result; - } - - /** - * This method is like `_.curry` except that arguments are applied to `func` - * in the manner of `_.partialRight` instead of `_.partial`. - * - * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for provided arguments. - * - * **Note:** This method doesn't set the "length" property of curried functions. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to curry. - * @param {number} [arity=func.length] The arity of `func`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new curried function. - * @example - * - * var abc = function(a, b, c) { - * return [a, b, c]; - * }; - * - * var curried = _.curryRight(abc); - * - * curried(3)(2)(1); - * // => [1, 2, 3] - * - * curried(2, 3)(1); - * // => [1, 2, 3] - * - * curried(1, 2, 3); - * // => [1, 2, 3] - * - * // Curried with placeholders. - * curried(3)(1, _)(2); - * // => [1, 2, 3] - */ - function curryRight(func, arity, guard) { - arity = guard ? undefined : arity; - var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity); - result.placeholder = curryRight.placeholder; - return result; - } - - /** - * Creates a debounced function that delays invoking `func` until after `wait` - * milliseconds have elapsed since the last time the debounced function was - * invoked. The debounced function comes with a `cancel` method to cancel - * delayed `func` invocations and a `flush` method to immediately invoke them. - * Provide `options` to indicate whether `func` should be invoked on the - * leading and/or trailing edge of the `wait` timeout. The `func` is invoked - * with the last arguments provided to the debounced function. Subsequent - * calls to the debounced function return the result of the last `func` - * invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the debounced function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.debounce` and `_.throttle`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to debounce. - * @param {number} [wait=0] The number of milliseconds to delay. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=false] - * Specify invoking on the leading edge of the timeout. - * @param {number} [options.maxWait] - * The maximum time `func` is allowed to be delayed before it's invoked. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * // Avoid costly calculations while the window size is in flux. - * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); - * - * // Invoke `sendMail` when clicked, debouncing subsequent calls. - * jQuery(element).on('click', _.debounce(sendMail, 300, { - * 'leading': true, - * 'trailing': false - * })); - * - * // Ensure `batchLog` is invoked once after 1 second of debounced calls. - * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); - * var source = new EventSource('/stream'); - * jQuery(source).on('message', debounced); - * - * // Cancel the trailing debounced invocation. - * jQuery(window).on('popstate', debounced.cancel); - */ - function debounce(func, wait, options) { - var lastArgs, - lastThis, - maxWait, - result, - timerId, - lastCallTime, - lastInvokeTime = 0, - leading = false, - maxing = false, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - wait = toNumber(wait) || 0; - if (isObject(options)) { - leading = !!options.leading; - maxing = 'maxWait' in options; - maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - - function invokeFunc(time) { - var args = lastArgs, - thisArg = lastThis; - - lastArgs = lastThis = undefined; - lastInvokeTime = time; - result = func.apply(thisArg, args); - return result; - } - - function leadingEdge(time) { - // Reset any `maxWait` timer. - lastInvokeTime = time; - // Start the timer for the trailing edge. - timerId = setTimeout(timerExpired, wait); - // Invoke the leading edge. - return leading ? invokeFunc(time) : result; - } - - function remainingWait(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime, - timeWaiting = wait - timeSinceLastCall; - - return maxing - ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) - : timeWaiting; - } - - function shouldInvoke(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return (lastCallTime === undefined || (timeSinceLastCall >= wait) || - (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); - } - - function timerExpired() { - var time = now(); - if (shouldInvoke(time)) { - return trailingEdge(time); - } - // Restart the timer. - timerId = setTimeout(timerExpired, remainingWait(time)); - } - - function trailingEdge(time) { - timerId = undefined; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (trailing && lastArgs) { - return invokeFunc(time); - } - lastArgs = lastThis = undefined; - return result; - } - - function cancel() { - if (timerId !== undefined) { - clearTimeout(timerId); - } - lastInvokeTime = 0; - lastArgs = lastCallTime = lastThis = timerId = undefined; - } - - function flush() { - return timerId === undefined ? result : trailingEdge(now()); - } - - function debounced() { - var time = now(), - isInvoking = shouldInvoke(time); - - lastArgs = arguments; - lastThis = this; - lastCallTime = time; - - if (isInvoking) { - if (timerId === undefined) { - return leadingEdge(lastCallTime); - } - if (maxing) { - // Handle invocations in a tight loop. - timerId = setTimeout(timerExpired, wait); - return invokeFunc(lastCallTime); - } - } - if (timerId === undefined) { - timerId = setTimeout(timerExpired, wait); - } - return result; - } - debounced.cancel = cancel; - debounced.flush = flush; - return debounced; - } - - /** - * Defers invoking the `func` until the current call stack has cleared. Any - * additional arguments are provided to `func` when it's invoked. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to defer. - * @param {...*} [args] The arguments to invoke `func` with. - * @returns {number} Returns the timer id. - * @example - * - * _.defer(function(text) { - * console.log(text); - * }, 'deferred'); - * // => Logs 'deferred' after one millisecond. - */ - var defer = baseRest(function(func, args) { - return baseDelay(func, 1, args); - }); - - /** - * Invokes `func` after `wait` milliseconds. Any additional arguments are - * provided to `func` when it's invoked. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @param {...*} [args] The arguments to invoke `func` with. - * @returns {number} Returns the timer id. - * @example - * - * _.delay(function(text) { - * console.log(text); - * }, 1000, 'later'); - * // => Logs 'later' after one second. - */ - var delay = baseRest(function(func, wait, args) { - return baseDelay(func, toNumber(wait) || 0, args); - }); - - /** - * Creates a function that invokes `func` with arguments reversed. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to flip arguments for. - * @returns {Function} Returns the new flipped function. - * @example - * - * var flipped = _.flip(function() { - * return _.toArray(arguments); - * }); - * - * flipped('a', 'b', 'c', 'd'); - * // => ['d', 'c', 'b', 'a'] - */ - function flip(func) { - return createWrap(func, WRAP_FLIP_FLAG); - } - - /** - * Creates a function that memoizes the result of `func`. If `resolver` is - * provided, it determines the cache key for storing the result based on the - * arguments provided to the memoized function. By default, the first argument - * provided to the memoized function is used as the map cache key. The `func` - * is invoked with the `this` binding of the memoized function. - * - * **Note:** The cache is exposed as the `cache` property on the memoized - * function. Its creation may be customized by replacing the `_.memoize.Cache` - * constructor with one whose instances implement the - * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) - * method interface of `clear`, `delete`, `get`, `has`, and `set`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] The function to resolve the cache key. - * @returns {Function} Returns the new memoized function. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * var other = { 'c': 3, 'd': 4 }; - * - * var values = _.memoize(_.values); - * values(object); - * // => [1, 2] - * - * values(other); - * // => [3, 4] - * - * object.a = 2; - * values(object); - * // => [1, 2] - * - * // Modify the result cache. - * values.cache.set(object, ['a', 'b']); - * values(object); - * // => ['a', 'b'] - * - * // Replace `_.memoize.Cache`. - * _.memoize.Cache = WeakMap; - */ - function memoize(func, resolver) { - if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { - throw new TypeError(FUNC_ERROR_TEXT); - } - var memoized = function() { - var args = arguments, - key = resolver ? resolver.apply(this, args) : args[0], - cache = memoized.cache; - - if (cache.has(key)) { - return cache.get(key); - } - var result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; - return result; - }; - memoized.cache = new (memoize.Cache || MapCache); - return memoized; - } - - // Expose `MapCache`. - memoize.Cache = MapCache; - - /** - * Creates a function that negates the result of the predicate `func`. The - * `func` predicate is invoked with the `this` binding and arguments of the - * created function. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} predicate The predicate to negate. - * @returns {Function} Returns the new negated function. - * @example - * - * function isEven(n) { - * return n % 2 == 0; - * } - * - * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); - * // => [1, 3, 5] - */ - function negate(predicate) { - if (typeof predicate != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - return function() { - var args = arguments; - switch (args.length) { - case 0: return !predicate.call(this); - case 1: return !predicate.call(this, args[0]); - case 2: return !predicate.call(this, args[0], args[1]); - case 3: return !predicate.call(this, args[0], args[1], args[2]); - } - return !predicate.apply(this, args); - }; - } - - /** - * Creates a function that is restricted to invoking `func` once. Repeat calls - * to the function return the value of the first invocation. The `func` is - * invoked with the `this` binding and arguments of the created function. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // => `createApplication` is invoked once - */ - function once(func) { - return before(2, func); - } - - /** - * Creates a function that invokes `func` with its arguments transformed. - * - * @static - * @since 4.0.0 - * @memberOf _ - * @category Function - * @param {Function} func The function to wrap. - * @param {...(Function|Function[])} [transforms=[_.identity]] - * The argument transforms. - * @returns {Function} Returns the new function. - * @example - * - * function doubled(n) { - * return n * 2; - * } - * - * function square(n) { - * return n * n; - * } - * - * var func = _.overArgs(function(x, y) { - * return [x, y]; - * }, [square, doubled]); - * - * func(9, 3); - * // => [81, 6] - * - * func(10, 5); - * // => [100, 10] - */ - var overArgs = castRest(function(func, transforms) { - transforms = (transforms.length == 1 && isArray(transforms[0])) - ? arrayMap(transforms[0], baseUnary(getIteratee())) - : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); - - var funcsLength = transforms.length; - return baseRest(function(args) { - var index = -1, - length = nativeMin(args.length, funcsLength); - - while (++index < length) { - args[index] = transforms[index].call(this, args[index]); - } - return apply(func, this, args); - }); - }); - - /** - * Creates a function that invokes `func` with `partials` prepended to the - * arguments it receives. This method is like `_.bind` except it does **not** - * alter the `this` binding. - * - * The `_.partial.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * **Note:** This method doesn't set the "length" property of partially - * applied functions. - * - * @static - * @memberOf _ - * @since 0.2.0 - * @category Function - * @param {Function} func The function to partially apply arguments to. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * function greet(greeting, name) { - * return greeting + ' ' + name; - * } - * - * var sayHelloTo = _.partial(greet, 'hello'); - * sayHelloTo('fred'); - * // => 'hello fred' - * - * // Partially applied with placeholders. - * var greetFred = _.partial(greet, _, 'fred'); - * greetFred('hi'); - * // => 'hi fred' - */ - var partial = baseRest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partial)); - return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders); - }); - - /** - * This method is like `_.partial` except that partially applied arguments - * are appended to the arguments it receives. - * - * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * **Note:** This method doesn't set the "length" property of partially - * applied functions. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Function - * @param {Function} func The function to partially apply arguments to. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * function greet(greeting, name) { - * return greeting + ' ' + name; - * } - * - * var greetFred = _.partialRight(greet, 'fred'); - * greetFred('hi'); - * // => 'hi fred' - * - * // Partially applied with placeholders. - * var sayHelloTo = _.partialRight(greet, 'hello', _); - * sayHelloTo('fred'); - * // => 'hello fred' - */ - var partialRight = baseRest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partialRight)); - return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders); - }); - - /** - * Creates a function that invokes `func` with arguments arranged according - * to the specified `indexes` where the argument value at the first index is - * provided as the first argument, the argument value at the second index is - * provided as the second argument, and so on. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to rearrange arguments for. - * @param {...(number|number[])} indexes The arranged argument indexes. - * @returns {Function} Returns the new function. - * @example - * - * var rearged = _.rearg(function(a, b, c) { - * return [a, b, c]; - * }, [2, 0, 1]); - * - * rearged('b', 'c', 'a') - * // => ['a', 'b', 'c'] - */ - var rearg = flatRest(function(func, indexes) { - return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes); - }); - - /** - * Creates a function that invokes `func` with the `this` binding of the - * created function and arguments from `start` and beyond provided as - * an array. - * - * **Note:** This method is based on the - * [rest parameter](https://mdn.io/rest_parameters). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @returns {Function} Returns the new function. - * @example - * - * var say = _.rest(function(what, names) { - * return what + ' ' + _.initial(names).join(', ') + - * (_.size(names) > 1 ? ', & ' : '') + _.last(names); - * }); - * - * say('hello', 'fred', 'barney', 'pebbles'); - * // => 'hello fred, barney, & pebbles' - */ - function rest(func, start) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - start = start === undefined ? start : toInteger(start); - return baseRest(func, start); - } - - /** - * Creates a function that invokes `func` with the `this` binding of the - * create function and an array of arguments much like - * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). - * - * **Note:** This method is based on the - * [spread operator](https://mdn.io/spread_operator). - * - * @static - * @memberOf _ - * @since 3.2.0 - * @category Function - * @param {Function} func The function to spread arguments over. - * @param {number} [start=0] The start position of the spread. - * @returns {Function} Returns the new function. - * @example - * - * var say = _.spread(function(who, what) { - * return who + ' says ' + what; - * }); - * - * say(['fred', 'hello']); - * // => 'fred says hello' - * - * var numbers = Promise.all([ - * Promise.resolve(40), - * Promise.resolve(36) - * ]); - * - * numbers.then(_.spread(function(x, y) { - * return x + y; - * })); - * // => a Promise of 76 - */ - function spread(func, start) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - start = start == null ? 0 : nativeMax(toInteger(start), 0); - return baseRest(function(args) { - var array = args[start], - otherArgs = castSlice(args, 0, start); - - if (array) { - arrayPush(otherArgs, array); - } - return apply(func, this, otherArgs); - }); - } - - /** - * Creates a throttled function that only invokes `func` at most once per - * every `wait` milliseconds. The throttled function comes with a `cancel` - * method to cancel delayed `func` invocations and a `flush` method to - * immediately invoke them. Provide `options` to indicate whether `func` - * should be invoked on the leading and/or trailing edge of the `wait` - * timeout. The `func` is invoked with the last arguments provided to the - * throttled function. Subsequent calls to the throttled function return the - * result of the last `func` invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the throttled function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.throttle` and `_.debounce`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to throttle. - * @param {number} [wait=0] The number of milliseconds to throttle invocations to. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=true] - * Specify invoking on the leading edge of the timeout. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new throttled function. - * @example - * - * // Avoid excessively updating the position while scrolling. - * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); - * - * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. - * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); - * jQuery(element).on('click', throttled); - * - * // Cancel the trailing throttled invocation. - * jQuery(window).on('popstate', throttled.cancel); - */ - function throttle(func, wait, options) { - var leading = true, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (isObject(options)) { - leading = 'leading' in options ? !!options.leading : leading; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - return debounce(func, wait, { - 'leading': leading, - 'maxWait': wait, - 'trailing': trailing - }); - } - - /** - * Creates a function that accepts up to one argument, ignoring any - * additional arguments. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. - * @example - * - * _.map(['6', '8', '10'], _.unary(parseInt)); - * // => [6, 8, 10] - */ - function unary(func) { - return ary(func, 1); - } - - /** - * Creates a function that provides `value` to `wrapper` as its first - * argument. Any additional arguments provided to the function are appended - * to those provided to the `wrapper`. The wrapper is invoked with the `this` - * binding of the created function. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {*} value The value to wrap. - * @param {Function} [wrapper=identity] The wrapper function. - * @returns {Function} Returns the new function. - * @example - * - * var p = _.wrap(_.escape, function(func, text) { - * return '

' + func(text) + '

'; - * }); - * - * p('fred, barney, & pebbles'); - * // => '

fred, barney, & pebbles

' - */ - function wrap(value, wrapper) { - return partial(castFunction(wrapper), value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Casts `value` as an array if it's not one. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Lang - * @param {*} value The value to inspect. - * @returns {Array} Returns the cast array. - * @example - * - * _.castArray(1); - * // => [1] - * - * _.castArray({ 'a': 1 }); - * // => [{ 'a': 1 }] - * - * _.castArray('abc'); - * // => ['abc'] - * - * _.castArray(null); - * // => [null] - * - * _.castArray(undefined); - * // => [undefined] - * - * _.castArray(); - * // => [] - * - * var array = [1, 2, 3]; - * console.log(_.castArray(array) === array); - * // => true - */ - function castArray() { - if (!arguments.length) { - return []; - } - var value = arguments[0]; - return isArray(value) ? value : [value]; - } - - /** - * Creates a shallow clone of `value`. - * - * **Note:** This method is loosely based on the - * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) - * and supports cloning arrays, array buffers, booleans, date objects, maps, - * numbers, `Object` objects, regexes, sets, strings, symbols, and typed - * arrays. The own enumerable properties of `arguments` objects are cloned - * as plain objects. An empty object is returned for uncloneable values such - * as error objects, functions, DOM nodes, and WeakMaps. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to clone. - * @returns {*} Returns the cloned value. - * @see _.cloneDeep - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var shallow = _.clone(objects); - * console.log(shallow[0] === objects[0]); - * // => true - */ - function clone(value) { - return baseClone(value, CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.clone` except that it accepts `customizer` which - * is invoked to produce the cloned value. If `customizer` returns `undefined`, - * cloning is handled by the method instead. The `customizer` is invoked with - * up to four arguments; (value [, index|key, object, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the cloned value. - * @see _.cloneDeepWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(false); - * } - * } - * - * var el = _.cloneWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 0 - */ - function cloneWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * This method is like `_.clone` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @returns {*} Returns the deep cloned value. - * @see _.clone - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var deep = _.cloneDeep(objects); - * console.log(deep[0] === objects[0]); - * // => false - */ - function cloneDeep(value) { - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.cloneWith` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the deep cloned value. - * @see _.cloneWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(true); - * } - * } - * - * var el = _.cloneDeepWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 20 - */ - function cloneDeepWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * Checks if `object` conforms to `source` by invoking the predicate - * properties of `source` with the corresponding property values of `object`. - * - * **Note:** This method is equivalent to `_.conforms` when `source` is - * partially applied. - * - * @static - * @memberOf _ - * @since 4.14.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); - * // => true - * - * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); - * // => false - */ - function conformsTo(object, source) { - return source == null || baseConformsTo(object, source, keys(source)); - } - - /** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.eq(object, object); - * // => true - * - * _.eq(object, other); - * // => false - * - * _.eq('a', 'a'); - * // => true - * - * _.eq('a', Object('a')); - * // => false - * - * _.eq(NaN, NaN); - * // => true - */ - function eq(value, other) { - return value === other || (value !== value && other !== other); - } - - /** - * Checks if `value` is greater than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - * @see _.lt - * @example - * - * _.gt(3, 1); - * // => true - * - * _.gt(3, 3); - * // => false - * - * _.gt(1, 3); - * // => false - */ - var gt = createRelationalOperation(baseGt); - - /** - * Checks if `value` is greater than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than or equal to - * `other`, else `false`. - * @see _.lte - * @example - * - * _.gte(3, 1); - * // => true - * - * _.gte(3, 3); - * // => true - * - * _.gte(1, 3); - * // => false - */ - var gte = createRelationalOperation(function(value, other) { - return value >= other; - }); - - /** - * Checks if `value` is likely an `arguments` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - * else `false`. - * @example - * - * _.isArguments(function() { return arguments; }()); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { - return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && - !propertyIsEnumerable.call(value, 'callee'); - }; - - /** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(document.body.children); - * // => false - * - * _.isArray('abc'); - * // => false - * - * _.isArray(_.noop); - * // => false - */ - var isArray = Array.isArray; - - /** - * Checks if `value` is classified as an `ArrayBuffer` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - * @example - * - * _.isArrayBuffer(new ArrayBuffer(2)); - * // => true - * - * _.isArrayBuffer(new Array(2)); - * // => false - */ - var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; - - /** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * - * _.isArrayLike([1, 2, 3]); - * // => true - * - * _.isArrayLike(document.body.children); - * // => true - * - * _.isArrayLike('abc'); - * // => true - * - * _.isArrayLike(_.noop); - * // => false - */ - function isArrayLike(value) { - return value != null && isLength(value.length) && !isFunction(value); - } - - /** - * This method is like `_.isArrayLike` except that it also checks if `value` - * is an object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array-like object, - * else `false`. - * @example - * - * _.isArrayLikeObject([1, 2, 3]); - * // => true - * - * _.isArrayLikeObject(document.body.children); - * // => true - * - * _.isArrayLikeObject('abc'); - * // => false - * - * _.isArrayLikeObject(_.noop); - * // => false - */ - function isArrayLikeObject(value) { - return isObjectLike(value) && isArrayLike(value); - } - - /** - * Checks if `value` is classified as a boolean primitive or object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. - * @example - * - * _.isBoolean(false); - * // => true - * - * _.isBoolean(null); - * // => false - */ - function isBoolean(value) { - return value === true || value === false || - (isObjectLike(value) && baseGetTag(value) == boolTag); - } - - /** - * Checks if `value` is a buffer. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. - * @example - * - * _.isBuffer(new Buffer(2)); - * // => true - * - * _.isBuffer(new Uint8Array(2)); - * // => false - */ - var isBuffer = nativeIsBuffer || stubFalse; - - /** - * Checks if `value` is classified as a `Date` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - * @example - * - * _.isDate(new Date); - * // => true - * - * _.isDate('Mon April 23 2012'); - * // => false - */ - var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; - - /** - * Checks if `value` is likely a DOM element. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. - * @example - * - * _.isElement(document.body); - * // => true - * - * _.isElement(''); - * // => false - */ - function isElement(value) { - return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); - } - - /** - * Checks if `value` is an empty object, collection, map, or set. - * - * Objects are considered empty if they have no own enumerable string keyed - * properties. - * - * Array-like values such as `arguments` objects, arrays, buffers, strings, or - * jQuery-like collections are considered empty if they have a `length` of `0`. - * Similarly, maps and sets are considered empty if they have a `size` of `0`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is empty, else `false`. - * @example - * - * _.isEmpty(null); - * // => true - * - * _.isEmpty(true); - * // => true - * - * _.isEmpty(1); - * // => true - * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({ 'a': 1 }); - * // => false - */ - function isEmpty(value) { - if (value == null) { - return true; - } - if (isArrayLike(value) && - (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || - isBuffer(value) || isTypedArray(value) || isArguments(value))) { - return !value.length; - } - var tag = getTag(value); - if (tag == mapTag || tag == setTag) { - return !value.size; - } - if (isPrototype(value)) { - return !baseKeys(value).length; - } - for (var key in value) { - if (hasOwnProperty.call(value, key)) { - return false; - } - } - return true; - } - - /** - * Performs a deep comparison between two values to determine if they are - * equivalent. - * - * **Note:** This method supports comparing arrays, array buffers, booleans, - * date objects, error objects, maps, numbers, `Object` objects, regexes, - * sets, strings, symbols, and typed arrays. `Object` objects are compared - * by their own, not inherited, enumerable properties. Functions and DOM - * nodes are compared by strict equality, i.e. `===`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.isEqual(object, other); - * // => true - * - * object === other; - * // => false - */ - function isEqual(value, other) { - return baseIsEqual(value, other); - } - - /** - * This method is like `_.isEqual` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with up to - * six arguments: (objValue, othValue [, index|key, object, other, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, othValue) { - * if (isGreeting(objValue) && isGreeting(othValue)) { - * return true; - * } - * } - * - * var array = ['hello', 'goodbye']; - * var other = ['hi', 'goodbye']; - * - * _.isEqualWith(array, other, customizer); - * // => true - */ - function isEqualWith(value, other, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - var result = customizer ? customizer(value, other) : undefined; - return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; - } - - /** - * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, - * `SyntaxError`, `TypeError`, or `URIError` object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an error object, else `false`. - * @example - * - * _.isError(new Error); - * // => true - * - * _.isError(Error); - * // => false - */ - function isError(value) { - if (!isObjectLike(value)) { - return false; - } - var tag = baseGetTag(value); - return tag == errorTag || tag == domExcTag || - (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value)); - } - - /** - * Checks if `value` is a finite primitive number. - * - * **Note:** This method is based on - * [`Number.isFinite`](https://mdn.io/Number/isFinite). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. - * @example - * - * _.isFinite(3); - * // => true - * - * _.isFinite(Number.MIN_VALUE); - * // => true - * - * _.isFinite(Infinity); - * // => false - * - * _.isFinite('3'); - * // => false - */ - function isFinite(value) { - return typeof value == 'number' && nativeIsFinite(value); - } - - /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ - function isFunction(value) { - if (!isObject(value)) { - return false; - } - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 9 which returns 'object' for typed arrays and other constructors. - var tag = baseGetTag(value); - return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; - } - - /** - * Checks if `value` is an integer. - * - * **Note:** This method is based on - * [`Number.isInteger`](https://mdn.io/Number/isInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an integer, else `false`. - * @example - * - * _.isInteger(3); - * // => true - * - * _.isInteger(Number.MIN_VALUE); - * // => false - * - * _.isInteger(Infinity); - * // => false - * - * _.isInteger('3'); - * // => false - */ - function isInteger(value) { - return typeof value == 'number' && value == toInteger(value); - } - - /** - * Checks if `value` is a valid array-like length. - * - * **Note:** This method is loosely based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. - * @example - * - * _.isLength(3); - * // => true - * - * _.isLength(Number.MIN_VALUE); - * // => false - * - * _.isLength(Infinity); - * // => false - * - * _.isLength('3'); - * // => false - */ - function isLength(value) { - return typeof value == 'number' && - value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ - function isObject(value) { - var type = typeof value; - return value != null && (type == 'object' || type == 'function'); - } - - /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ - function isObjectLike(value) { - return value != null && typeof value == 'object'; - } - - /** - * Checks if `value` is classified as a `Map` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - * @example - * - * _.isMap(new Map); - * // => true - * - * _.isMap(new WeakMap); - * // => false - */ - var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; - - /** - * Performs a partial deep comparison between `object` and `source` to - * determine if `object` contains equivalent property values. - * - * **Note:** This method is equivalent to `_.matches` when `source` is - * partially applied. - * - * Partial comparisons will match empty array and empty object `source` - * values against any array or object value, respectively. See `_.isEqual` - * for a list of supported value comparisons. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.isMatch(object, { 'b': 2 }); - * // => true - * - * _.isMatch(object, { 'b': 1 }); - * // => false - */ - function isMatch(object, source) { - return object === source || baseIsMatch(object, source, getMatchData(source)); - } - - /** - * This method is like `_.isMatch` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with five - * arguments: (objValue, srcValue, index|key, object, source). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, srcValue) { - * if (isGreeting(objValue) && isGreeting(srcValue)) { - * return true; - * } - * } - * - * var object = { 'greeting': 'hello' }; - * var source = { 'greeting': 'hi' }; - * - * _.isMatchWith(object, source, customizer); - * // => true - */ - function isMatchWith(object, source, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseIsMatch(object, source, getMatchData(source), customizer); - } - - /** - * Checks if `value` is `NaN`. - * - * **Note:** This method is based on - * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as - * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for - * `undefined` and other non-number values. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. - * @example - * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true - * - * _.isNaN(undefined); - * // => false - */ - function isNaN(value) { - // An `NaN` primitive is the only value that is not equal to itself. - // Perform the `toStringTag` check first to avoid errors with some - // ActiveX objects in IE. - return isNumber(value) && value != +value; - } - - /** - * Checks if `value` is a pristine native function. - * - * **Note:** This method can't reliably detect native functions in the presence - * of the core-js package because core-js circumvents this kind of detection. - * Despite multiple requests, the core-js maintainer has made it clear: any - * attempt to fix the detection will be obstructed. As a result, we're left - * with little choice but to throw an error. Unfortunately, this also affects - * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), - * which rely on core-js. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - * @example - * - * _.isNative(Array.prototype.push); - * // => true - * - * _.isNative(_); - * // => false - */ - function isNative(value) { - if (isMaskable(value)) { - throw new Error(CORE_ERROR_TEXT); - } - return baseIsNative(value); - } - - /** - * Checks if `value` is `null`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `null`, else `false`. - * @example - * - * _.isNull(null); - * // => true - * - * _.isNull(void 0); - * // => false - */ - function isNull(value) { - return value === null; - } - - /** - * Checks if `value` is `null` or `undefined`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is nullish, else `false`. - * @example - * - * _.isNil(null); - * // => true - * - * _.isNil(void 0); - * // => true - * - * _.isNil(NaN); - * // => false - */ - function isNil(value) { - return value == null; - } - - /** - * Checks if `value` is classified as a `Number` primitive or object. - * - * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are - * classified as numbers, use the `_.isFinite` method. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a number, else `false`. - * @example - * - * _.isNumber(3); - * // => true - * - * _.isNumber(Number.MIN_VALUE); - * // => true - * - * _.isNumber(Infinity); - * // => true - * - * _.isNumber('3'); - * // => false - */ - function isNumber(value) { - return typeof value == 'number' || - (isObjectLike(value) && baseGetTag(value) == numberTag); - } - - /** - * Checks if `value` is a plain object, that is, an object created by the - * `Object` constructor or one with a `[[Prototype]]` of `null`. - * - * @static - * @memberOf _ - * @since 0.8.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * _.isPlainObject(new Foo); - * // => false - * - * _.isPlainObject([1, 2, 3]); - * // => false - * - * _.isPlainObject({ 'x': 0, 'y': 0 }); - * // => true - * - * _.isPlainObject(Object.create(null)); - * // => true - */ - function isPlainObject(value) { - if (!isObjectLike(value) || baseGetTag(value) != objectTag) { - return false; - } - var proto = getPrototype(value); - if (proto === null) { - return true; - } - var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; - return typeof Ctor == 'function' && Ctor instanceof Ctor && - funcToString.call(Ctor) == objectCtorString; - } - - /** - * Checks if `value` is classified as a `RegExp` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - * @example - * - * _.isRegExp(/abc/); - * // => true - * - * _.isRegExp('/abc/'); - * // => false - */ - var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; - - /** - * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 - * double precision number which isn't the result of a rounded unsafe integer. - * - * **Note:** This method is based on - * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. - * @example - * - * _.isSafeInteger(3); - * // => true - * - * _.isSafeInteger(Number.MIN_VALUE); - * // => false - * - * _.isSafeInteger(Infinity); - * // => false - * - * _.isSafeInteger('3'); - * // => false - */ - function isSafeInteger(value) { - return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is classified as a `Set` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - * @example - * - * _.isSet(new Set); - * // => true - * - * _.isSet(new WeakSet); - * // => false - */ - var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; - - /** - * Checks if `value` is classified as a `String` primitive or object. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a string, else `false`. - * @example - * - * _.isString('abc'); - * // => true - * - * _.isString(1); - * // => false - */ - function isString(value) { - return typeof value == 'string' || - (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag); - } - - /** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ - function isSymbol(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && baseGetTag(value) == symbolTag); - } - - /** - * Checks if `value` is classified as a typed array. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - * @example - * - * _.isTypedArray(new Uint8Array); - * // => true - * - * _.isTypedArray([]); - * // => false - */ - var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; - - /** - * Checks if `value` is `undefined`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. - * @example - * - * _.isUndefined(void 0); - * // => true - * - * _.isUndefined(null); - * // => false - */ - function isUndefined(value) { - return value === undefined; - } - - /** - * Checks if `value` is classified as a `WeakMap` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. - * @example - * - * _.isWeakMap(new WeakMap); - * // => true - * - * _.isWeakMap(new Map); - * // => false - */ - function isWeakMap(value) { - return isObjectLike(value) && getTag(value) == weakMapTag; - } - - /** - * Checks if `value` is classified as a `WeakSet` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. - * @example - * - * _.isWeakSet(new WeakSet); - * // => true - * - * _.isWeakSet(new Set); - * // => false - */ - function isWeakSet(value) { - return isObjectLike(value) && baseGetTag(value) == weakSetTag; - } - - /** - * Checks if `value` is less than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - * @see _.gt - * @example - * - * _.lt(1, 3); - * // => true - * - * _.lt(3, 3); - * // => false - * - * _.lt(3, 1); - * // => false - */ - var lt = createRelationalOperation(baseLt); - - /** - * Checks if `value` is less than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than or equal to - * `other`, else `false`. - * @see _.gte - * @example - * - * _.lte(1, 3); - * // => true - * - * _.lte(3, 3); - * // => true - * - * _.lte(3, 1); - * // => false - */ - var lte = createRelationalOperation(function(value, other) { - return value <= other; - }); - - /** - * Converts `value` to an array. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to convert. - * @returns {Array} Returns the converted array. - * @example - * - * _.toArray({ 'a': 1, 'b': 2 }); - * // => [1, 2] - * - * _.toArray('abc'); - * // => ['a', 'b', 'c'] - * - * _.toArray(1); - * // => [] - * - * _.toArray(null); - * // => [] - */ - function toArray(value) { - if (!value) { - return []; - } - if (isArrayLike(value)) { - return isString(value) ? stringToArray(value) : copyArray(value); - } - if (symIterator && value[symIterator]) { - return iteratorToArray(value[symIterator]()); - } - var tag = getTag(value), - func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values); - - return func(value); - } - - /** - * Converts `value` to a finite number. - * - * @static - * @memberOf _ - * @since 4.12.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted number. - * @example - * - * _.toFinite(3.2); - * // => 3.2 - * - * _.toFinite(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toFinite(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toFinite('3.2'); - * // => 3.2 - */ - function toFinite(value) { - if (!value) { - return value === 0 ? value : 0; - } - value = toNumber(value); - if (value === INFINITY || value === -INFINITY) { - var sign = (value < 0 ? -1 : 1); - return sign * MAX_INTEGER; - } - return value === value ? value : 0; - } - - /** - * Converts `value` to an integer. - * - * **Note:** This method is loosely based on - * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toInteger(3.2); - * // => 3 - * - * _.toInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toInteger(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toInteger('3.2'); - * // => 3 - */ - function toInteger(value) { - var result = toFinite(value), - remainder = result % 1; - - return result === result ? (remainder ? result - remainder : result) : 0; - } - - /** - * Converts `value` to an integer suitable for use as the length of an - * array-like object. - * - * **Note:** This method is based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toLength(3.2); - * // => 3 - * - * _.toLength(Number.MIN_VALUE); - * // => 0 - * - * _.toLength(Infinity); - * // => 4294967295 - * - * _.toLength('3.2'); - * // => 3 - */ - function toLength(value) { - return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; - } - - /** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ - function toNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject(other) ? (other + '') : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = value.replace(reTrim, ''); - var isBinary = reIsBinary.test(value); - return (isBinary || reIsOctal.test(value)) - ? freeParseInt(value.slice(2), isBinary ? 2 : 8) - : (reIsBadHex.test(value) ? NAN : +value); - } - - /** - * Converts `value` to a plain object flattening inherited enumerable string - * keyed properties of `value` to own properties of the plain object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {Object} Returns the converted plain object. - * @example - * - * function Foo() { - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.assign({ 'a': 1 }, new Foo); - * // => { 'a': 1, 'b': 2 } - * - * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); - * // => { 'a': 1, 'b': 2, 'c': 3 } - */ - function toPlainObject(value) { - return copyObject(value, keysIn(value)); - } - - /** - * Converts `value` to a safe integer. A safe integer can be compared and - * represented correctly. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toSafeInteger(3.2); - * // => 3 - * - * _.toSafeInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toSafeInteger(Infinity); - * // => 9007199254740991 - * - * _.toSafeInteger('3.2'); - * // => 3 - */ - function toSafeInteger(value) { - return value - ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) - : (value === 0 ? value : 0); - } - - /** - * Converts `value` to a string. An empty string is returned for `null` - * and `undefined` values. The sign of `-0` is preserved. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.toString(null); - * // => '' - * - * _.toString(-0); - * // => '-0' - * - * _.toString([1, 2, 3]); - * // => '1,2,3' - */ - function toString(value) { - return value == null ? '' : baseToString(value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Assigns own enumerable string keyed properties of source objects to the - * destination object. Source objects are applied from left to right. - * Subsequent sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object` and is loosely based on - * [`Object.assign`](https://mdn.io/Object/assign). - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assignIn - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assign({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'c': 3 } - */ - var assign = createAssigner(function(object, source) { - if (isPrototype(source) || isArrayLike(source)) { - copyObject(source, keys(source), object); - return; - } - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - assignValue(object, key, source[key]); - } - } - }); - - /** - * This method is like `_.assign` except that it iterates over own and - * inherited source properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extend - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assign - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assignIn({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } - */ - var assignIn = createAssigner(function(object, source) { - copyObject(source, keysIn(source), object); - }); - - /** - * This method is like `_.assignIn` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extendWith - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignInWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { - copyObject(source, keysIn(source), object, customizer); - }); - - /** - * This method is like `_.assign` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignInWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignWith = createAssigner(function(object, source, srcIndex, customizer) { - copyObject(source, keys(source), object, customizer); - }); - - /** - * Creates an array of values corresponding to `paths` of `object`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Array} Returns the picked values. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _.at(object, ['a[0].b.c', 'a[1]']); - * // => [3, 4] - */ - var at = flatRest(baseAt); - - /** - * Creates an object that inherits from the `prototype` object. If a - * `properties` object is given, its own enumerable string keyed properties - * are assigned to the created object. - * - * @static - * @memberOf _ - * @since 2.3.0 - * @category Object - * @param {Object} prototype The object to inherit from. - * @param {Object} [properties] The properties to assign to the object. - * @returns {Object} Returns the new object. - * @example - * - * function Shape() { - * this.x = 0; - * this.y = 0; - * } - * - * function Circle() { - * Shape.call(this); - * } - * - * Circle.prototype = _.create(Shape.prototype, { - * 'constructor': Circle - * }); - * - * var circle = new Circle; - * circle instanceof Circle; - * // => true - * - * circle instanceof Shape; - * // => true - */ - function create(prototype, properties) { - var result = baseCreate(prototype); - return properties == null ? result : baseAssign(result, properties); - } - - /** - * Assigns own and inherited enumerable string keyed properties of source - * objects to the destination object for all destination properties that - * resolve to `undefined`. Source objects are applied from left to right. - * Once a property is set, additional values of the same property are ignored. - * - * **Note:** This method mutates `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaultsDeep - * @example - * - * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var defaults = baseRest(function(object, sources) { - object = Object(object); - - var index = -1; - var length = sources.length; - var guard = length > 2 ? sources[2] : undefined; - - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - length = 1; - } - - while (++index < length) { - var source = sources[index]; - var props = keysIn(source); - var propsIndex = -1; - var propsLength = props.length; - - while (++propsIndex < propsLength) { - var key = props[propsIndex]; - var value = object[key]; - - if (value === undefined || - (eq(value, objectProto[key]) && !hasOwnProperty.call(object, key))) { - object[key] = source[key]; - } - } - } - - return object; - }); - - /** - * This method is like `_.defaults` except that it recursively assigns - * default properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaults - * @example - * - * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); - * // => { 'a': { 'b': 2, 'c': 3 } } - */ - var defaultsDeep = baseRest(function(args) { - args.push(undefined, customDefaultsMerge); - return apply(mergeWith, undefined, args); - }); - - /** - * This method is like `_.find` except that it returns the key of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findKey(users, function(o) { return o.age < 40; }); - * // => 'barney' (iteration order is not guaranteed) - * - * // The `_.matches` iteratee shorthand. - * _.findKey(users, { 'age': 1, 'active': true }); - * // => 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findKey(users, 'active'); - * // => 'barney' - */ - function findKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); - } - - /** - * This method is like `_.findKey` except that it iterates over elements of - * a collection in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findLastKey(users, function(o) { return o.age < 40; }); - * // => returns 'pebbles' assuming `_.findKey` returns 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.findLastKey(users, { 'age': 36, 'active': true }); - * // => 'barney' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findLastKey(users, 'active'); - * // => 'pebbles' - */ - function findLastKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); - } - - /** - * Iterates over own and inherited enumerable string keyed properties of an - * object and invokes `iteratee` for each property. The iteratee is invoked - * with three arguments: (value, key, object). Iteratee functions may exit - * iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forInRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forIn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). - */ - function forIn(object, iteratee) { - return object == null - ? object - : baseFor(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * This method is like `_.forIn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forIn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forInRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. - */ - function forInRight(object, iteratee) { - return object == null - ? object - : baseForRight(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * Iterates over own enumerable string keyed properties of an object and - * invokes `iteratee` for each property. The iteratee is invoked with three - * arguments: (value, key, object). Iteratee functions may exit iteration - * early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwnRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forOwn(object, iteratee) { - return object && baseForOwn(object, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forOwn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwnRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. - */ - function forOwnRight(object, iteratee) { - return object && baseForOwnRight(object, getIteratee(iteratee, 3)); - } - - /** - * Creates an array of function property names from own enumerable properties - * of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functionsIn - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functions(new Foo); - * // => ['a', 'b'] - */ - function functions(object) { - return object == null ? [] : baseFunctions(object, keys(object)); - } - - /** - * Creates an array of function property names from own and inherited - * enumerable properties of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functions - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functionsIn(new Foo); - * // => ['a', 'b', 'c'] - */ - function functionsIn(object) { - return object == null ? [] : baseFunctions(object, keysIn(object)); - } - - /** - * Gets the value at `path` of `object`. If the resolved value is - * `undefined`, the `defaultValue` is returned in its place. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.get(object, 'a[0].b.c'); - * // => 3 - * - * _.get(object, ['a', '0', 'b', 'c']); - * // => 3 - * - * _.get(object, 'a.b.c', 'default'); - * // => 'default' - */ - function get(object, path, defaultValue) { - var result = object == null ? undefined : baseGet(object, path); - return result === undefined ? defaultValue : result; - } - - /** - * Checks if `path` is a direct property of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = { 'a': { 'b': 2 } }; - * var other = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.has(object, 'a'); - * // => true - * - * _.has(object, 'a.b'); - * // => true - * - * _.has(object, ['a', 'b']); - * // => true - * - * _.has(other, 'a'); - * // => false - */ - function has(object, path) { - return object != null && hasPath(object, path, baseHas); - } - - /** - * Checks if `path` is a direct or inherited property of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.hasIn(object, 'a'); - * // => true - * - * _.hasIn(object, 'a.b'); - * // => true - * - * _.hasIn(object, ['a', 'b']); - * // => true - * - * _.hasIn(object, 'b'); - * // => false - */ - function hasIn(object, path) { - return object != null && hasPath(object, path, baseHasIn); - } - - /** - * Creates an object composed of the inverted keys and values of `object`. - * If `object` contains duplicate values, subsequent values overwrite - * property assignments of previous values. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Object - * @param {Object} object The object to invert. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invert(object); - * // => { '1': 'c', '2': 'b' } - */ - var invert = createInverter(function(result, value, key) { - if (value != null && - typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - - result[value] = key; - }, constant(identity)); - - /** - * This method is like `_.invert` except that the inverted object is generated - * from the results of running each element of `object` thru `iteratee`. The - * corresponding inverted value of each inverted key is an array of keys - * responsible for generating the inverted value. The iteratee is invoked - * with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Object - * @param {Object} object The object to invert. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invertBy(object); - * // => { '1': ['a', 'c'], '2': ['b'] } - * - * _.invertBy(object, function(value) { - * return 'group' + value; - * }); - * // => { 'group1': ['a', 'c'], 'group2': ['b'] } - */ - var invertBy = createInverter(function(result, value, key) { - if (value != null && - typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - - if (hasOwnProperty.call(result, value)) { - result[value].push(key); - } else { - result[value] = [key]; - } - }, getIteratee); - - /** - * Invokes the method at `path` of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {...*} [args] The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - * @example - * - * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; - * - * _.invoke(object, 'a[0].b.c.slice', 1, 3); - * // => [2, 3] - */ - var invoke = baseRest(baseInvoke); - - /** - * Creates an array of the own enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. See the - * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * for more details. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keys(new Foo); - * // => ['a', 'b'] (iteration order is not guaranteed) - * - * _.keys('hi'); - * // => ['0', '1'] - */ - function keys(object) { - return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); - } - - /** - * Creates an array of the own and inherited enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keysIn(new Foo); - * // => ['a', 'b', 'c'] (iteration order is not guaranteed) - */ - function keysIn(object) { - return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); - } - - /** - * The opposite of `_.mapValues`; this method creates an object with the - * same values as `object` and keys generated by running each own enumerable - * string keyed property of `object` thru `iteratee`. The iteratee is invoked - * with three arguments: (value, key, object). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapValues - * @example - * - * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { - * return key + value; - * }); - * // => { 'a1': 1, 'b2': 2 } - */ - function mapKeys(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - - baseForOwn(object, function(value, key, object) { - baseAssignValue(result, iteratee(value, key, object), value); - }); - return result; - } - - /** - * Creates an object with the same keys as `object` and values generated - * by running each own enumerable string keyed property of `object` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, key, object). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapKeys - * @example - * - * var users = { - * 'fred': { 'user': 'fred', 'age': 40 }, - * 'pebbles': { 'user': 'pebbles', 'age': 1 } - * }; - * - * _.mapValues(users, function(o) { return o.age; }); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - * - * // The `_.property` iteratee shorthand. - * _.mapValues(users, 'age'); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - */ - function mapValues(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - - baseForOwn(object, function(value, key, object) { - baseAssignValue(result, key, iteratee(value, key, object)); - }); - return result; - } - - /** - * This method is like `_.assign` except that it recursively merges own and - * inherited enumerable string keyed properties of source objects into the - * destination object. Source properties that resolve to `undefined` are - * skipped if a destination value exists. Array and plain object properties - * are merged recursively. Other objects and value types are overridden by - * assignment. Source objects are applied from left to right. Subsequent - * sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @example - * - * var object = { - * 'a': [{ 'b': 2 }, { 'd': 4 }] - * }; - * - * var other = { - * 'a': [{ 'c': 3 }, { 'e': 5 }] - * }; - * - * _.merge(object, other); - * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } - */ - var merge = createAssigner(function(object, source, srcIndex) { - baseMerge(object, source, srcIndex); - }); - - /** - * This method is like `_.merge` except that it accepts `customizer` which - * is invoked to produce the merged values of the destination and source - * properties. If `customizer` returns `undefined`, merging is handled by the - * method instead. The `customizer` is invoked with six arguments: - * (objValue, srcValue, key, object, source, stack). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} customizer The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * function customizer(objValue, srcValue) { - * if (_.isArray(objValue)) { - * return objValue.concat(srcValue); - * } - * } - * - * var object = { 'a': [1], 'b': [2] }; - * var other = { 'a': [3], 'b': [4] }; - * - * _.mergeWith(object, other, customizer); - * // => { 'a': [1, 3], 'b': [2, 4] } - */ - var mergeWith = createAssigner(function(object, source, srcIndex, customizer) { - baseMerge(object, source, srcIndex, customizer); - }); - - /** - * The opposite of `_.pick`; this method creates an object composed of the - * own and inherited enumerable property paths of `object` that are not omitted. - * - * **Note:** This method is considerably slower than `_.pick`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to omit. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omit(object, ['a', 'c']); - * // => { 'b': '2' } - */ - var omit = flatRest(function(object, paths) { - var result = {}; - if (object == null) { - return result; - } - var isDeep = false; - paths = arrayMap(paths, function(path) { - path = castPath(path, object); - isDeep || (isDeep = path.length > 1); - return path; - }); - copyObject(object, getAllKeysIn(object), result); - if (isDeep) { - result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); - } - var length = paths.length; - while (length--) { - baseUnset(result, paths[length]); - } - return result; - }); - - /** - * The opposite of `_.pickBy`; this method creates an object composed of - * the own and inherited enumerable string keyed properties of `object` that - * `predicate` doesn't return truthy for. The predicate is invoked with two - * arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omitBy(object, _.isNumber); - * // => { 'b': '2' } - */ - function omitBy(object, predicate) { - return pickBy(object, negate(getIteratee(predicate))); - } - - /** - * Creates an object composed of the picked `object` properties. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pick(object, ['a', 'c']); - * // => { 'a': 1, 'c': 3 } - */ - var pick = flatRest(function(object, paths) { - return object == null ? {} : basePick(object, paths); - }); - - /** - * Creates an object composed of the `object` properties `predicate` returns - * truthy for. The predicate is invoked with two arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pickBy(object, _.isNumber); - * // => { 'a': 1, 'c': 3 } - */ - function pickBy(object, predicate) { - if (object == null) { - return {}; - } - var props = arrayMap(getAllKeysIn(object), function(prop) { - return [prop]; - }); - predicate = getIteratee(predicate); - return basePickBy(object, props, function(value, path) { - return predicate(value, path[0]); - }); - } - - /** - * This method is like `_.get` except that if the resolved value is a - * function it's invoked with the `this` binding of its parent object and - * its result is returned. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to resolve. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; - * - * _.result(object, 'a[0].b.c1'); - * // => 3 - * - * _.result(object, 'a[0].b.c2'); - * // => 4 - * - * _.result(object, 'a[0].b.c3', 'default'); - * // => 'default' - * - * _.result(object, 'a[0].b.c3', _.constant('default')); - * // => 'default' - */ - function result(object, path, defaultValue) { - path = castPath(path, object); - - var index = -1, - length = path.length; - - // Ensure the loop is entered when path is empty. - if (!length) { - length = 1; - object = undefined; - } - while (++index < length) { - var value = object == null ? undefined : object[toKey(path[index])]; - if (value === undefined) { - index = length; - value = defaultValue; - } - object = isFunction(value) ? value.call(object) : value; - } - return object; - } - - /** - * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, - * it's created. Arrays are created for missing index properties while objects - * are created for all other missing properties. Use `_.setWith` to customize - * `path` creation. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.set(object, 'a[0].b.c', 4); - * console.log(object.a[0].b.c); - * // => 4 - * - * _.set(object, ['x', '0', 'y', 'z'], 5); - * console.log(object.x[0].y.z); - * // => 5 - */ - function set(object, path, value) { - return object == null ? object : baseSet(object, path, value); - } - - /** - * This method is like `_.set` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.setWith(object, '[0][1]', 'a', Object); - * // => { '0': { '1': 'a' } } - */ - function setWith(object, path, value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseSet(object, path, value, customizer); - } - - /** - * Creates an array of own enumerable string keyed-value pairs for `object` - * which can be consumed by `_.fromPairs`. If `object` is a map or set, its - * entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entries - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairs(new Foo); - * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) - */ - var toPairs = createToPairs(keys); - - /** - * Creates an array of own and inherited enumerable string keyed-value pairs - * for `object` which can be consumed by `_.fromPairs`. If `object` is a map - * or set, its entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entriesIn - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairsIn(new Foo); - * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) - */ - var toPairsIn = createToPairs(keysIn); - - /** - * An alternative to `_.reduce`; this method transforms `object` to a new - * `accumulator` object which is the result of running each of its own - * enumerable string keyed properties thru `iteratee`, with each invocation - * potentially mutating the `accumulator` object. If `accumulator` is not - * provided, a new object with the same `[[Prototype]]` will be used. The - * iteratee is invoked with four arguments: (accumulator, value, key, object). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The custom accumulator value. - * @returns {*} Returns the accumulated value. - * @example - * - * _.transform([2, 3, 4], function(result, n) { - * result.push(n *= n); - * return n % 2 == 0; - * }, []); - * // => [4, 9] - * - * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } - */ - function transform(object, iteratee, accumulator) { - var isArr = isArray(object), - isArrLike = isArr || isBuffer(object) || isTypedArray(object); - - iteratee = getIteratee(iteratee, 4); - if (accumulator == null) { - var Ctor = object && object.constructor; - if (isArrLike) { - accumulator = isArr ? new Ctor : []; - } - else if (isObject(object)) { - accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; - } - else { - accumulator = {}; - } - } - (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { - return iteratee(accumulator, value, index, object); - }); - return accumulator; - } - - /** - * Removes the property at `path` of `object`. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 7 } }] }; - * _.unset(object, 'a[0].b.c'); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - * - * _.unset(object, ['a', '0', 'b', 'c']); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - */ - function unset(object, path) { - return object == null ? true : baseUnset(object, path); - } - - /** - * This method is like `_.set` except that accepts `updater` to produce the - * value to set. Use `_.updateWith` to customize `path` creation. The `updater` - * is invoked with one argument: (value). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.update(object, 'a[0].b.c', function(n) { return n * n; }); - * console.log(object.a[0].b.c); - * // => 9 - * - * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); - * console.log(object.x[0].y.z); - * // => 0 - */ - function update(object, path, updater) { - return object == null ? object : baseUpdate(object, path, castFunction(updater)); - } - - /** - * This method is like `_.update` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.updateWith(object, '[0][1]', _.constant('a'), Object); - * // => { '0': { '1': 'a' } } - */ - function updateWith(object, path, updater, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); - } - - /** - * Creates an array of the own enumerable string keyed property values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.values(new Foo); - * // => [1, 2] (iteration order is not guaranteed) - * - * _.values('hi'); - * // => ['h', 'i'] - */ - function values(object) { - return object == null ? [] : baseValues(object, keys(object)); - } - - /** - * Creates an array of the own and inherited enumerable string keyed property - * values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.valuesIn(new Foo); - * // => [1, 2, 3] (iteration order is not guaranteed) - */ - function valuesIn(object) { - return object == null ? [] : baseValues(object, keysIn(object)); - } - - /*------------------------------------------------------------------------*/ - - /** - * Clamps `number` within the inclusive `lower` and `upper` bounds. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Number - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - * @example - * - * _.clamp(-10, -5, 5); - * // => -5 - * - * _.clamp(10, -5, 5); - * // => 5 - */ - function clamp(number, lower, upper) { - if (upper === undefined) { - upper = lower; - lower = undefined; - } - if (upper !== undefined) { - upper = toNumber(upper); - upper = upper === upper ? upper : 0; - } - if (lower !== undefined) { - lower = toNumber(lower); - lower = lower === lower ? lower : 0; - } - return baseClamp(toNumber(number), lower, upper); - } - - /** - * Checks if `n` is between `start` and up to, but not including, `end`. If - * `end` is not specified, it's set to `start` with `start` then set to `0`. - * If `start` is greater than `end` the params are swapped to support - * negative ranges. - * - * @static - * @memberOf _ - * @since 3.3.0 - * @category Number - * @param {number} number The number to check. - * @param {number} [start=0] The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - * @see _.range, _.rangeRight - * @example - * - * _.inRange(3, 2, 4); - * // => true - * - * _.inRange(4, 8); - * // => true - * - * _.inRange(4, 2); - * // => false - * - * _.inRange(2, 2); - * // => false - * - * _.inRange(1.2, 2); - * // => true - * - * _.inRange(5.2, 4); - * // => false - * - * _.inRange(-3, -2, -6); - * // => true - */ - function inRange(number, start, end) { - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - number = toNumber(number); - return baseInRange(number, start, end); - } - - /** - * Produces a random number between the inclusive `lower` and `upper` bounds. - * If only one argument is provided a number between `0` and the given number - * is returned. If `floating` is `true`, or either `lower` or `upper` are - * floats, a floating-point number is returned instead of an integer. - * - * **Note:** JavaScript follows the IEEE-754 standard for resolving - * floating-point values which can produce unexpected results. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Number - * @param {number} [lower=0] The lower bound. - * @param {number} [upper=1] The upper bound. - * @param {boolean} [floating] Specify returning a floating-point number. - * @returns {number} Returns the random number. - * @example - * - * _.random(0, 5); - * // => an integer between 0 and 5 - * - * _.random(5); - * // => also an integer between 0 and 5 - * - * _.random(5, true); - * // => a floating-point number between 0 and 5 - * - * _.random(1.2, 5.2); - * // => a floating-point number between 1.2 and 5.2 - */ - function random(lower, upper, floating) { - if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { - upper = floating = undefined; - } - if (floating === undefined) { - if (typeof upper == 'boolean') { - floating = upper; - upper = undefined; - } - else if (typeof lower == 'boolean') { - floating = lower; - lower = undefined; - } - } - if (lower === undefined && upper === undefined) { - lower = 0; - upper = 1; - } - else { - lower = toFinite(lower); - if (upper === undefined) { - upper = lower; - lower = 0; - } else { - upper = toFinite(upper); - } - } - if (lower > upper) { - var temp = lower; - lower = upper; - upper = temp; - } - if (floating || lower % 1 || upper % 1) { - var rand = nativeRandom(); - return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper); - } - return baseRandom(lower, upper); - } - - /*------------------------------------------------------------------------*/ - - /** - * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the camel cased string. - * @example - * - * _.camelCase('Foo Bar'); - * // => 'fooBar' - * - * _.camelCase('--foo-bar--'); - * // => 'fooBar' - * - * _.camelCase('__FOO_BAR__'); - * // => 'fooBar' - */ - var camelCase = createCompounder(function(result, word, index) { - word = word.toLowerCase(); - return result + (index ? capitalize(word) : word); - }); - - /** - * Converts the first character of `string` to upper case and the remaining - * to lower case. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to capitalize. - * @returns {string} Returns the capitalized string. - * @example - * - * _.capitalize('FRED'); - * // => 'Fred' - */ - function capitalize(string) { - return upperFirst(toString(string).toLowerCase()); - } - - /** - * Deburrs `string` by converting - * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) - * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) - * letters to basic Latin letters and removing - * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to deburr. - * @returns {string} Returns the deburred string. - * @example - * - * _.deburr('déjà vu'); - * // => 'deja vu' - */ - function deburr(string) { - string = toString(string); - return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); - } - - /** - * Checks if `string` ends with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=string.length] The position to search up to. - * @returns {boolean} Returns `true` if `string` ends with `target`, - * else `false`. - * @example - * - * _.endsWith('abc', 'c'); - * // => true - * - * _.endsWith('abc', 'b'); - * // => false - * - * _.endsWith('abc', 'b', 2); - * // => true - */ - function endsWith(string, target, position) { - string = toString(string); - target = baseToString(target); - - var length = string.length; - position = position === undefined - ? length - : baseClamp(toInteger(position), 0, length); - - var end = position; - position -= target.length; - return position >= 0 && string.slice(position, end) == target; - } - - /** - * Converts the characters "&", "<", ">", '"', and "'" in `string` to their - * corresponding HTML entities. - * - * **Note:** No other characters are escaped. To escape additional - * characters use a third-party library like [_he_](https://mths.be/he). - * - * Though the ">" character is escaped for symmetry, characters like - * ">" and "/" don't need escaping in HTML and have no special meaning - * unless they're part of a tag or unquoted attribute value. See - * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) - * (under "semi-related fun fact") for more details. - * - * When working with HTML you should always - * [quote attribute values](http://wonko.com/post/html-escaping) to reduce - * XSS vectors. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escape('fred, barney, & pebbles'); - * // => 'fred, barney, & pebbles' - */ - function escape(string) { - string = toString(string); - return (string && reHasUnescapedHtml.test(string)) - ? string.replace(reUnescapedHtml, escapeHtmlChar) - : string; - } - - /** - * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", - * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escapeRegExp('[lodash](https://lodash.com/)'); - * // => '\[lodash\]\(https://lodash\.com/\)' - */ - function escapeRegExp(string) { - string = toString(string); - return (string && reHasRegExpChar.test(string)) - ? string.replace(reRegExpChar, '\\$&') - : string; - } - - /** - * Converts `string` to - * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the kebab cased string. - * @example - * - * _.kebabCase('Foo Bar'); - * // => 'foo-bar' - * - * _.kebabCase('fooBar'); - * // => 'foo-bar' - * - * _.kebabCase('__FOO_BAR__'); - * // => 'foo-bar' - */ - var kebabCase = createCompounder(function(result, word, index) { - return result + (index ? '-' : '') + word.toLowerCase(); - }); - - /** - * Converts `string`, as space separated words, to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the lower cased string. - * @example - * - * _.lowerCase('--Foo-Bar--'); - * // => 'foo bar' - * - * _.lowerCase('fooBar'); - * // => 'foo bar' - * - * _.lowerCase('__FOO_BAR__'); - * // => 'foo bar' - */ - var lowerCase = createCompounder(function(result, word, index) { - return result + (index ? ' ' : '') + word.toLowerCase(); - }); - - /** - * Converts the first character of `string` to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.lowerFirst('Fred'); - * // => 'fred' - * - * _.lowerFirst('FRED'); - * // => 'fRED' - */ - var lowerFirst = createCaseFirst('toLowerCase'); - - /** - * Pads `string` on the left and right sides if it's shorter than `length`. - * Padding characters are truncated if they can't be evenly divided by `length`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.pad('abc', 8); - * // => ' abc ' - * - * _.pad('abc', 8, '_-'); - * // => '_-abc_-_' - * - * _.pad('abc', 3); - * // => 'abc' - */ - function pad(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - if (!length || strLength >= length) { - return string; - } - var mid = (length - strLength) / 2; - return ( - createPadding(nativeFloor(mid), chars) + - string + - createPadding(nativeCeil(mid), chars) - ); - } - - /** - * Pads `string` on the right side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padEnd('abc', 6); - * // => 'abc ' - * - * _.padEnd('abc', 6, '_-'); - * // => 'abc_-_' - * - * _.padEnd('abc', 3); - * // => 'abc' - */ - function padEnd(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - return (length && strLength < length) - ? (string + createPadding(length - strLength, chars)) - : string; - } - - /** - * Pads `string` on the left side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padStart('abc', 6); - * // => ' abc' - * - * _.padStart('abc', 6, '_-'); - * // => '_-_abc' - * - * _.padStart('abc', 3); - * // => 'abc' - */ - function padStart(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - return (length && strLength < length) - ? (createPadding(length - strLength, chars) + string) - : string; - } - - /** - * Converts `string` to an integer of the specified radix. If `radix` is - * `undefined` or `0`, a `radix` of `10` is used unless `value` is a - * hexadecimal, in which case a `radix` of `16` is used. - * - * **Note:** This method aligns with the - * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category String - * @param {string} string The string to convert. - * @param {number} [radix=10] The radix to interpret `value` by. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {number} Returns the converted integer. - * @example - * - * _.parseInt('08'); - * // => 8 - * - * _.map(['6', '08', '10'], _.parseInt); - * // => [6, 8, 10] - */ - function parseInt(string, radix, guard) { - if (guard || radix == null) { - radix = 0; - } else if (radix) { - radix = +radix; - } - return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); - } - - /** - * Repeats the given string `n` times. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to repeat. - * @param {number} [n=1] The number of times to repeat the string. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {string} Returns the repeated string. - * @example - * - * _.repeat('*', 3); - * // => '***' - * - * _.repeat('abc', 2); - * // => 'abcabc' - * - * _.repeat('abc', 0); - * // => '' - */ - function repeat(string, n, guard) { - if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) { - n = 1; - } else { - n = toInteger(n); - } - return baseRepeat(toString(string), n); - } - - /** - * Replaces matches for `pattern` in `string` with `replacement`. - * - * **Note:** This method is based on - * [`String#replace`](https://mdn.io/String/replace). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to modify. - * @param {RegExp|string} pattern The pattern to replace. - * @param {Function|string} replacement The match replacement. - * @returns {string} Returns the modified string. - * @example - * - * _.replace('Hi Fred', 'Fred', 'Barney'); - * // => 'Hi Barney' - */ - function replace() { - var args = arguments, - string = toString(args[0]); - - return args.length < 3 ? string : string.replace(args[1], args[2]); - } - - /** - * Converts `string` to - * [snake case](https://en.wikipedia.org/wiki/Snake_case). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the snake cased string. - * @example - * - * _.snakeCase('Foo Bar'); - * // => 'foo_bar' - * - * _.snakeCase('fooBar'); - * // => 'foo_bar' - * - * _.snakeCase('--FOO-BAR--'); - * // => 'foo_bar' - */ - var snakeCase = createCompounder(function(result, word, index) { - return result + (index ? '_' : '') + word.toLowerCase(); - }); - - /** - * Splits `string` by `separator`. - * - * **Note:** This method is based on - * [`String#split`](https://mdn.io/String/split). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to split. - * @param {RegExp|string} separator The separator pattern to split by. - * @param {number} [limit] The length to truncate results to. - * @returns {Array} Returns the string segments. - * @example - * - * _.split('a-b-c', '-', 2); - * // => ['a', 'b'] - */ - function split(string, separator, limit) { - if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { - separator = limit = undefined; - } - limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; - if (!limit) { - return []; - } - string = toString(string); - if (string && ( - typeof separator == 'string' || - (separator != null && !isRegExp(separator)) - )) { - separator = baseToString(separator); - if (!separator && hasUnicode(string)) { - return castSlice(stringToArray(string), 0, limit); - } - } - return string.split(separator, limit); - } - - /** - * Converts `string` to - * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). - * - * @static - * @memberOf _ - * @since 3.1.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the start cased string. - * @example - * - * _.startCase('--foo-bar--'); - * // => 'Foo Bar' - * - * _.startCase('fooBar'); - * // => 'Foo Bar' - * - * _.startCase('__FOO_BAR__'); - * // => 'FOO BAR' - */ - var startCase = createCompounder(function(result, word, index) { - return result + (index ? ' ' : '') + upperFirst(word); - }); - - /** - * Checks if `string` starts with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=0] The position to search from. - * @returns {boolean} Returns `true` if `string` starts with `target`, - * else `false`. - * @example - * - * _.startsWith('abc', 'a'); - * // => true - * - * _.startsWith('abc', 'b'); - * // => false - * - * _.startsWith('abc', 'b', 1); - * // => true - */ - function startsWith(string, target, position) { - string = toString(string); - position = position == null - ? 0 - : baseClamp(toInteger(position), 0, string.length); - - target = baseToString(target); - return string.slice(position, position + target.length) == target; - } - - /** - * Creates a compiled template function that can interpolate data properties - * in "interpolate" delimiters, HTML-escape interpolated data properties in - * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data - * properties may be accessed as free variables in the template. If a setting - * object is given, it takes precedence over `_.templateSettings` values. - * - * **Note:** In the development build `_.template` utilizes - * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) - * for easier debugging. - * - * For more information on precompiling templates see - * [lodash's custom builds documentation](https://lodash.com/custom-builds). - * - * For more information on Chrome extension sandboxes see - * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The template string. - * @param {Object} [options={}] The options object. - * @param {RegExp} [options.escape=_.templateSettings.escape] - * The HTML "escape" delimiter. - * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] - * The "evaluate" delimiter. - * @param {Object} [options.imports=_.templateSettings.imports] - * An object to import into the template as free variables. - * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] - * The "interpolate" delimiter. - * @param {string} [options.sourceURL='lodash.templateSources[n]'] - * The sourceURL of the compiled template. - * @param {string} [options.variable='obj'] - * The data object variable name. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the compiled template function. - * @example - * - * // Use the "interpolate" delimiter to create a compiled template. - * var compiled = _.template('hello <%= user %>!'); - * compiled({ 'user': 'fred' }); - * // => 'hello fred!' - * - * // Use the HTML "escape" delimiter to escape data property values. - * var compiled = _.template('<%- value %>'); - * compiled({ 'value': '` + - `` + - `` + - ``, - ); - await run.stop(); - }); - - it('works with anonymous', async () => { - const overrides = { crossOrigin: CrossOrigin.Anonymous }; - const run = await architect.scheduleTarget(targetSpec, overrides); - const output = (await run.result) as BrowserBuilderOutput; - expect(output.success).toBe(true); - const fileName = join(normalize(output.outputPath), 'index.html'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toBe( - `` + - `` + - `` + - `` + - `` + - `` + - ``, - ); - await run.stop(); - }); - - it('works with none', async () => { - const overrides = { crossOrigin: CrossOrigin.None }; - const run = await architect.scheduleTarget(targetSpec, overrides); - const output = (await run.result) as BrowserBuilderOutput; - expect(output.success).toBe(true); - const fileName = join(normalize(output.outputPath), 'index.html'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toBe( - `` + - `` + - `` + - `` + - `` + - `` + - ``, - ); - await run.stop(); - }); - - it('works for lazy chunks', async () => { - host.writeMultipleFiles({ - 'src/lazy-module.ts': 'export const value = 100;', - 'src/main.ts': `import('./lazy-module');`, - }); - - const overrides = { crossOrigin: CrossOrigin.UseCredentials }; - const run = await architect.scheduleTarget(targetSpec, overrides); - const output = (await run.result) as BrowserBuilderOutput; - expect(output.success).toBe(true); - - const fileName = join(normalize(output.outputPath), 'runtime.js'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toContain('script.crossOrigin = "use-credentials"'); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts deleted file mode 100644 index 4908d4105b6c..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/differential_loading_spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { PathFragment } from '@angular-devkit/core'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - -const TEST_TIMEOUT = 8 * 60 * 1000; - -// tslint:disable-next-line: no-big-function -describe('Browser Builder with differential loading', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - // to trigger differential loading we need an non ever green browser - host.writeMultipleFiles({ - '.browserslistrc': 'IE 10', - }); - - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(async () => host.restore().toPromise()); - - it('emits all the neccessary files for default configuration', async () => { - const { files } = await browserBuild(architect, host, target); - - const expectedOutputs = [ - 'favicon.ico', - 'index.html', - - 'main-es2017.js', - 'main-es2017.js.map', - 'main-es5.js', - 'main-es5.js.map', - - 'polyfills-es2017.js', - 'polyfills-es2017.js.map', - 'polyfills-es5.js', - 'polyfills-es5.js.map', - - 'runtime-es2017.js', - 'runtime-es2017.js.map', - 'runtime-es5.js', - 'runtime-es5.js.map', - - 'vendor-es2017.js', - 'vendor-es2017.js.map', - 'vendor-es5.js', - 'vendor-es5.js.map', - - 'styles.css', - 'styles.css.map', - ] as PathFragment[]; - - expect(Object.keys(files)).toEqual(jasmine.arrayWithExactContents(expectedOutputs)); - }, TEST_TIMEOUT); - - it('emits all the neccessary files for target of ESNext', async () => { - host.replaceInFile( - 'tsconfig.json', - '"target": "es2017",', - `"target": "esnext",`, - ); - - const { files } = await browserBuild(architect, host, target); - - const expectedOutputs = [ - 'favicon.ico', - 'index.html', - - 'main-esnext.js', - 'main-esnext.js.map', - 'main-es5.js', - 'main-es5.js.map', - - 'polyfills-esnext.js', - 'polyfills-esnext.js.map', - 'polyfills-es5.js', - 'polyfills-es5.js.map', - - 'runtime-esnext.js', - 'runtime-esnext.js.map', - 'runtime-es5.js', - 'runtime-es5.js.map', - - 'vendor-esnext.js', - 'vendor-esnext.js.map', - 'vendor-es5.js', - 'vendor-es5.js.map', - - 'styles.css', - 'styles.css.map', - ] as PathFragment[]; - - expect(Object.keys(files)).toEqual(jasmine.arrayWithExactContents(expectedOutputs)); - }, TEST_TIMEOUT); - - it('deactivates differential loading for watch mode', async () => { - const { files } = await browserBuild(architect, host, target, { watch: true }); - - const expectedOutputs = [ - 'favicon.ico', - 'index.html', - - 'main-es2017.js', - 'main-es2017.js.map', - - 'polyfills-es2017.js', - 'polyfills-es2017.js.map', - - 'runtime-es2017.js', - 'runtime-es2017.js.map', - - 'vendor-es2017.js', - 'vendor-es2017.js.map', - - 'styles.css', - 'styles.css.map', - ] as PathFragment[]; - - expect(Object.keys(files)).toEqual(jasmine.arrayWithExactContents(expectedOutputs)); - }, TEST_TIMEOUT); - - it('emits the right ES formats', async () => { - const { files } = await browserBuild(architect, host, target, { - optimization: true, - vendorChunk: false, - }); - expect(await files['main-es5.js']).not.toContain('const '); - expect(await files['main-es2017.js']).toContain('const '); - }, TEST_TIMEOUT); - - it('wraps ES5 scripts in an IIFE', async () => { - const { files } = await browserBuild(architect, host, target, { optimization: false }); - expect(await files['main-es5.js']).toMatch(/^\(function \(\) \{/); - expect(await files['main-es2017.js']).not.toMatch(/^\(function \(\) \{/); - }, TEST_TIMEOUT); - - it('uses the right zone.js variant', async () => { - const { files } = await browserBuild(architect, host, target, { optimization: false }); - expect(await files['polyfills-es5.js']).toContain('zone.js/plugins/zone-legacy'); - expect(await files['polyfills-es5.js']).toContain('registerElementPatch'); - expect(await files['polyfills-es2017.js']).not.toContain('zone.js/plugins/zone-legacy'); - expect(await files['polyfills-es2017.js']).not.toContain('registerElementPatch'); - }, TEST_TIMEOUT); - - it('adds `type="module"` when differential loading is needed', async () => { - host.writeMultipleFiles({ - '.browserslistrc': ` - last 1 chrome version - IE 9 - `, - }); - - const { files } = await browserBuild(architect, host, target, { watch: true }); - expect(await files['index.html']).toContain( - '' + - '' + - '' + - '', - ); - }, TEST_TIMEOUT); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/errors_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/errors_spec.ts deleted file mode 100644 index 11d8306ea0c0..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/errors_spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { logging } from '@angular-devkit/core'; -import { createArchitect, host } from '../../test-utils'; - -describe('Browser Builder errors', () => { - const targetSpec = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('shows error when files are not part of the compilation', async () => { - host.replaceInFile( - 'src/tsconfig.app.json', - /,\r?\n?\s*"polyfills\.ts"/, - '', - ); - host.replaceInFile( - 'src/tsconfig.app.json', - '"**/*.ts"', - '"**/*.d.ts"', - ); - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, {}, { logger }); - const output = await run.result; - expect(output.success).toBe(false); - expect(logs.join()).toContain('polyfills.ts is missing from the TypeScript'); - await run.stop(); - }); - - it('shows TS syntax errors', async () => { - host.appendToFile('src/app/app.component.ts', ']]]'); - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, {}, { logger }); - const output = await run.result; - expect(output.success).toBe(false); - expect(logs.join()).toContain('Declaration or statement expected'); - await run.stop(); - }); - - it('shows static analysis errors', async () => { - host.replaceInFile('src/app/app.component.ts', `'app-root'`, `(() => 'app-root')()`); - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, { aot: true }, { logger }); - const output = await run.result; - expect(output.success).toBe(false); - - // Wait for the builder to complete - await run.stop(); - - expect(logs.join()).toContain('selector must be a string'); - }); - - it('shows missing export errors', async () => { - host.writeMultipleFiles({ - 'src/not-main.js': ` - import { missingExport } from 'rxjs'; - console.log(missingExport); - `, - }); - const overrides = { main: 'src/not-main.js' }; - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, overrides, { logger }); - const output = await run.result; - expect(output.success).toBe(false); - - // Wait for the builder to complete - await run.stop(); - - expect(logs.join()).toContain(`export 'missingExport' (imported as 'missingExport') was not found in 'rxjs'`); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/index_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/index_spec.ts deleted file mode 100644 index f74d864b4ab2..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/index_spec.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; -import { join, normalize, tags, virtualFs, workspaces } from '@angular-devkit/core'; -import { createArchitect, host } from '../../test-utils'; - -// tslint:disable-next-line:no-big-function -describe('Browser Builder index HTML processing', () => { - const targetSpec = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works with UTF-8 BOM', async () => { - host.writeMultipleFiles({ - 'src/index.html': Buffer.from( - '\ufeff', - 'utf8'), - }); - - const run = await architect.scheduleTarget(targetSpec); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - const fileName = join(normalize(output.outputPath), 'index.html'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toBe( - `` - + `` - + `` - + ``, - ); - await run.stop(); - }); - - // todo: enable when utf16 is supported - xit('works with UTF16 LE BOM', async () => { - host.writeMultipleFiles({ - 'src/index.html': Buffer.from( - '\ufeff', - 'utf16le'), - }); - - const run = await architect.scheduleTarget(targetSpec); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - const fileName = join(normalize(output.outputPath), 'index.html'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toBe( - `` - + `` - + `` - + `` - + ``, - ); - await run.stop(); - }); - - it('keeps escaped charaters', async () => { - host.writeMultipleFiles({ - 'src/index.html': tags.oneLine` - í - - `, - }); - - const run = await architect.scheduleTarget(targetSpec); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - const fileName = join(normalize(output.outputPath), 'index.html'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toBe( - `í ` - + `` - + `` - + ``, - ); - await run.stop(); - }); - - it('keeps custom template charaters', async () => { - host.writeMultipleFiles({ - 'src/index.html': tags.oneLine` - <%= csrf_meta_tags %> - - `, - }); - - const run = await architect.scheduleTarget(targetSpec); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - const fileName = join(normalize(output.outputPath), 'index.html'); - const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toBe( - `<%= csrf_meta_tags %> ` - + `` - + `` - + ``, - ); - await run.stop(); - }); - - it('uses the input value from the index option longform', async () => { - const { workspace } = await workspaces.readWorkspace(host.root(), workspaces.createWorkspaceHost(host)); - const app = workspace.projects.get('app'); - if (!app) { - fail('Test application "app" not found.'); - - return; - } - const target = app.targets.get('build'); - if (!target) { - fail('Test application "app" target "build" not found.'); - - return; - } - if (!target.options) { - target.options = {}; - } - target.options.index = { input: 'src/index-2.html' }; - await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); - - host.writeMultipleFiles({ - 'src/index-2.html': tags.oneLine` - <%= csrf_meta_tags %> - - `, - }); - await host.delete(join(host.root(), normalize('src/index.html'))).toPromise(); - - // Recreate architect to use update angular.json - architect = (await createArchitect(host.root())).architect; - - const run = await architect.scheduleTarget(targetSpec); - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - const outputIndexPath = join(host.root(), 'dist', 'index.html'); - const content = await host.read(normalize(outputIndexPath)).toPromise(); - expect(virtualFs.fileBufferToString(content)).toBe( - `<%= csrf_meta_tags %> ` - + `` - + `` - + ``, - ); - }); - - it('uses the output value from the index option longform', async () => { - const { workspace } = await workspaces.readWorkspace(host.root(), workspaces.createWorkspaceHost(host)); - const app = workspace.projects.get('app'); - if (!app) { - fail('Test application "app" not found.'); - - return; - } - const target = app.targets.get('build'); - if (!target) { - fail('Test application "app" target "build" not found.'); - - return; - } - if (!target.options) { - target.options = {}; - } - target.options.index = { input: 'src/index.html', output: 'main.html' }; - await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); - - host.writeMultipleFiles({ - 'src/index.html': tags.oneLine` - - - `, - }); - - // Recreate architect to use update angular.json - architect = (await createArchitect(host.root())).architect; - - const run = await architect.scheduleTarget(targetSpec); - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - const outputIndexPath = join(host.root(), 'dist', 'main.html'); - const content = await host.read(normalize(outputIndexPath)).toPromise(); - expect(virtualFs.fileBufferToString(content)).toBe( - ` ` - + `` - + `` - + ``, - ); - }); - - it('creates subdirectories for output value from the index option longform', async () => { - const { workspace } = await workspaces.readWorkspace(host.root(), workspaces.createWorkspaceHost(host)); - const app = workspace.projects.get('app'); - if (!app) { - fail('Test application "app" not found.'); - - return; - } - const target = app.targets.get('build'); - if (!target) { - fail('Test application "app" target "build" not found.'); - - return; - } - if (!target.options) { - target.options = {}; - } - target.options.index = { input: 'src/index.html', output: 'extra/main.html' }; - await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); - - host.writeMultipleFiles({ - 'src/index.html': tags.oneLine` - - - `, - }); - - // Recreate architect to use update angular.json - architect = (await createArchitect(host.root())).architect; - - const run = await architect.scheduleTarget(targetSpec); - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - const outputIndexPath = join(host.root(), 'dist', 'extra', 'main.html'); - const content = await host.read(normalize(outputIndexPath)).toPromise(); - expect(virtualFs.fileBufferToString(content)).toBe( - ` ` - + `` - + `` - + ``, - ); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/inline-critical-css-optimization_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/inline-critical-css-optimization_spec.ts deleted file mode 100644 index a7d9d114559c..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/inline-critical-css-optimization_spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - -describe('Browser Builder inline critical CSS optimization', () => { - const target = { project: 'app', target: 'build' }; - const overrides = { - optimization: { - scripts: false, - styles: { - minify: true, - inlineCritical: true, - }, - fonts: false, - }, - }; - - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - host.writeMultipleFiles({ - 'src/styles.css': ` - body { color: #000 } - `, - }); - }); - - afterEach(async () => host.restore().toPromise()); - - it('works', async () => { - const { files } = await browserBuild(architect, host, target, overrides); - const html = await files['index.html']; - expect(html).toContain(``); - expect(html).toContain(`body{color:#000;}`); - }); - - it('works with deployUrl', async () => { - const { files } = await browserBuild(architect, host, target, { ...overrides, deployUrl: 'http://cdn.com/' }); - const html = await files['index.html']; - expect(html).toContain(``); - expect(html).toContain(`body{color:#000;}`); - }); - - it('should not inline critical css when option is disabled', async () => { - const { files } = await browserBuild(architect, host, target, { optimization: false }); - const html = await files['index.html']; - expect(html).toContain(``); - expect(html).not.toContain(`body{color:#000;}`); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/poll_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/poll_spec.ts deleted file mode 100644 index 9f330fc759f5..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/poll_spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { debounceTime, take, tap } from 'rxjs/operators'; -import { createArchitect, host } from '../../test-utils'; - - -describe('Browser Builder poll', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works', async () => { - const overrides = { watch: true, poll: 10000 }; - const intervals: number[] = []; - let startTime: number | undefined; - - const run = await architect.scheduleTarget(target, overrides); - await run.output.pipe( - // Debounce 1s, otherwise changes are too close together and polling doesn't work. - debounceTime(1000), - tap((buildEvent) => { - expect(buildEvent.success).toBe(true); - if (startTime != undefined) { - intervals.push(Date.now() - startTime - 1000); - } - startTime = Date.now(); - host.appendToFile('src/main.ts', 'console.log(1);'); - }), - take(4), - ).toPromise(); - - intervals.sort(); - const median = intervals[Math.trunc(intervals.length / 2)]; - expect(median).toBeGreaterThan(3000); - expect(median).toBeLessThan(12000); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/replacements_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/replacements_spec.ts deleted file mode 100644 index 795cbadaf918..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/replacements_spec.ts +++ /dev/null @@ -1,213 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { logging, normalize, virtualFs } from '@angular-devkit/core'; -import { of, race } from 'rxjs'; -import { delay, filter, map, take, takeUntil, takeWhile, tap, timeout } from 'rxjs/operators'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - - -describe('Browser Builder file replacements', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - beforeEach(() => host.writeMultipleFiles({ - 'src/meaning-too.ts': 'export var meaning = 42;', - 'src/meaning.ts': `export var meaning = 10;`, - - 'src/main.ts': ` - import { meaning } from './meaning'; - - console.log(meaning); - `, - })); - - it('allows file replacements', async () => { - const overrides = { - fileReplacements: [ - { - replace: normalize('/src/meaning.ts'), - with: normalize('/src/meaning-too.ts'), - }, - ], - }; - - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['main.js']).toMatch(/meaning\s*=\s*42/); - expect(await files['main.js']).not.toMatch(/meaning\s*=\s*10/); - }); - - it(`allows file replacements with deprecated format`, async () => { - const overrides = { - fileReplacements: [ - { - src: normalize('/src/meaning.ts'), - replaceWith: normalize('/src/meaning-too.ts'), - }, - ], - }; - - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['main.js']).toMatch(/meaning\s*=\s*42/); - expect(await files['main.js']).not.toMatch(/meaning\s*=\s*10/); - }); - - it(`fails compilation with missing 'replace' file`, async () => { - const overrides = { - fileReplacements: [ - { - replace: normalize('/src/meaning.ts'), - with: normalize('/src/meaning-three.ts'), - }, - ], - }; - - const run = await architect.scheduleTarget(target, overrides); - try { - await run.result; - expect('THE ABOVE LINE SHOULD THROW').toBe(''); - } catch {} - await run.stop(); - }); - - it(`fails compilation with missing 'with' file`, async () => { - const overrides = { - fileReplacements: [ - { - replace: normalize('/src/meaning-three.ts'), - with: normalize('/src/meaning-too.ts'), - }, - ], - }; - - const run = await architect.scheduleTarget(target, overrides); - try { - await run.result; - expect('THE ABOVE LINE SHOULD THROW').toBe(''); - } catch {} - await run.stop(); - }); - - it('file replacements work with watch mode', async () => { - const overrides = { - fileReplacements: [ - { - replace: normalize('/src/meaning.ts'), - with: normalize('/src/meaning-too.ts'), - }, - ], - watch: true, - }; - - let buildCount = 0; - let phase = 1; - - const run = await architect.scheduleTarget(target, overrides); - await run.output.pipe( - timeout(30000), - tap((result) => { - expect(result.success).toBe(true, 'build should succeed'); - - const fileName = normalize('dist/main.js'); - const content = virtualFs.fileBufferToString(host.scopedSync().read(fileName)); - const has42 = /meaning\s*=\s*42/.test(content); - buildCount++; - switch (phase) { - case 1: - const has10 = /meaning\s*=\s*10/.test(content); - - if (has42 && !has10) { - phase = 2; - host.writeMultipleFiles({ - 'src/meaning-too.ts': 'export var meaning = 84;', - }); - } - break; - - case 2: - const has84 = /meaning\s*=\s*84/.test(content); - - if (has84 && !has42) { - phase = 3; - } else { - // try triggering a rebuild again - host.writeMultipleFiles({ - 'src/meaning-too.ts': 'export var meaning = 84;', - }); - } - break; - } - }), - takeWhile(() => phase < 3), - ).toPromise().catch(() => { - throw new Error(`stuck at phase ${phase} [builds: ${buildCount}]`); - }); - - await run.stop(); - }); - - it('file replacements work with forked type checker on watch mode', async () => { - host.writeMultipleFiles({ - 'src/file-replaced.ts': 'export var obj = { one: 1, two: 2 };', - 'src/file.ts': `export var obj = { one: 1 };`, - 'src/main.ts': ` - import { obj } from './file'; - console.log(obj.two); - `, - }); - - const overrides = { - fileReplacements: [{ - replace: normalize('/src/file.ts'), - with: normalize('/src/file-replaced.ts'), - }], - watch: true, - }; - - const unexpectedError = `Property 'two' does not exist on type '{ one: number; }'`; - const expectedError = `Property 'prop' does not exist on type '{}'`; - - const logger = new logging.Logger(''); - - // Race between a timeout and the expected log entry. - const stop$ = race( - of(null).pipe(delay(45000 * 2 / 3)), - logger.pipe( - filter(entry => entry.message.includes(expectedError)), - map(entry => entry.message), - take(1), - ), - ); - - let errorAdded = false; - const run = await architect.scheduleTarget(target, overrides, { logger }); - run.output.pipe( - tap((buildEvent) => expect(buildEvent.success).toBe(true, 'build should succeed')), - tap(() => { - // Introduce a known type error to detect in the logger filter. - if (!errorAdded) { - host.appendToFile('src/main.ts', 'console.log({}.prop);'); - errorAdded = true; - } - }), - takeUntil(stop$), - ).subscribe(); - - const res = await stop$.toPromise(); - expect(res).not.toBe(null, 'Test timed out.'); - expect(res).not.toContain(unexpectedError); - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts deleted file mode 100644 index 030d9dbc6958..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/resolve-json-module_spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { join, virtualFs } from '@angular-devkit/core'; -import { take, tap } from 'rxjs/operators'; -import { createArchitect, host, outputPath } from '../../test-utils'; - - -describe('Browser Builder resolve json module', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works with watch', async () => { - host.writeMultipleFiles({ - 'src/my-json-file.json': `{"foo": "1"}`, - 'src/main.ts': `import * as a from './my-json-file.json'; console.log(a);`, - }); - - host.replaceInFile( - 'tsconfig.json', - '"target": "es2017"', - '"target": "es5", "resolveJsonModule": true', - ); - - const overrides = { watch: true }; - - let buildCount = 1; - const run = await architect.scheduleTarget(target, overrides); - await run.output.pipe( - tap(() => { - const content = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'main.js')), - ); - - switch (buildCount) { - case 1: - expect(content).toContain('"foo":"1"'); - host.writeMultipleFiles({ - 'src/my-json-file.json': `{"foo": "2"}`, - }); - break; - case 2: - expect(content).toContain('"foo":"2"'); - break; - } - - buildCount++; - }), - tap((buildEvent) => expect(buildEvent.success).toBe(true)), - take(2), - ).toPromise(); - }); - -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/resources-output-path_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/resources-output-path_spec.ts deleted file mode 100644 index fb9e0a87b6ae..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/resources-output-path_spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -import { Architect } from '@angular-devkit/architect'; -import { normalize } from '@angular-devkit/core'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - - -describe('Browser Builder styles resources output path', () => { - const imgSvg = ` - - - - `; - - function writeFiles() { - // Use a large image for the relative ref so it cannot be inlined. - host.copyFile('src/spectrum.png', './src/assets/global-img-relative.png'); - host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png'); - host.writeMultipleFiles({ - 'src/styles.css': ` - h1 { background: url('https://github.com/assets/global-img-absolute.svg'); } - h2 { background: url('https://github.com/assets/global-img-relative.png'); } - `, - 'src/app/app.component.css': ` - h3 { background: url('https://github.com/assets/component-img-absolute.svg'); } - h4 { background: url('https://github.com/assets/component-img-relative.png'); } - `, - 'src/assets/global-img-absolute.svg': imgSvg, - 'src/assets/component-img-absolute.svg': imgSvg, - }); - } - - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it(`supports resourcesOutputPath in resource urls`, async () => { - writeFiles(); - // Check base paths are correctly generated. - const overrides = { - aot: true, - extractCss: true, - resourcesOutputPath: 'out-assets', - }; - - const { files } = await browserBuild(architect, host, target, overrides); - const styles = await files['styles.css']; - const main = await files['main.js']; - - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(styles).toContain(`url('https://github.com/out-assets/global-img-relative.png')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/out-assets/component-img-relative.png')`); - - expect(host.scopedSync() - .exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); - expect(host.scopedSync() - .exists(normalize('dist/out-assets/global-img-relative.png'))).toBe(true); - expect(host.scopedSync() - .exists(normalize('dist/assets/component-img-absolute.svg'))).toBe(true); - expect(host.scopedSync() - .exists(normalize('dist/out-assets/component-img-relative.png'))).toBe(true); - }); - - it(`supports blank resourcesOutputPath`, async () => { - writeFiles(); - - // Check base paths are correctly generated. - const overrides = { aot: true, extractCss: true }; - const { files } = await browserBuild(architect, host, target, overrides); - const styles = await files['styles.css']; - const main = await files['main.js']; - - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(styles).toContain(`url('https://github.com/global-img-relative.png')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/component-img-relative.png')`); - expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))) - .toBe(true); - expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))) - .toBe(true); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/scripts-array_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/scripts-array_spec.ts deleted file mode 100644 index 3451187e839a..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/scripts-array_spec.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { logging } from '@angular-devkit/core'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - - -describe('Browser Builder scripts array', () => { - const scripts: { [path: string]: string } = { - 'src/input-script.js': 'console.log(\'input-script\'); var number = 1+1;', - 'src/zinput-script.js': 'console.log(\'zinput-script\');', - 'src/finput-script.js': 'console.log(\'finput-script\');', - 'src/uinput-script.js': 'console.log(\'uinput-script\');', - 'src/binput-script.js': 'console.log(\'binput-script\');', - 'src/ainput-script.js': 'console.log(\'ainput-script\');', - 'src/cinput-script.js': 'console.log(\'cinput-script\');', - 'src/lazy-script.js': 'console.log(\'lazy-script\');', - 'src/pre-rename-script.js': 'console.log(\'pre-rename-script\');', - 'src/pre-rename-lazy-script.js': 'console.log(\'pre-rename-lazy-script\');', - }; - const getScriptsOption = () => [ - 'src/input-script.js', - 'src/zinput-script.js', - 'src/finput-script.js', - 'src/uinput-script.js', - 'src/binput-script.js', - 'src/ainput-script.js', - 'src/cinput-script.js', - { input: 'src/lazy-script.js', bundleName: 'lazy-script', inject: false }, - { input: 'src/pre-rename-script.js', bundleName: 'renamed-script' }, - { input: 'src/pre-rename-lazy-script.js', bundleName: 'renamed-lazy-script', inject: false }, - ]; - - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works', async () => { - const matches: Record = { - 'scripts.js': 'input-script', - 'lazy-script.js': 'lazy-script', - 'renamed-script.js': 'pre-rename-script', - 'renamed-lazy-script.js': 'pre-rename-lazy-script', - 'main.js': 'input-script', - 'index.html': '' - + '' - + '' - + '' - + '' - + '', - }; - - host.writeMultipleFiles(scripts); - host.appendToFile('src/main.ts', '\nimport \'./input-script.js\';'); - - // Remove styles so we don't have to account for them in the index.html order check. - const { files } = await browserBuild(architect, host, target, { - styles: [], - scripts: getScriptsOption(), - } as {}); - - for (const [fileName, content] of Object.entries(matches)) { - expect(await files[fileName]).toMatch(content); - } - }); - - it('works in watch mode with differential loading', async () => { - const matches: Record = { - 'scripts.js': 'input-script', - 'lazy-script.js': 'lazy-script', - 'renamed-script.js': 'pre-rename-script', - 'renamed-lazy-script.js': 'pre-rename-lazy-script', - 'main-es2017.js': 'input-script', - 'index.html': '' - + '' - + '' - + '' - + '' - + '', - }; - - host.writeMultipleFiles(scripts); - host.appendToFile('src/main.ts', '\nimport \'./input-script.js\';'); - - // Enable differential loading - host.appendToFile('.browserslistrc', '\nIE 11'); - - // Remove styles so we don't have to account for them in the index.html order check. - const { files } = await browserBuild(architect, host, target, { - styles: [], - scripts: getScriptsOption(), - watch: true, - } as {}); - - for (const [fileName, content] of Object.entries(matches)) { - expect(await files[fileName]).toMatch(content); - } - }); - - it('uglifies, uses sourcemaps, and adds hashes', async () => { - host.writeMultipleFiles(scripts); - - const { files } = await browserBuild(architect, host, target, { - optimization: true, - sourceMap: true, - outputHashing: 'all', - scripts: getScriptsOption(), - } as {}); - - const fileNames = Object.keys(files); - const scriptsBundle = fileNames.find(n => /scripts\.[0-9a-f]{20}\.js/.test(n)); - expect(scriptsBundle).toBeTruthy(); - expect(await files[scriptsBundle || '']).toMatch('var number=2;'); - - expect(fileNames.some(n => /scripts\.[0-9a-f]{20}\.js\.map/.test(n))).toBeTruthy(); - expect(fileNames.some(n => /renamed-script\.[0-9a-f]{20}\.js/.test(n))).toBeTruthy(); - expect(fileNames.some(n => /renamed-script\.[0-9a-f]{20}\.js\.map/.test(n))).toBeTruthy(); - expect(fileNames.some(n => /script\.[0-9a-f]{20}\.js/.test(n))).toBeTruthy(); - expect(await files['lazy-script.js']).not.toBeUndefined(); - expect(await files['lazy-script.js.map']).not.toBeUndefined(); - expect(await files['renamed-lazy-script.js']).not.toBeUndefined(); - expect(await files['renamed-lazy-script.js.map']).not.toBeUndefined(); - }); - - it('preserves script order', async () => { - host.writeMultipleFiles(scripts); - - const { files } = await browserBuild(architect, host, target, { - scripts: getScriptsOption(), - } as {}); - - expect(await files['scripts.js']).toMatch(new RegExp( - /.*['"]input-script['"](.|\n|\r)*/.source - + /['"]zinput-script['"](.|\n|\r)*/.source - + /['"]finput-script['"](.|\n|\r)*/.source - + /['"]uinput-script['"](.|\n|\r)*/.source - + /['"]binput-script['"](.|\n|\r)*/.source - + /['"]ainput-script['"](.|\n|\r)*/.source - + /['"]cinput-script['"]/.source, - )); - }); - - it('chunk in entry', async () => { - host.writeMultipleFiles(scripts); - - const logger = new logging.Logger('build-script-chunk-entry'); - const logs: string[] = []; - logger.subscribe(({ message }) => { - logs.push(message); - }); - - await browserBuild( - architect, - host, - target, - { - scripts: getScriptsOption(), - } as {}, - { logger }, - ); - - const joinedLogs = logs.join('\n'); - expect(joinedLogs).toMatch(/lazy-script.+69 bytes/); - expect(joinedLogs).toMatch(/renamed-script.+78 bytes/); - expect(joinedLogs).toMatch(/renamed-lazy-script.+88 bytes/); - expect(joinedLogs).not.toContain('Lazy Chunks'); - }); - - it(`should error when a script doesn't exist`, async () => { - await expectAsync(browserBuild( - architect, - host, - target, - { - scripts: ['./invalid.js'], - }, - )) - .toBeRejectedWithError(`Script file ./invalid.js does not exist.`); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/service-worker_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/service-worker_spec.ts deleted file mode 100644 index 7ce6b377b740..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/service-worker_spec.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { normalize, virtualFs } from '@angular-devkit/core'; -import { debounceTime, take, tap } from 'rxjs/operators'; -import { createArchitect, host } from '../../test-utils'; - -// tslint:disable-next-line: no-big-function -describe('Browser Builder service worker', () => { - const manifest = { - index: '/index.html', - assetGroups: [ - { - name: 'app', - installMode: 'prefetch', - resources: { - files: [ - '/favicon.ico', - '/index.html', - '/*.bundle.css', - '/*.bundle.js', - '/*.chunk.js', - ], - }, - }, - { - name: 'assets', - installMode: 'lazy', - updateMode: 'prefetch', - resources: { - files: [ - '/assets/**', - '/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)', - ], - }, - }, - ], - }; - - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('errors if no ngsw-config.json is present', async () => { - const overrides = { serviceWorker: true }; - - const run = await architect.scheduleTarget(target, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); - - await run.stop(); - }); - - it('works with service worker', async () => { - host.writeMultipleFiles({ - 'src/ngsw-config.json': JSON.stringify(manifest), - 'src/assets/folder-asset.txt': 'folder-asset.txt', - 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, - }); - - const overrides = { serviceWorker: true }; - const run = await architect.scheduleTarget(target, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - expect(host.scopedSync().exists(normalize('dist/ngsw.json'))).toBeTrue(); - const ngswJson = JSON.parse(virtualFs.fileBufferToString( - host.scopedSync().read(normalize('dist/ngsw.json')))); - // Verify index and assets are there. - expect(ngswJson).toEqual(jasmine.objectContaining({ - configVersion: 1, - index: '/index.html', - navigationUrls: [ - { positive: true, regex: '^\\\/.*$' }, - { positive: false, regex: '^\\\/(?:.+\\\/)?[^\/]*\\.[^\/]*$' }, - { positive: false, regex: '^\\\/(?:.+\\\/)?[^\/]*__[^\/]*$' }, - { positive: false, regex: '^\\\/(?:.+\\\/)?[^\/]*__[^\/]*\\\/.*$' }, - ], - assetGroups: [ - { - name: 'app', - installMode: 'prefetch', - updateMode: 'prefetch', - urls: [ - '/favicon.ico', - '/index.html', - ], - cacheQueryOptions: { - ignoreVary: true, - }, - patterns: [], - }, - { - name: 'assets', - installMode: 'lazy', - updateMode: 'prefetch', - urls: [ - '/assets/folder-asset.txt', - '/spectrum.png', - ], - cacheQueryOptions: { - ignoreVary: true, - }, - patterns: [], - }, - ], - dataGroups: [], - hashTable: { - '/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01', - '/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4', - '/index.html': 'f0bea8ced1dfbeeb771a5f48651fbcff52a625eb', - '/spectrum.png': '8d048ece46c0f3af4b598a95fd8e4709b631c3c0', - }, - })); - - await run.stop(); - }); - - it('works in watch mode', async () => { - host.writeMultipleFiles({ - 'src/ngsw-config.json': JSON.stringify(manifest), - 'src/assets/folder-asset.txt': 'folder-asset.txt', - 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, - }); - - const overrides = { serviceWorker: true, watch: true }; - const run = await architect.scheduleTarget(target, overrides); - let buildNumber = 0; - - await run.output - .pipe( - debounceTime(3000), - tap(buildEvent => { - expect(buildEvent.success).toBeTrue(); - - const ngswJsonPath = normalize('dist/ngsw.json'); - expect(host.scopedSync().exists(ngswJsonPath)).toBeTrue(); - const ngswJson = JSON.parse(virtualFs.fileBufferToString(host.scopedSync().read(ngswJsonPath))); - - const hashTableEntries = Object.keys(ngswJson.hashTable); - - buildNumber += 1; - switch (buildNumber) { - case 1: - expect(hashTableEntries).toEqual([ - '/assets/folder-asset.txt', - '/favicon.ico', - '/index.html', - '/spectrum.png', - ]); - - host.copyFile('src/assets/folder-asset.txt', 'src/assets/folder-new-asset.txt'); - break; - - case 2: - expect(hashTableEntries).toEqual([ - '/assets/folder-asset.txt', - '/assets/folder-new-asset.txt', - '/favicon.ico', - '/index.html', - '/spectrum.png', - ]); - break; - } - }), - take(2), - ) - .toPromise(); - - await run.stop(); - }); - - it('works with service worker and baseHref', async () => { - host.writeMultipleFiles({ - 'src/ngsw-config.json': JSON.stringify(manifest), - 'src/assets/folder-asset.txt': 'folder-asset.txt', - }); - - const overrides = { serviceWorker: true, baseHref: '/foo/bar' }; - const run = await architect.scheduleTarget(target, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - expect(host.scopedSync().exists(normalize('dist/ngsw.json'))).toBeTrue(); - const ngswJson = JSON.parse(virtualFs.fileBufferToString( - host.scopedSync().read(normalize('dist/ngsw.json')))); - // Verify index and assets include the base href. - expect(ngswJson).toEqual(jasmine.objectContaining({ - configVersion: 1, - index: '/foo/bar/index.html', - navigationUrls: [ - { positive: true, regex: '^\\\/.*$' }, - { positive: false, regex: '^\\\/(?:.+\\\/)?[^\/]*\\.[^\/]*$' }, - { positive: false, regex: '^\\\/(?:.+\\\/)?[^\/]*__[^\/]*$' }, - { positive: false, regex: '^\\\/(?:.+\\\/)?[^\/]*__[^\/]*\\\/.*$' }, - ], - assetGroups: [ - { - name: 'app', - installMode: 'prefetch', - updateMode: 'prefetch', - urls: [ - '/foo/bar/favicon.ico', - '/foo/bar/index.html', - ], - patterns: [], - cacheQueryOptions: { - ignoreVary: true, - }, - }, - { - name: 'assets', - installMode: 'lazy', - updateMode: 'prefetch', - urls: [ - '/foo/bar/assets/folder-asset.txt', - ], - patterns: [], - cacheQueryOptions: { - ignoreVary: true, - }, - }, - ], - dataGroups: [], - hashTable: { - '/foo/bar/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01', - '/foo/bar/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4', - '/foo/bar/index.html': 'f6650ac91428c6933dfe4c24079b3b15400da1ba', - }, - })); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/source-map_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/source-map_spec.ts deleted file mode 100644 index 73675b20f6dc..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/source-map_spec.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { OutputHashing } from '@angular-devkit/build-angular'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - -// tslint:disable-next-line:no-big-function -describe('Browser Builder source map', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works', async () => { - const overrides = { - sourceMap: true, - extractCss: true, - styles: ['src/styles.css'], - }; - - host.writeMultipleFiles({ - 'src/styles.css': `div { flex: 1 }`, - }); - - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['main.js.map']).not.toBeUndefined(); - expect(await files['styles.css.map']).not.toBeUndefined(); - }); - - it(`sourcemaps sources should not start with '/'`, async () => { - // If sourcemaps sources start with a '/' it will break VS code breakpoints - // Unless 'sourceMapPathOverrides' are provided - const overrides = { - sourceMap: true, - }; - - const { files } = await browserBuild(architect, host, target, overrides); - const mainJSMap = await files['main.js.map']; - expect(mainJSMap).not.toBeUndefined(); - - const sources: string[] = JSON.parse(mainJSMap).sources; - for (const source of sources) { - expect(source.startsWith('/')).toBe(false, `${source} started with an '/'.`); - } - }); - - it('works with outputHashing', async () => { - const { files } = await browserBuild(architect, host, target, { - sourceMap: true, - outputHashing: OutputHashing.All, - }); - - expect(Object.keys(files).some(name => /main\.[0-9a-f]{20}\.js.map/.test(name))).toBeTruthy(); - }); - - it('does not output source map when disabled', async () => { - const { files } = await browserBuild(architect, host, target, { - sourceMap: false, - }); - - expect(files['main.js.map']).toBeUndefined(); - }); - - it('supports hidden sourcemaps', async () => { - const overrides = { - sourceMap: { - hidden: true, - styles: true, - scripts: true, - }, - extractCss: true, - styles: ['src/styles.scss'], - }; - - host.writeMultipleFiles({ - 'src/styles.scss': `div { flex: 1 }`, - }); - - const { files } = await browserBuild(architect, host, target, overrides); - expect('main.js.map' in files).toBe(true); - expect('styles.css.map' in files).toBe(true); - expect(await files['main.js']).not.toContain('sourceMappingURL=main.js.map'); - expect(await files['styles.css']).not.toContain('sourceMappingURL=styles.css.map'); - }); - - it('supports styles only sourcemaps', async () => { - const overrides = { - sourceMap: { - styles: true, - scripts: false, - }, - extractCss: true, - styles: ['src/styles.scss'], - }; - - host.writeMultipleFiles({ - 'src/styles.scss': `div { flex: 1 }`, - }); - - const { files } = await browserBuild(architect, host, target, overrides); - expect('main.js.map' in files).toBe(false); - expect('styles.css.map' in files).toBe(true); - expect(await files['main.js']).not.toContain('sourceMappingURL=main.js.map'); - expect(await files['styles.css']).toContain('sourceMappingURL=styles.css.map'); - }); - - it('supports scripts only sourcemaps', async () => { - const overrides = { - sourceMap: { - styles: false, - scripts: true, - }, - extractCss: true, - styles: ['src/styles.scss'], - }; - - host.writeMultipleFiles({ - 'src/styles.scss': `div { flex: 1 }`, - }); - - const { files } = await browserBuild(architect, host, target, overrides); - expect('main.js.map' in files).toBe(true); - expect('styles.css.map' in files).toBe(false); - expect(await files['main.js']).toContain('sourceMappingURL=main.js.map'); - expect(await files['styles.css']).not.toContain('sourceMappingURL=styles.css.map'); - }); - - it('should not inline component styles sourcemaps when hidden', async () => { - const overrides = { - sourceMap: { - hidden: true, - styles: true, - scripts: true, - }, - extractCss: true, - styles: ['src/styles.scss'], - }; - - host.writeMultipleFiles({ - 'src/styles.scss': `div { flex: 1 }`, - 'src/app/app.component.css': `p { color: red; }`, - }); - - const { files } = await browserBuild(architect, host, target, overrides); - expect('main.js.map' in files).toBe(true); - expect('styles.css.map' in files).toBe(true); - expect(await files['main.js']).not.toContain('sourceMappingURL=main.js.map'); - expect(await files['main.js']).not.toContain('sourceMappingURL=data:application/json'); - expect(await files['styles.css']).not.toContain('sourceMappingURL=styles.css.map'); - expect(await files['styles.css']).not.toContain('sourceMappingURL=data:application/json'); - }); - - it('should resolve sources to partial SCSS files', async () => { - const overrides = { - sourceMap: true, - extractCss: true, - styles: ['src/styles.scss'], - }; - - host.writeMultipleFiles({ - 'src/styles.scss': `@import 'https://github.com/partial';`, - 'src/_partial.scss': `p { color: red; }`, - }); - - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css.map']).toContain('_partial.scss'); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/stats-json_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/stats-json_spec.ts deleted file mode 100644 index 4ef7c3a5c0b6..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/stats-json_spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - - -describe('Browser Builder stats json', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works', async () => { - const { files } = await browserBuild(architect, host, target, { statsJson: true }); - expect('stats.json' in files).toBe(true); - }); - - it('works with profile flag', async () => { - const { files } = await browserBuild(architect, host, target, { statsJson: true }); - expect('stats.json' in files).toBe(true); - const stats = JSON.parse(await files['stats.json']); - expect(stats.chunks[0].modules[0].profile.building).toBeDefined(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/styles_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/styles_spec.ts deleted file mode 100644 index 7e8e64ec45e5..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/styles_spec.ts +++ /dev/null @@ -1,693 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function - -import { Architect } from '@angular-devkit/architect'; -import { normalize, tags } from '@angular-devkit/core'; -import { dirname } from 'path'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - -describe('Browser Builder styles', () => { - const extensionsWithImportSupport = ['css', 'scss', 'less', 'styl']; - const extensionsWithVariableSupport = ['scss', 'less', 'styl']; - const imgSvg = ` - - - - `; - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('supports global styles', async () => { - const styles = [ - 'src/input-style.css', - { input: 'src/lazy-style.css', bundleName: 'lazy-style', inject: false }, - { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, - { input: 'src/pre-rename-lazy-style.css', bundleName: 'renamed-lazy-style', inject: false }, - ] as {}; - const cssMatches: { [path: string]: string } = { - 'styles.css': '.input-style', - 'lazy-style.css': '.lazy-style', - 'renamed-style.css': '.pre-rename-style', - 'renamed-lazy-style.css': '.pre-rename-lazy-style', - }; - const cssIndexMatches: { [path: string]: string } = { - 'index.html': - '' + - '', - }; - const jsMatches: { [path: string]: string } = { - 'styles.js': '.input-style', - 'lazy-style.js': '.lazy-style', - 'renamed-style.js': '.pre-rename-style', - 'renamed-lazy-style.js': '.pre-rename-lazy-style', - }; - const jsIndexMatches: { [path: string]: string } = { - 'index.html': - '' + - '' + - '' + - '' + - '' + - '', - }; - - host.writeMultipleFiles({ - 'src/string-style.css': '.string-style { color: red }', - 'src/input-style.css': '.input-style { color: red }', - 'src/lazy-style.css': '.lazy-style { color: red }', - 'src/pre-rename-style.css': '.pre-rename-style { color: red }', - 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', - }); - - let { files } = await browserBuild(architect, host, target, { extractCss: true, styles }); - // Check css files were created. - for (const cssFileName of Object.keys(cssMatches)) { - expect(await files[cssFileName]).toMatch(cssMatches[cssFileName]); - } - // Check no js files are created. - for (const jsFileName of Object.keys(jsMatches)) { - expect(jsFileName in files).toBe(false); - } - - // Check check index has styles in the right order. - for (const cssIndexFileName of Object.keys(cssIndexMatches)) { - expect(await files[cssIndexFileName]).toMatch(cssIndexMatches[cssIndexFileName]); - } - - // Also test with extractCss false. - files = (await browserBuild(architect, host, target, { extractCss: false, styles })).files; - - // Check js files were created. - for (const jsFileName of Object.keys(jsMatches)) { - expect(await files[jsFileName]).toMatch(jsMatches[jsFileName]); - } - - // Check no css files are created. - for (const cssFileName of Object.keys(cssMatches)) { - expect(cssFileName in files).toBe(false); - } - - // Check check index has styles in the right order. - for (const jsIndexFileName of Object.keys(jsIndexMatches)) { - expect(await files[jsIndexFileName]).toMatch(jsIndexMatches[jsIndexFileName]); - } - }); - - it('supports empty styleUrls in components', async () => { - host.writeMultipleFiles({ - './src/app/app.component.ts': ` - import { Component } from '@angular/core'; - - @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: [] - }) - export class AppComponent { - title = 'app'; - } - `, - }); - - await browserBuild(architect, host, target, { extractCss: true }); - }); - - it('supports autoprefixer with inline component styles in JIT mode', async () => { - host.writeMultipleFiles({ - './src/app/app.component.ts': ` - import { Component } from '@angular/core'; - - @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styles: ['div { flex: 1 }'], - }) - export class AppComponent { - title = 'app'; - } - `, - '.browserslistrc': 'IE 10', - }); - - // Set target to ES5 to avoid differential loading and unnecessary testing time - host.replaceInFile('tsconfig.json', '"target": "es2017"', '"target": "es5"'); - - const { files } = await browserBuild(architect, host, target, { aot: false }); - - expect(await files['main.js']).toContain('-ms-flex: 1;'); - }); - - it('supports autoprefixer with inline component styles in AOT mode', async () => { - host.writeMultipleFiles({ - './src/app/app.component.ts': ` - import { Component } from '@angular/core'; - - @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styles: ['div { flex: 1 }'], - }) - export class AppComponent { - title = 'app'; - } - `, - '.browserslistrc': 'IE 10', - }); - - // Set target to ES5 to avoid differential loading and unnecessary testing time - host.replaceInFile('tsconfig.json', '"target": "es2017"', '"target": "es5"'); - - const { files } = await browserBuild(architect, host, target, { aot: true }); - - expect(await files['main.js']).toContain('-ms-flex: 1;'); - }); - - extensionsWithImportSupport.forEach(ext => { - it(`supports imports in ${ext} files`, async () => { - host.writeMultipleFiles({ - [`src/styles.${ext}`]: ` - @import 'https://github.com/imported-styles.$%7Bext%7D'; - body { background-color: #00f; } - `, - [`src/imported-styles.${ext}`]: 'p { background-color: #f00; }', - [`src/app/app.component.${ext}`]: ` - @import 'https://github.com/imported-component-styles.$%7Bext%7D'; - .outer { - .inner { - background: #fff; - } - } - `, - [`src/app/imported-component-styles.${ext}`]: 'h1 { background: #000; }', - }); - - const matches: { [path: string]: RegExp } = { - 'styles.css': new RegExp( - // The global style should be there - /p\s*{\s*background-color: #f00;\s*}(.|\n|\r)*/.source + - // The global style via import should be there - /body\s*{\s*background-color: #00f;\s*}/.source, - ), - 'styles.css.map': /"mappings":".+"/, - 'main.js': new RegExp( - // The component style should be there - /h1(.|\n|\r)*background:\s*#000(.|\n|\r)*/.source + - // The component style via import should be there - /.outer(.|\n|\r)*.inner(.|\n|\r)*background:\s*#[fF]+/.source, - ), - }; - - const overrides = { - extractCss: true, - sourceMap: true, - styles: [`src/styles.${ext}`], - }; - - host.replaceInFile( - 'src/app/app.component.ts', - './app.component.css', - `./app.component.${ext}`, - ); - - const { files } = await browserBuild(architect, host, target, overrides); - for (const fileName of Object.keys(matches)) { - expect(await files[fileName]).toMatch(matches[fileName]); - } - }); - }); - - extensionsWithImportSupport.forEach(ext => { - it(`supports material imports in ${ext} files`, async () => { - host.writeMultipleFiles({ - [`src/styles.${ext}`]: ` - @import "https://github.com/~@angular/material/prebuilt-themes/indigo-pink.css"; - @import "https://github.com/@angular/material/prebuilt-themes/indigo-pink.css"; - `, - [`src/app/app.component.${ext}`]: ` - @import "https://github.com/~@angular/material/prebuilt-themes/indigo-pink.css"; - @import "https://github.com/@angular/material/prebuilt-themes/indigo-pink.css"; - `, - }); - host.replaceInFile( - 'src/app/app.component.ts', - './app.component.css', - `./app.component.${ext}`, - ); - - const overrides = { - extractCss: true, - styles: [{ input: `src/styles.${ext}` }], - }; - await browserBuild(architect, host, target, overrides); - }); - }); - - extensionsWithVariableSupport.forEach(ext => { - it(`supports ${ext} includePaths`, async () => { - let variableAssignment = ''; - let variablereference = ''; - if (ext === 'scss') { - variableAssignment = '$primary-color:'; - variablereference = '$primary-color'; - } else if (ext === 'styl') { - variableAssignment = '$primary-color ='; - variablereference = '$primary-color'; - } else if (ext === 'less') { - variableAssignment = '@primary-color:'; - variablereference = '@primary-color'; - } - - host.writeMultipleFiles({ - [`src/style-paths/variables.${ext}`]: `${variableAssignment} #f00;`, - [`src/styles.${ext}`]: ` - @import 'https://github.com/variables'; - h1 { color: ${variablereference}; } - `, - [`src/app/app.component.${ext}`]: ` - @import 'https://github.com/variables'; - h2 { color: ${variablereference}; } - `, - }); - - const matches: { [path: string]: RegExp } = { - 'styles.css': /h1\s*{\s*color: #f00;\s*}/, - 'main.js': /h2.*{.*color: #f00;.*}/, - }; - - host.replaceInFile( - 'src/app/app.component.ts', - './app.component.css', - `./app.component.${ext}`, - ); - - const overrides = { - extractCss: true, - styles: [`src/styles.${ext}`], - stylePreprocessorOptions: { - includePaths: ['src/style-paths'], - }, - }; - - const { files } = await browserBuild(architect, host, target, overrides); - for (const fileName of Object.keys(matches)) { - expect(await files[fileName]).toMatch(matches[fileName]); - } - }); - }); - - it(`supports font-awesome imports`, async () => { - host.writeMultipleFiles({ - 'src/styles.scss': ` - @import "https://github.com/font-awesome/scss/font-awesome"; - `, - }); - - const overrides = { extractCss: true, styles: [`src/styles.scss`] }; - await browserBuild(architect, host, target, overrides); - }); - - it(`supports font-awesome imports (tilde)`, async () => { - host.writeMultipleFiles({ - 'src/styles.scss': ` - $fa-font-path: "~font-awesome/fonts"; - @import "https://github.com/~font-awesome/scss/font-awesome"; - `, - }); - - const overrides = { extractCss: true, styles: [`src/styles.scss`] }; - await browserBuild(architect, host, target, overrides); - }); - - it(`supports font-awesome imports without extractCss`, async () => { - host.writeMultipleFiles({ - 'src/styles.scss': ` - @import "https://github.com/~font-awesome/css/font-awesome.css"; - `, - }); - - const overrides = { extractCss: false, styles: [`src/styles.scss`] }; - await browserBuild(architect, host, target, overrides); - }); - - it(`uses autoprefixer`, async () => { - host.writeMultipleFiles({ - 'src/styles.css': tags.stripIndents` - @import url(https://github.com/imported-styles.css); - /* normal-comment */ - /*! important-comment */ - div { flex: 1 }`, - 'src/imported-styles.css': tags.stripIndents` - /* normal-comment */ - /*! important-comment */ - section { flex: 1 }`, - '.browserslistrc': 'IE 10', - }); - - // Set to target to ES5 to avoid differential loading and unnecessary testing time - host.replaceInFile('tsconfig.json', '"target": "es2017"', '"target": "es5"'); - - const overrides = { extractCss: true, optimization: false }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css']).toContain(tags.stripIndents` - /* normal-comment */ - /*! important-comment */ - section { -ms-flex: 1; flex: 1 } - /* normal-comment */ - /*! important-comment */ - div { -ms-flex: 1; flex: 1 }`); - }); - - it(`minimizes css`, async () => { - host.writeMultipleFiles({ - 'src/styles.css': tags.stripIndents` - /* normal-comment */ - /*! important-comment */ - div { flex: 1 }`, - }); - - const overrides = { extractCss: true, optimization: true }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css']).toContain('/*! important-comment */div{flex:1}'); - }); - - it('supports autoprefixer grid comments in SCSS with optimization true', async () => { - host.writeMultipleFiles({ - 'src/styles.scss': tags.stripIndents` - /* autoprefixer grid: autoplace */ - .css-grid-container { - display: grid; - row-gap: 10px; - grid-template-columns: 100px; - } - `, - '.browserslistrc': 'IE 10', - }); - - // Set target to ES5 to avoid differential loading and unnecessary testing time - host.replaceInFile('tsconfig.json', '"target": "es2017"', '"target": "es5"'); - - const overrides = { extractCss: true, optimization: true, styles: ['src/styles.scss'] }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css']).toContain('-ms-grid-columns:100px;'); - }); - - // TODO: consider making this a unit test in the url processing plugins. - it(`supports baseHref/deployUrl in resource urls`, async () => { - // Use a large image for the relative ref so it cannot be inlined. - host.copyFile('src/spectrum.png', './src/assets/global-img-relative.png'); - host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png'); - host.writeMultipleFiles({ - 'src/styles.css': ` - h1 { background: url('https://github.com/assets/global-img-absolute.svg'); } - h2 { background: url('https://github.com/assets/global-img-relative.png'); } - `, - 'src/app/app.component.css': ` - h3 { background: url('https://github.com/assets/component-img-absolute.svg'); } - h4 { background: url('https://github.com/assets/component-img-relative.png'); } - `, - 'src/assets/global-img-absolute.svg': imgSvg, - 'src/assets/component-img-absolute.svg': imgSvg, - }); - - let { files } = await browserBuild(architect, host, target, { aot: true, extractCss: true }); - - // Check base paths are correctly generated. - let styles = await files['styles.css']; - let main = await files['main.js']; - - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(styles).toContain(`url('https://github.com/global-img-relative.png')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/component-img-relative.png')`); - - expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); - expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))).toBe(true); - expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))).toBe( - true, - ); - expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))).toBe(true); - - // Check urls with deploy-url scheme are used as is. - files = (await browserBuild(architect, host, target, { - extractCss: true, - baseHref: '/base/', - deployUrl: 'http://deploy.url/', - })).files; - - styles = await files['styles.css']; - main = await files['main.js']; - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - - // Check urls with base-href scheme are used as is (with deploy-url). - files = (await browserBuild(architect, host, target, { - extractCss: true, - baseHref: 'http://base.url/', - deployUrl: 'deploy/', - })).files; - styles = await files['styles.css']; - main = await files['main.js']; - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - - // Check urls with deploy-url and base-href scheme only use deploy-url. - files = (await browserBuild(architect, host, target, { - extractCss: true, - baseHref: 'http://base.url/', - deployUrl: 'http://deploy.url/', - })).files; - styles = await files['styles.css']; - main = await files['main.js']; - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - - // Check with schemeless base-href and deploy-url flags. - files = (await browserBuild(architect, host, target, { - extractCss: true, - baseHref: '/base/', - deployUrl: 'deploy/', - })).files; - styles = await files['styles.css']; - main = await files['main.js']; - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - - // Check with identical base-href and deploy-url flags. - files = (await browserBuild(architect, host, target, { - extractCss: true, - baseHref: '/base/', - deployUrl: '/base/', - })).files; - - styles = await files['styles.css']; - main = await files['main.js']; - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - - // Check with only base-href flag. - files = (await browserBuild(architect, host, target, { - extractCss: true, - baseHref: '/base/', - })).files; - - styles = await files['styles.css']; - main = await files['main.js']; - expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); - expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); - // NOTE: Timeout for large amount of builds in test. Test should be split up when refactored. - }, 4 * 60 * 1000); - - it(`supports bootstrap@4 with full path`, async () => { - const bootstrapPath = dirname(require.resolve('bootstrap/package.json')); - - const overrides = { - extractCss: true, - styles: [bootstrapPath + '/dist/css/bootstrap.css'], - scripts: [bootstrapPath + '/dist/js/bootstrap.js'], - }; - - await browserBuild(architect, host, target, overrides); - }); - - it(`supports bootstrap@4 with package reference`, async () => { - const overrides = { - extractCss: true, - styles: ['bootstrap/dist/css/bootstrap.css'], - scripts: ['bootstrap/dist/js/bootstrap.js'], - }; - - await browserBuild(architect, host, target, overrides); - }); - - it(`supports inline javascript in less`, async () => { - const overrides = { styles: [`src/styles.less`] }; - host.writeMultipleFiles({ - 'src/styles.less': ` - .myFunction() { - @functions: ~\`(function () { - return ''; - })()\`; - } - .myFunction(); - `, - }); - - await browserBuild(architect, host, target, overrides); - }); - - it('causes equal failure for tilde and tilde-slash url()', async () => { - host.writeMultipleFiles({ - 'src/styles.css': ` - body { - background-image: url('https://github.com/~/does-not-exist.jpg'); - } - `, - }); - - const overrides = { extractCss: true, optimization: true }; - const run = await architect.scheduleTarget(target, overrides); - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); - - host.writeMultipleFiles({ - 'src/styles.css': ` - body { - background-image: url('https://github.com/~does-not-exist.jpg'); - } - `, - }); - - const run2 = await architect.scheduleTarget(target, overrides); - await expectAsync(run2.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); - }); - - it('supports Protocol-relative Url', async () => { - host.writeMultipleFiles({ - 'src/styles.css': ` - body { - background-image: url('https://cdn.com/classic-bg.jpg'); - } - `, - }); - - const overrides = { extractCss: true, optimization: true }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css']).toContain('background-image:url(https://cdn.com/classic-bg.jpg)'); - }); - - it('supports fonts with space in filename', async () => { - host.writeMultipleFiles({ - 'src/styles.css': ` - @font-face { - font-family: "Font Awesome"; - src: url("https://github.com/assets/fa%20solid-900.woff2") format("woff2"); - } - - body { - font-family: "Font Awesome"; - } - `, - 'src/assets/fa solid-900.woff2': '', - }); - - const overrides = { extractCss: true }; - const { output } = await browserBuild(architect, host, target, overrides); - expect(output.success).toBe(true); - }); - - extensionsWithImportSupport.forEach(ext => { - it(`retains declarations order in ${ext} files with extractCss when using @import`, async () => { - host.writeMultipleFiles({ - [`src/styles-one.${ext}`]: tags.stripIndents` - .one { - color: #fff; - } - `, - [`src/styles-two.${ext}`]: tags.stripIndents` - .two { - color: #fff; - } - `, - // LESS doesn't support css imports by default. - // See: https://github.com/less/less.js/issues/3188#issuecomment-374690630 - [`src/styles-three.${ext}`]: tags.stripIndents` - @import ${ext === 'less' ? ' (css) ' : ''}url("https://fonts.googleapis.com/css?family=Roboto:400"); - .three { - color: #fff; - } - `, - }); - - const overrides = { - extractCss: true, - styles: [ - `src/styles-one.${ext}`, - `src/styles-two.${ext}`, - `src/styles-three.${ext}`, - ], - }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css']).toMatch(/\.one(.|\n|\r)*\.two(.|\n|\r)*\.three/); - }); - }); - - extensionsWithImportSupport.forEach(ext => { - it(`adjusts relative resource URLs when using @import in ${ext} (global)`, async () => { - host.copyFile('src/spectrum.png', './src/more-styles/images/global-img-relative.png'); - host.writeMultipleFiles({ - [`src/styles-one.${ext}`]: tags.stripIndents` - @import "https://github.com/more-styles/styles-two.$%7Bext%7D"; - `, - [`src/more-styles/styles-two.${ext}`]: tags.stripIndents` - .two { - background-image: url(https://github.com/images/global-img-relative.png); - } - `, - }); - - const overrides = { - sourceMap: false, - extractCss: true, - styles: [ - `src/styles-one.${ext}`, - ], - }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['styles.css']).toContain('\'global-img-relative.png\''); - }); - - it(`adjusts relative resource URLs when using @import in ${ext} (component)`, async () => { - host.copyFile('src/spectrum.png', './src/app/images/component-img-relative.png'); - host.writeMultipleFiles({ - [`src/app/styles/component-styles.${ext}`]: ` - div { background-image: url(https://github.com/images/component-img-relative.png); } - `, - [`src/app/app.component.${ext}`]: ` - @import "https://github.com/styles/component-styles.$%7Bext%7D"; - `, - }); - - host.replaceInFile( - 'src/app/app.component.ts', - './app.component.css', - `./app.component.${ext}`, - ); - - const overrides = { - sourceMap: false, - }; - await browserBuild(architect, host, target, overrides); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/unused-files-warning_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/unused-files-warning_spec.ts deleted file mode 100644 index d1295d67a07a..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/unused-files-warning_spec.ts +++ /dev/null @@ -1,249 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; -import { logging } from '@angular-devkit/core'; -import { debounceTime, take, tap } from 'rxjs/operators'; -import { createArchitect, host } from '../../test-utils'; - -// tslint:disable-next-line:no-big-function -describe('Browser Builder unused files warnings', () => { - const warningMessageSuffix = `is part of the TypeScript compilation but it's unused`; - const targetSpec = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('should not show warning when all files are used', async () => { - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - expect(logs.join().includes(warningMessageSuffix)).toBe(false); - - await run.stop(); - }); - - it('should show warning when some files are unused', async () => { - host.replaceInFile( - 'src/tsconfig.app.json', - '"main.ts"', - '"main.ts", "environments/environment.prod.ts"', - ); - - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - expect(logs.join().includes(`environment.prod.ts ${warningMessageSuffix}`)).toBe(true); - - await run.stop(); - }); - - it('should not show warning when excluded files are unused', async () => { - const ignoredFiles = { - 'src/file.d.ts': 'export type MyType = number;', - }; - - host.writeMultipleFiles(ignoredFiles); - - host.replaceInFile( - 'src/tsconfig.app.json', - '"main.ts"', - `"main.ts", ${Object.keys(ignoredFiles).map(f => `"${f.replace('src/', '')}"`).join(',')}`, - ); - - host.replaceInFile( - 'src/tsconfig.app.json', - '"compilerOptions":', - '"angularCompilerOptions": { "strictTemplates": true }, "compilerOptions":', - ); - - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, { aot: true }, { logger }); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - expect(logs.join().includes(warningMessageSuffix)).toBe(false); - - await run.stop(); - }); - - it('should not show warning when type files are used', async () => { - host.writeMultipleFiles({ - 'src/app/type.ts': 'export type MyType = number;', - }); - - host.replaceInFile( - 'src/app/app.component.ts', - `'@angular/core';`, - `'@angular/core';\nimport { MyType } from './type';\n`, - ); - - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - expect(logs.join().includes(warningMessageSuffix)).toBe(false); - - await run.stop(); - }); - - it('should not show warning when type files are used transitively', async () => { - host.writeMultipleFiles({ - 'src/app/type.ts': - `import {Myinterface} from './interface'; export type MyType = Myinterface;`, - 'src/app/interface.ts': 'export interface Myinterface {nbr: number;}', - }); - - host.replaceInFile( - 'src/app/app.component.ts', - `'@angular/core';`, - `'@angular/core';\nimport { MyType } from './type';\n`, - ); - - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); - const output = await run.result as BrowserBuilderOutput; - expect(output.success).toBe(true); - expect(logs.join().includes(warningMessageSuffix)).toBe(false); - - await run.stop(); - }); - - it('works for rebuilds', async () => { - host.replaceInFile( - 'src/tsconfig.app.json', - '"**/*.d.ts"', - '"**/*.d.ts", "testing/**/*.ts"', - ); - - const logger = new logging.Logger(''); - let logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - let buildNumber = 0; - const run = await architect.scheduleTarget(targetSpec, { watch: true }, { logger }); - - await run.output - .pipe( - debounceTime(1000), - tap(buildEvent => { - expect(buildEvent.success).toBe(true); - - buildNumber ++; - switch (buildNumber) { - case 1: - // The first should not have unused files - expect(logs.join().includes(warningMessageSuffix)).toBe(false, `Case ${buildNumber} failed.`); - - // Write a used file - host.writeMultipleFiles({ - 'src/testing/type.ts': 'export type MyType = number;', - }); - - // touch file to trigger build - host.replaceInFile( - 'src/app/app.component.ts', - `'@angular/core';`, - `'@angular/core';\n`, - ); - break; - - case 2: - // The second should have type.ts as unused - expect(logs.join().includes(`type.ts ${warningMessageSuffix}`)).toBe(true, `Case ${buildNumber} failed.`); - - host.replaceInFile( - 'src/app/app.component.ts', - `'@angular/core';`, - `'@angular/core';\nimport { MyType } from '../testing/type';`, - ); - break; - - case 3: - // The third should not have any unused files - expect(logs.join().includes(warningMessageSuffix)).toBe(false, `Case ${buildNumber} failed.`); - break; - } - - logs = []; - }), - take(3), - ) - .toPromise(); - await run.stop(); - }); - - it('should only show warning once per file', async () => { - host.replaceInFile( - 'src/tsconfig.app.json', - '"**/*.d.ts"', - '"**/*.d.ts", "testing/**/*.ts"', - ); - - // Write a used file - host.writeMultipleFiles({ - 'src/testing/type.ts': 'export type MyType = number;', - }); - - const logger = new logging.Logger(''); - let logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - let buildNumber = 0; - const run = await architect.scheduleTarget(targetSpec, { watch: true }, { logger }); - - await run.output - .pipe( - debounceTime(1000), - tap(buildEvent => { - expect(buildEvent.success).toBe(true); - - buildNumber ++; - switch (buildNumber) { - case 1: - // The first should have type.ts as unused. - expect(logs.join().includes(`type.ts ${warningMessageSuffix}`)).toBe(true, `Case ${buildNumber} failed.`); - - // touch a file to trigger a rebuild - host.appendToFile('src/main.ts', ''); - break; - case 2: - // The second should should have type.ts as unused but shouldn't warn. - expect(logs.join().includes(warningMessageSuffix)).toBe(false, `Case ${buildNumber} failed.`); - break; - } - - logs = []; - }), - take(2), - ) - .toPromise(); - await run.stop(); - }); - -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/vendor-source-map_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/vendor-source-map_spec.ts deleted file mode 100644 index b2c9a3373fc7..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/vendor-source-map_spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import * as path from 'path'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - -describe('Browser Builder external source map', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it('works', async () => { - const overrides = { - sourceMap: { - scripts: true, - styles: true, - vendor: true, - }, - }; - - const { files } = await browserBuild(architect, host, target, overrides); - const sourcePaths: string[] = JSON.parse(await files['vendor.js.map']).sources; - const hasTsSourcePaths = sourcePaths.some(p => path.extname(p) == '.ts'); - expect(hasTsSourcePaths).toBe(true, `vendor.js.map should have '.ts' extentions`); - }); - - it('does not map sourcemaps from external library when disabled', async () => { - const overrides = { - sourceMap: { - scripts: true, - styles: true, - vendor: false, - }, - }; - - const { files } = await browserBuild(architect, host, target, overrides); - const sourcePaths: string[] = JSON.parse(await files['vendor.js.map']).sources; - const hasTsSourcePaths = sourcePaths.some(p => path.extname(p) == '.ts'); - expect(hasTsSourcePaths).toBe(false, `vendor.js.map not should have '.ts' extentions`); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts deleted file mode 100644 index 51d1dd6d867e..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/web-worker_spec.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { join, logging, virtualFs } from '@angular-devkit/core'; -import { timer } from 'rxjs'; -import { debounceTime, map, switchMap, takeWhile, tap } from 'rxjs/operators'; -import { browserBuild, createArchitect, host, outputPath } from '../../test-utils'; - - -describe('Browser Builder Web Worker support', () => { - const target = { project: 'app', target: 'build' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - const workerFiles: { [k: string]: string } = { - 'src/app/dep.ts': `export const foo = 'bar';`, - 'src/app/app.worker.ts': ` - /// - - import { foo } from './dep'; - console.log('hello from worker'); - addEventListener('message', ({ data }) => { - console.log('worker got message:', data); - if (data === 'hello') { - postMessage(foo); - } - }); - `, - 'src/main.ts': ` - import { enableProdMode } from '@angular/core'; - import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - import { AppModule } from './app/app.module'; - import { environment } from './environments/environment'; - if (environment.production) { enableProdMode(); } - platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)); - - const worker = new Worker(new URL('./app/app.worker', import.meta.url), { type: 'module' }); - worker.onmessage = ({ data }) => { - console.log('page got message:', data); - }; - worker.postMessage('hello'); - `, - // Make a new tsconfig for the *.worker.ts files. - // The final place for this tsconfig must take into consideration editor tooling, unit - // tests, and integration with other build targets. - './src/tsconfig.worker.json': ` - { - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/worker", - "lib": [ - "es2018", - "webworker" - ], - "types": [] - }, - "include": [ - "**/*.worker.ts", - ] - }`, - // Alter the app tsconfig to not include *.worker.ts files. - './src/tsconfig.app.json': ` - { - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/worker", - "types": [] - }, - "files": [ - "main.ts", - "polyfills.ts" - ] - }`, - }; - - it('bundles TS worker', async () => { - host.writeMultipleFiles(workerFiles); - - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - const overrides = { webWorkerTsConfig: 'src/tsconfig.worker.json' }; - await browserBuild(architect, host, target, overrides, { logger }); - - // Worker bundle contains worker code. - const workerContent = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'src_app_app_worker_ts.js'))); - expect(workerContent).toContain('hello from worker'); - expect(workerContent).toContain('bar'); - - // Main bundle references worker. - const mainContent = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'main.js'))); - expect(mainContent).toContain('src_app_app_worker_ts'); - expect(logs.join().includes('Warning')).toBe(false, 'Should show no warnings.'); - }); - - it('minimizes and hashes worker', async () => { - host.writeMultipleFiles(workerFiles); - const overrides = { - webWorkerTsConfig: 'src/tsconfig.worker.json', - outputHashing: 'all', - optimization: true, - }; - await browserBuild(architect, host, target, overrides); - - // Worker bundle should have hash and minified code. - const workerBundle = host.fileMatchExists(outputPath, /src_app_app_worker_ts\.[0-9a-f]{20}\.js/) as string; - expect(workerBundle).toBeTruthy('workerBundle should exist'); - const workerContent = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, workerBundle))); - expect(workerContent).toContain('hello from worker'); - expect(workerContent).toContain('bar'); - expect(workerContent).toContain('"hello"===e&&postMessage'); - - // Main bundle should reference hashed worker bundle. - const mainBundle = host.fileMatchExists(outputPath, /main\.[0-9a-f]{20}\.js/) as string; - expect(mainBundle).toBeTruthy('mainBundle should exist'); - const mainContent = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, mainBundle))); - expect(mainContent).toContain('src_app_app_worker_ts'); - }); - - it('rebuilds TS worker', async () => { - host.writeMultipleFiles(workerFiles); - const overrides = { - webWorkerTsConfig: 'src/tsconfig.worker.json', - watch: true, - }; - - let phase = 1; - const workerPath = join(outputPath, 'src_app_app_worker_ts.js'); - let workerContent = ''; - - // The current linux-based CI environments may not fully settled in regards to filesystem - // changes from previous tests which reuse the same directory and fileset. - // The initial delay helps mitigate false positive rebuild triggers in such scenarios. - const { run } = await timer(1000).pipe( - switchMap(() => architect.scheduleTarget(target, overrides)), - switchMap(run => run.output.pipe(map(output => ({ run, output })))), - debounceTime(1000), - tap(({ output }) => expect(output.success).toBe(true, 'build should succeed')), - tap(() => { - switch (phase) { - case 1: - // Original worker content should be there. - workerContent = virtualFs.fileBufferToString(host.scopedSync().read(workerPath)); - expect(workerContent).toContain('bar'); - // Change content of worker dependency. - host.writeMultipleFiles({ 'src/app/dep.ts': `export const foo = 'baz';` }); - phase = 2; - break; - - case 2: - workerContent = virtualFs.fileBufferToString(host.scopedSync().read(workerPath)); - // Worker content should have changed. - expect(workerContent).toContain('baz'); - phase = 3; - break; - } - }), - takeWhile(() => phase < 3), - ).toPromise(); - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/works_spec.ts b/packages/angular_devkit/build_angular/src/browser/specs/works_spec.ts deleted file mode 100644 index 8de151c2ca8c..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/specs/works_spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { describeBuilder } from '../../testing'; -import { buildWebpackBrowser } from '../index'; - -const BROWSER_BUILDER_INFO = { - name: '@angular-devkit/build-angular:browser', - schemaPath: __dirname + '/../schema.json', -}; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('basic test', () => { - it('works', async () => { - // Provide a target and options for builder execution - harness.useTarget('build', { - outputPath: 'dist', - index: 'src/index.html', - main: 'src/main.ts', - polyfills: 'src/polyfills.ts', - tsConfig: 'src/tsconfig.app.json', - progress: false, - assets: ['src/favicon.ico', 'src/assets'], - styles: ['src/styles.css'], - scripts: [], - }); - - // Execute builder with above provided project, target, and options - await harness.executeOnce(); - - // Default files should be in outputPath. - expect(harness.hasFile('dist/runtime.js')).toBe(true); - expect(harness.hasFile('dist/main.js')).toBe(true); - expect(harness.hasFile('dist/polyfills.js')).toBe(true); - expect(harness.hasFile('dist/vendor.js')).toBe(true); - expect(harness.hasFile('dist/favicon.ico')).toBe(true); - expect(harness.hasFile('dist/styles.css')).toBe(true); - expect(harness.hasFile('dist/index.html')).toBe(true); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/behavior/typescript-target_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/behavior/typescript-target_spec.ts deleted file mode 100644 index 8ebb5f784d66..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/behavior/typescript-target_spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Behavior: "TypeScript Configuration - target"', () => { - it('downlevels async functions when targetting ES2017', async () => { - // Set TypeScript configuration target to ES2017 to enable native async - await harness.modifyFile('src/tsconfig.app.json', (content) => { - const tsconfig = JSON.parse(content); - if (!tsconfig.compilerOptions) { - tsconfig.compilerOptions = {}; - } - tsconfig.compilerOptions.target = 'es2017'; - - return JSON.stringify(tsconfig); - }); - - // Add a JavaScript file with async code - await harness.writeFile( - 'src/async-test.js', - 'async function testJs() { console.log("from-async-js-function"); }', - ); - - // Add an async function to the project as well as JavaScript file - await harness.modifyFile( - 'src/main.ts', - (content) => - 'import "./async-test";\n' + - content + - `\nasync function testApp(): Promise { console.log("from-async-app-function"); }`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching('Zone.js does not support native async/await in ES2017+'), - }), - ); - - harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); - harness.expectFile('dist/main.js').content.toContain('"from-async-app-function"'); - harness.expectFile('dist/main.js').content.toContain('"from-async-js-function"'); - }); - - it('creates correct sourcemaps when downleveling async functions', async () => { - // Set TypeScript configuration target to ES2017 to enable native async - await harness.modifyFile('src/tsconfig.app.json', (content) => { - const tsconfig = JSON.parse(content); - if (!tsconfig.compilerOptions) { - tsconfig.compilerOptions = {}; - } - tsconfig.compilerOptions.target = 'es2017'; - - return JSON.stringify(tsconfig); - }); - - // Add a JavaScript file with async code - await harness.writeFile( - 'src/async-test.js', - 'async function testJs() { console.log("from-async-js-function"); }', - ); - - // Add an async function to the project as well as JavaScript file - // The type `Void123` is used as a unique identifier for the final sourcemap - // If sourcemaps are not properly propagated then it will not be in the final sourcemap - await harness.modifyFile( - 'src/main.ts', - (content) => - 'import "./async-test";\n' + - content + - '\ntype Void123 = void;' + - `\nasync function testApp(): Promise { console.log("from-async-app-function"); }`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - sourceMap: { - scripts: true, - }, - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); - harness.expectFile('dist/main.js.map').content.toContain('Promise'); - }); - - it('downlevels async functions when targetting greater than ES2017', async () => { - // Set TypeScript configuration target greater than ES2017 to enable native async - await harness.modifyFile('src/tsconfig.app.json', (content) => { - const tsconfig = JSON.parse(content); - if (!tsconfig.compilerOptions) { - tsconfig.compilerOptions = {}; - } - tsconfig.compilerOptions.target = 'es2020'; - - return JSON.stringify(tsconfig); - }); - - // Add an async function to the project - await harness.writeFile( - 'src/main.ts', - 'async function test(): Promise { console.log("from-async-function"); }', - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching('Zone.js does not support native async/await in ES2017+'), - }), - ); - - harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); - harness.expectFile('dist/main.js').content.toContain('"from-async-function"'); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/allowed-common-js-dependencies_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/allowed-common-js-dependencies_spec.ts deleted file mode 100644 index f39c6877a96e..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/allowed-common-js-dependencies_spec.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { logging } from '@angular-devkit/core'; -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, harness => { - describe('Option: "allowedCommonJsDependencies"', () => { - describe('given option is not set', () => { - for (const aot of [true, false]) { - it(`should not show warning for styles import in ${aot ? 'AOT' : 'JIT'} Mode`, async () => { - await harness.writeFile('./test.css', `body { color: red; };`); - await harness.appendToFile('src/app/app.component.ts', `import '../../test.css';`); - - harness.useTarget('build', { - ...BASE_OPTIONS, - allowedCommonJsDependencies: [], - aot, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/CommonJS or AMD dependencies/), - }), - ); - }); - - it(`should show warning when depending on a Common JS bundle in ${aot ? 'AOT' : 'JIT'} Mode`, async () => { - // Add a Common JS dependency - await harness.appendToFile('src/app/app.component.ts', `import 'bootstrap';`); - - harness.useTarget('build', { - ...BASE_OPTIONS, - allowedCommonJsDependencies: [], - aot, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/Warning: .+app\.component\.ts depends on 'bootstrap'\. CommonJS or AMD dependencies/), - }), - ); - expect(logs).not.toContain( - jasmine.objectContaining({ message: jasmine.stringMatching('jquery') }), - 'Should not warn on transitive CommonJS packages which parent is also CommonJS.', - ); - }); - } - }); - - it('should not show warning when depending on a Common JS bundle which is allowed', async () => { - // Add a Common JS dependency - await harness.appendToFile('src/app/app.component.ts', ` - import 'bootstrap'; - import 'zone.js/dist/zone-error'; - `); - - harness.useTarget('build', { - ...BASE_OPTIONS, - allowedCommonJsDependencies: [ - 'bootstrap', - 'zone.js', - ], - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/CommonJS or AMD dependencies/), - }), - ); - }); - - it(`should not show warning when importing non global local data '@angular/common/locale/fr'`, async () => { - await harness.appendToFile('src/app/app.component.ts', `import '@angular/common/locales/fr';`); - - harness.useTarget('build', { - ...BASE_OPTIONS, - allowedCommonJsDependencies: [], - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/CommonJS or AMD dependencies/), - }), - ); - }); - - it('should not show warning in JIT for templateUrl and styleUrl when using paths', async () => { - await harness.modifyFile( - 'tsconfig.json', content => { - return content.replace(/"baseUrl": ".\/",/, ` - "baseUrl": "./", - "paths": { - "@app/*": [ - "src/app/*" - ] - }, - `); - }); - - await harness.modifyFile( - 'src/app/app.module.ts', - content => content.replace('./app.component', '@app/app.component'), - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - allowedCommonJsDependencies: [], - aot: false, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/CommonJS or AMD dependencies/), - }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/assets_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/assets_spec.ts deleted file mode 100644 index 7893869156b7..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/assets_spec.ts +++ /dev/null @@ -1,394 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "assets"', () => { - beforeEach(async () => { - // Application code is not needed for asset tests - await harness.writeFile('src/main.ts', ''); - }); - - it('supports an empty array value', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - }); - - it('supports mixing shorthand and longhand syntax', async () => { - await harness.writeFile('src/files/test.svg', ''); - await harness.writeFile('src/files/another.file', 'asset file'); - await harness.writeFile('src/extra.file', 'extra file'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: ['src/extra.file', { glob: '*', input: 'src/files', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/extra.file').content.toBe('extra file'); - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').content.toBe('asset file'); - }); - - describe('shorthand syntax', () => { - it('copies a single asset', async () => { - await harness.writeFile('src/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: ['src/test.svg'], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - }); - - it('copies multiple assets', async () => { - await harness.writeFile('src/test.svg', ''); - await harness.writeFile('src/another.file', 'asset file'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: ['src/test.svg', 'src/another.file'], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').content.toBe('asset file'); - }); - - it('copies an asset with directory and maintains directory in output', async () => { - await harness.writeFile('src/subdirectory/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: ['src/subdirectory/test.svg'], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/subdirectory/test.svg').content.toBe(''); - }); - - it('does not fail if asset does not exist', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: ['src/test.svg'], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').toNotExist(); - }); - - it('throws exception if asset path is not within project source root', async () => { - await harness.writeFile('test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: ['test.svg'], - }); - - const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); - - expect(result).toBeUndefined(); - expect(error).toEqual( - jasmine.objectContaining({ - message: jasmine.stringMatching('path must start with the project source root'), - }), - ); - - harness.expectFile('dist/test.svg').toNotExist(); - }); - }); - - describe('longhand syntax', () => { - it('copies a single asset', async () => { - await harness.writeFile('src/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - }); - - it('copies multiple assets as separate entries', async () => { - await harness.writeFile('src/test.svg', ''); - await harness.writeFile('src/another.file', 'asset file'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [ - { glob: 'test.svg', input: 'src', output: '.' }, - { glob: 'another.file', input: 'src', output: '.' }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').content.toBe('asset file'); - }); - - it('copies multiple assets with a single entry glob pattern', async () => { - await harness.writeFile('src/test.svg', ''); - await harness.writeFile('src/another.file', 'asset file'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: '{test.svg,another.file}', input: 'src', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').content.toBe('asset file'); - }); - - it('copies multiple assets with a wildcard glob pattern', async () => { - await harness.writeFile('src/files/test.svg', ''); - await harness.writeFile('src/files/another.file', 'asset file'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: '*', input: 'src/files', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').content.toBe('asset file'); - }); - - it('copies multiple assets with a recursive wildcard glob pattern', async () => { - await harness.writeFiles({ - 'src/files/test.svg': '', - 'src/files/another.file': 'asset file', - 'src/files/nested/extra.file': 'extra file', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: '**/*', input: 'src/files', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').content.toBe('asset file'); - harness.expectFile('dist/nested/extra.file').content.toBe('extra file'); - }); - - it('automatically ignores "." prefixed files when using wildcard glob pattern', async () => { - await harness.writeFile('src/files/.gitkeep', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: '*', input: 'src/files', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/.gitkeep').toNotExist(); - }); - - it('supports ignoring a specific file when using a glob pattern', async () => { - await harness.writeFiles({ - 'src/files/test.svg': '', - 'src/files/another.file': 'asset file', - 'src/files/nested/extra.file': 'extra file', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [ - { glob: '**/*', input: 'src/files', output: '.', ignore: ['another.file'] }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').toNotExist(); - harness.expectFile('dist/nested/extra.file').content.toBe('extra file'); - }); - - it('supports ignoring with a glob pattern when using a glob pattern', async () => { - await harness.writeFiles({ - 'src/files/test.svg': '', - 'src/files/another.file': 'asset file', - 'src/files/nested/extra.file': 'extra file', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [ - { glob: '**/*', input: 'src/files', output: '.', ignore: ['**/*.file'] }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - harness.expectFile('dist/another.file').toNotExist(); - harness.expectFile('dist/nested/extra.file').toNotExist(); - }); - - it('copies an asset with directory and maintains directory in output', async () => { - await harness.writeFile('src/subdirectory/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'subdirectory/test.svg', input: 'src', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/subdirectory/test.svg').content.toBe(''); - }); - - it('does not fail if asset does not exist', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').toNotExist(); - }); - - it('uses project output path when output option is empty string', async () => { - await harness.writeFile('src/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: '' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - }); - - it('uses project output path when output option is "."', async () => { - await harness.writeFile('src/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: '.' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - }); - - it('uses project output path when output option is "/"', async () => { - await harness.writeFile('src/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: '/' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').content.toBe(''); - }); - - it('creates a project output sub-path when output option path does not exist', async () => { - await harness.writeFile('src/test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: 'subdirectory' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/subdirectory/test.svg').content.toBe(''); - }); - - it('throws exception if output option is not within project output path', async () => { - await harness.writeFile('test.svg', ''); - - harness.useTarget('build', { - ...BASE_OPTIONS, - assets: [{ glob: 'test.svg', input: 'src', output: '..' }], - }); - - const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); - - expect(result).toBeUndefined(); - expect(error).toEqual( - jasmine.objectContaining({ - message: jasmine.stringMatching( - 'An asset cannot be written to a location outside of the output path', - ), - }), - ); - - harness.expectFile('dist/test.svg').toNotExist(); - }); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/extract-licenses_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/extract-licenses_spec.ts deleted file mode 100644 index 50b29d23ee1b..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/extract-licenses_spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "extractLicenses"', () => { - it(`should generate '3rdpartylicenses.txt' when 'extractLicenses' is true`, async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - extractLicenses: true, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - harness.expectFile('dist/3rdpartylicenses.txt').content.toContain('MIT'); - }); - - it(`should not generate '3rdpartylicenses.txt' when 'extractLicenses' is false`, async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - extractLicenses: false, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - harness.expectFile('dist/3rdpartylicenses.txt').toNotExist(); - }); - - it(`should not generate '3rdpartylicenses.txt' when 'extractLicenses' is not set`, async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - harness.expectFile('dist/3rdpartylicenses.txt').toNotExist(); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/inline-style-language_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/inline-style-language_spec.ts deleted file mode 100644 index 8bcdd8f0ce25..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/inline-style-language_spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { buildWebpackBrowser } from '../../index'; -import { InlineStyleLanguage } from '../../schema'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "inlineStyleLanguage"', () => { - beforeEach(async () => { - // Setup application component with inline style property - await harness.modifyFile('src/app/app.component.ts', (content) => { - return content - .replace('styleUrls', 'styles') - .replace('./app.component.css', '__STYLE_MARKER__'); - }); - }); - - for (const aot of [true, false]) { - describe(`[${aot ? 'AOT' : 'JIT'}]`, () => { - it('supports SCSS inline component styles when set to "scss"', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - inlineStyleLanguage: InlineStyleLanguage.Scss, - aot, - }); - - await harness.modifyFile('src/app/app.component.ts', (content) => - content.replace( - '__STYLE_MARKER__', - '$primary-color: green;\\nh1 { color: $primary-color; }', - ), - ); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - harness.expectFile('dist/main.js').content.toContain('color: green'); - }); - - it('supports Sass inline component styles when set to "sass"', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - inlineStyleLanguage: InlineStyleLanguage.Sass, - aot, - }); - - await harness.modifyFile('src/app/app.component.ts', (content) => - content.replace( - '__STYLE_MARKER__', - '$primary-color: green\\nh1\\n\\tcolor: $primary-color', - ), - ); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - harness.expectFile('dist/main.js').content.toContain('color: green'); - }); - - // Stylus currently does not function due to the sourcemap logic within the `stylus-loader` - // which tries to read each stylesheet directly from disk. In this case, each stylesheet is - // virtual and cannot be read from disk. This issue affects data URIs in general. - // xit('supports Stylus inline component styles when set to "stylus"', async () => { - // harness.useTarget('build', { - // ...BASE_OPTIONS, - // inlineStyleLanguage: InlineStyleLanguage.Stylus, - // aot, - // }); - - // await harness.modifyFile('src/app/app.component.ts', (content) => - // content.replace( - // '__STYLE_MARKER__', - // '$primary-color = green;\\nh1 { color: $primary-color; }', - // ), - // ); - - // const { result } = await harness.executeOnce(); - - // expect(result?.success).toBe(true); - // harness.expectFile('dist/main.js').content.toContain('color: green'); - // }); - - it('supports Less inline component styles when set to "less"', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - inlineStyleLanguage: InlineStyleLanguage.Less, - aot, - }); - - await harness.modifyFile('src/app/app.component.ts', (content) => - content.replace( - '__STYLE_MARKER__', - '@primary-color: green;\\nh1 { color: @primary-color; }', - ), - ); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - harness.expectFile('dist/main.js').content.toContain('color: green'); - }); - }); - } - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/output-hashing_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/output-hashing_spec.ts deleted file mode 100644 index 6f046fcede67..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/output-hashing_spec.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - - -import { buildWebpackBrowser } from '../../index'; -import { OutputHashing } from '../../schema'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "outputHashing"', () => { - beforeEach(async () => { - // Application code is not needed for asset tests - await harness.writeFile('src/main.ts', ''); - }); - - it('hashes all filenames when set to "all"', async () => { - await harness.writeFile( - 'src/styles.css', - `h1 { background: url('https://github.com/spectrum.png')}`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/styles.css'], - outputHashing: OutputHashing.All, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeTrue(); - }); - - it(`doesn't hash any filenames when not set`, async () => { - await harness.writeFile( - 'src/styles.css', - `h1 { background: url('https://github.com/spectrum.png')}`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/styles.css'], - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeFalse(); - }); - - it(`doesn't hash any filenames when set to "none"`, async () => { - await harness.writeFile( - 'src/styles.css', - `h1 { background: url('https://github.com/spectrum.png')}`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/styles.css'], - outputHashing: OutputHashing.None, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeFalse(); - }); - - it(`hashes CSS resources filenames only when set to "media"`, async () => { - await harness.writeFile( - 'src/styles.css', - `h1 { background: url('https://github.com/spectrum.png')}`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/styles.css'], - outputHashing: OutputHashing.Media, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeTrue(); - }); - - it(`hashes bundles filenames only when set to "bundles"`, async () => { - await harness.writeFile( - 'src/styles.css', - `h1 { background: url('https://github.com/spectrum.png')}`, - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/styles.css'], - outputHashing: OutputHashing.Bundles, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{20}\.js$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{20}\.js$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{20}\.js$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.css$/)).toBeTrue(); - expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{20}\.png$/)).toBeFalse(); - }); - - it('does not hash non injected styles', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - outputHashing: OutputHashing.All, - styles: [{ - input: 'src/styles.css', - inject: false, - }], - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.js$/)).toBeFalse(); - expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{20}\.js.map$/)).toBeFalse(); - harness.expectFile('dist/styles.css').toExist(); - harness.expectFile('dist/styles.css.map').toExist(); - }); - - it('does not override different files which has the same filenames when hashing is "none"', async () => { - await harness.writeFiles({ - 'src/styles.css': ` - h1 { background: url('https://github.com/test.svg')} - h2 { background: url('https://github.com/small/test.svg')} - `, - './src/test.svg': ` - Hello World - `, - './src/small/test.svg': ` - Hello World - `, - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/styles.css'], - outputHashing: OutputHashing.None, - }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBe(true); - - harness.expectFile('dist/test.svg').toExist(); - harness.expectFile('dist/small-test.svg').toExist(); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/show-circular-dependencies_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/show-circular-dependencies_spec.ts deleted file mode 100644 index 33dfd04b80ea..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/show-circular-dependencies_spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { logging } from '@angular-devkit/core'; -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "showCircularDependencies"', () => { - beforeEach(async () => { - // Add circular dependency - await harness.appendToFile( - 'src/app/app.component.ts', - `import { AppModule } from './app.module'; console.log(AppModule);`, - ); - }); - - it('should show cyclic dependency warning when option is set to true', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - showCircularDependencies: true, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/Circular dependency detected/), - }), - ); - }); - - it('should not show cyclic dependency warning when option is set to false', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - showCircularDependencies: false, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/Circular dependency detected/), - }), - ); - }); - - it('should not show cyclic dependency warning when option is not present', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching(/Circular dependency detected/), - }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/stats-json_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/stats-json_spec.ts deleted file mode 100644 index f458a2f04d0a..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/stats-json_spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "statsJson"', () => { - beforeEach(async () => { - // Application code is not needed for stat JSON tests - await harness.writeFile('src/main.ts', ''); - }); - - it('generates a Webpack Stats file in output when true', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - statsJson: true, - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - if (harness.expectFile('dist/stats.json').toExist()) { - const content = harness.readFile('dist/stats.json'); - expect(() => JSON.parse(content)) - .withContext('Expected Webpack Stats file to be valid JSON.') - .not.toThrow(); - } - }); - - it('includes Webpack profiling information', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - statsJson: true, - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - if (harness.expectFile('dist/stats.json').toExist()) { - const stats = JSON.parse(harness.readFile('dist/stats.json')); - expect(stats?.chunks?.[0]?.modules?.[0]?.profile?.building).toBeDefined(); - } - }); - - it('does not generate a Webpack Stats file in output when false', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - statsJson: false, - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/stats.json').toNotExist(); - }); - - it('does not generate a Webpack Stats file in output when not present', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/stats.json').toNotExist(); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/styles_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/styles_spec.ts deleted file mode 100644 index 7ee1a9005032..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/styles_spec.ts +++ /dev/null @@ -1,432 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "styles"', () => { - beforeEach(async () => { - // Application code is not needed for styles tests - await harness.writeFile('src/main.ts', ''); - }); - - it('supports an empty array value', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').toNotExist(); - }); - - it('does not create an output styles file when option is not present', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').toNotExist(); - }); - - describe('shorthand syntax', () => { - it('processes a single style into a single output', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/test-style-a.css'], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('processes multiple styles into a single output', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/test-style-a.css', 'src/test-style-b.css'], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); - harness.expectFile('dist/styles.css').content.toContain('.test-b {color: green}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('preserves order of multiple styles in single output', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - 'src/test-style-c.css': '.test-c {color: blue}', - 'src/test-style-d.css': '.test-d {color: yellow}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [ - 'src/test-style-c.css', - 'src/test-style-d.css', - 'src/test-style-b.css', - 'src/test-style-a.css', - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness - .expectFile('dist/styles.css') - .content.toMatch( - /\.test-c {color: blue}\s+\.test-d {color: yellow}\s+\.test-b {color: green}\s+\.test-a {color: red}/, - ); - }); - - it('fails and shows an error if style does not exist', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/test-style-a.css'], - }); - - const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); - - expect(result?.success).toBeFalse(); - expect(logs).toContain( - jasmine.objectContaining({ message: jasmine.stringMatching('Module not found:') }), - ); - - harness.expectFile('dist/styles.css').toNotExist(); - }); - - it('shows the output style as a chunk entry in the logging output', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: ['src/test-style-a.css'], - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - expect(logs).toContain( - jasmine.objectContaining({ message: jasmine.stringMatching(/styles\.css.+\d+ bytes/) }), - ); - }); - }); - - describe('longhand syntax', () => { - it('processes a single style into a single output', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('processes a single style into a single output named with bundleName', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css', bundleName: 'extra' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('uses default bundleName when bundleName is empty string', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css', bundleName: '' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('processes multiple styles with no bundleName into a single output', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css' }, { input: 'src/test-style-b.css' }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); - harness.expectFile('dist/styles.css').content.toContain('.test-b {color: green}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('processes multiple styles with same bundleName into a single output', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [ - { input: 'src/test-style-a.css', bundleName: 'extra' }, - { input: 'src/test-style-b.css', bundleName: 'extra' }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); - harness.expectFile('dist/extra.css').content.toContain('.test-b {color: green}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('processes multiple styles with different bundleNames into separate outputs', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [ - { input: 'src/test-style-a.css', bundleName: 'extra' }, - { input: 'src/test-style-b.css', bundleName: 'other' }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); - harness.expectFile('dist/other.css').content.toContain('.test-b {color: green}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('preserves order of multiple styles in single output', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - 'src/test-style-c.css': '.test-c {color: blue}', - 'src/test-style-d.css': '.test-d {color: yellow}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [ - { input: 'src/test-style-c.css' }, - { input: 'src/test-style-d.css' }, - { input: 'src/test-style-b.css' }, - { input: 'src/test-style-a.css' }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness - .expectFile('dist/styles.css') - .content.toMatch( - /\.test-c {color: blue}\s+\.test-d {color: yellow}\s+\.test-b {color: green}\s+\.test-a {color: red}/, - ); - }); - - it('preserves order of multiple styles with different bundleNames', async () => { - await harness.writeFiles({ - 'src/test-style-a.css': '.test-a {color: red}', - 'src/test-style-b.css': '.test-b {color: green}', - 'src/test-style-c.css': '.test-c {color: blue}', - 'src/test-style-d.css': '.test-d {color: yellow}', - }); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [ - { input: 'src/test-style-c.css', bundleName: 'other' }, - { input: 'src/test-style-d.css', bundleName: 'extra' }, - { input: 'src/test-style-b.css', bundleName: 'extra' }, - { input: 'src/test-style-a.css', bundleName: 'other' }, - ], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness - .expectFile('dist/other.css') - .content.toMatch(/\.test-c {color: blue}\s+\.test-a {color: red}/); - harness - .expectFile('dist/extra.css') - .content.toMatch(/\.test-d {color: yellow}\s+\.test-b {color: green}/); - harness - .expectFile('dist/index.html') - .content.toMatch( - /\s*/, - ); - }); - - it('adds link element to index when inject is true', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css', inject: true }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.toContain(''); - }); - - it('does not add link element to index when inject is false', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css', inject: false }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - // `inject: false` causes the bundleName to be the input file name - harness.expectFile('dist/test-style-a.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.not.toContain(''); - }); - - it('does not add link element to index with bundleName when inject is false', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css', bundleName: 'extra', inject: false }], - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); - harness - .expectFile('dist/index.html') - .content.not.toContain(''); - }); - - it('shows the output style as a chunk entry in the logging output', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css' }], - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - expect(logs).toContain( - jasmine.objectContaining({ message: jasmine.stringMatching(/styles\.css.+\d+ bytes/) }), - ); - }); - - it('shows the output style as a chunk entry with bundleName in the logging output', async () => { - await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); - - harness.useTarget('build', { - ...BASE_OPTIONS, - styles: [{ input: 'src/test-style-a.css', bundleName: 'extra' }], - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - expect(logs).toContain( - jasmine.objectContaining({ message: jasmine.stringMatching(/extra\.css.+\d+ bytes/) }), - ); - }); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/tsconfig_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/tsconfig_spec.ts deleted file mode 100644 index f74bd5784e4e..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/tsconfig_spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "tsConfig"', () => { - it('uses a provided TypeScript configuration file', async () => { - // Setup a TS file that uses ES2015+ const and then target ES5. - // The const usage should be downleveled in the output if the TS config is used. - await harness.writeFile('src/main.ts', 'const a = 5; console.log(a);'); - await harness.writeFile( - 'src/tsconfig.option.json', - JSON.stringify({ - compilerOptions: { - target: 'es5', - types: [], - }, - files: ['main.ts'], - }), - ); - - harness.useTarget('build', { - ...BASE_OPTIONS, - tsConfig: 'src/tsconfig.option.json', - }); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - - harness.expectFile('dist/main.js').content.not.toContain('const'); - }); - - it('throws an exception when TypeScript Configuration file does not exist', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - tsConfig: 'src/missing.json', - }); - - const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); - - expect(result).toBeUndefined(); - expect(error).toEqual( - jasmine.objectContaining({ - message: jasmine.stringMatching('no such file or directory'), - }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/watch_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/options/watch_spec.ts deleted file mode 100644 index c57834ed03e7..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/watch_spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { concatMap, count, take, timeout } from 'rxjs/operators'; -import { buildWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; - -describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { - describe('Option: "watch"', () => { - beforeEach(async () => { - // Application code is not needed for these tests - await harness.writeFile('src/main.ts', ''); - }); - - it('does not wait for file changes when false', (done) => { - harness.useTarget('build', { - ...BASE_OPTIONS, - watch: false, - }); - - // If the build waits then it will timeout with the custom timeout. - // A single build should not take more than 15 seconds. - let count = 0; - harness - .execute() - .pipe(timeout(15000)) - .subscribe({ - complete() { - expect(count).toBe(1); - done(); - }, - next({ result }) { - count++; - expect(result?.success).toBe(true); - }, - error(error) { - done.fail(error); - }, - }); - }); - - it('does not wait for file changes when not present', (done) => { - harness.useTarget('build', { - ...BASE_OPTIONS, - }); - - // If the build waits then it will timeout with the custom timeout. - // A single build should not take more than 15 seconds. - let count = 0; - harness - .execute() - .pipe(timeout(15000)) - .subscribe({ - complete() { - expect(count).toBe(1); - done(); - }, - next({ result }) { - count++; - expect(result?.success).toBe(true); - }, - error(error) { - done.fail(error); - }, - }); - }); - - it('watches for file changes when true', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - main: 'src/main.ts', - watch: true, - }); - - const buildCount = await harness - .execute() - .pipe( - timeout(30000), - concatMap(async ({ result }, index) => { - expect(result?.success).toBe(true); - - switch (index) { - case 0: - harness.expectFile('dist/main.js').content.not.toContain('abcd1234'); - - await harness.modifyFile( - 'src/main.ts', - (content) => content + 'console.log("abcd1234");', - ); - break; - case 1: - harness.expectFile('dist/main.js').content.toContain('abcd1234'); - break; - } - }), - take(2), - count(), - ) - .toPromise(); - - expect(buildCount).toBe(2); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/setup.ts b/packages/angular_devkit/build_angular/src/browser/tests/setup.ts deleted file mode 100644 index 6c44764e064f..000000000000 --- a/packages/angular_devkit/build_angular/src/browser/tests/setup.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Schema } from '../schema'; - -export { describeBuilder } from '../../testing'; - -export const BROWSER_BUILDER_INFO = Object.freeze({ - name: '@angular-devkit/build-angular:browser', - schemaPath: __dirname + '/../schema.json', -}); - -/** - * Contains all required browser builder fields. - * Also disables progress reporting to minimize logging output. - */ -export const BASE_OPTIONS = Object.freeze({ - index: 'src/index.html', - main: 'src/main.ts', - outputPath: 'dist', - tsConfig: 'src/tsconfig.app.json', - progress: false, -}); diff --git a/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts b/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts new file mode 100644 index 000000000000..d47e786630ac --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts @@ -0,0 +1,174 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { normalize, virtualFs } from '@angular-devkit/core'; +import { createArchitect, host } from '../../testing/test-utils'; + +describe('AppShell Builder', () => { + const target = { project: 'app', target: 'app-shell' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + const appShellRouteFiles = { + 'src/styles.css': ` + p { color: #000 } + `, + 'src/app/app-shell/app-shell.component.html': ` +

+ app-shell works! +

+ `, + 'src/app/app-shell/app-shell.component.ts': ` + import { Component, OnInit } from '@angular/core'; + + @Component({ + selector: 'app-app-shell', + templateUrl: './app-shell.component.html', + }) + export class AppShellComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + + } + `, + 'src/app/app.module.ts': ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + + import { AppRoutingModule } from './app-routing.module'; + import { AppComponent } from './app.component'; + import { RouterModule } from '@angular/router'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule.withServerTransition({ appId: 'serverApp' }), + AppRoutingModule, + RouterModule + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + 'src/app/app.server.module.ts': ` + import { NgModule } from '@angular/core'; + import { ServerModule } from '@angular/platform-server'; + + import { AppModule } from './app.module'; + import { AppComponent } from './app.component'; + import { Routes, RouterModule } from '@angular/router'; + import { AppShellComponent } from './app-shell/app-shell.component'; + + const routes: Routes = [ { path: 'shell', component: AppShellComponent }]; + + @NgModule({ + imports: [ + AppModule, + ServerModule, + RouterModule.forRoot(routes), + ], + bootstrap: [AppComponent], + declarations: [AppShellComponent], + }) + export class AppServerModule {} + `, + 'src/main.ts': ` + import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + import { AppModule } from './app/app.module'; + + document.addEventListener('DOMContentLoaded', () => { + platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.log(err)); + }); + `, + 'src/app/app-routing.module.ts': ` + import { NgModule } from '@angular/core'; + import { Routes, RouterModule } from '@angular/router'; + + const routes: Routes = []; + + @NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] + }) + export class AppRoutingModule { } + `, + 'src/app/app.component.html': ` + + `, + }; + + it('works (basic)', async () => { + host.replaceInFile( + 'src/app/app.module.ts', + / {4}BrowserModule/, + ` + BrowserModule.withServerTransition({ appId: 'some-app' }) + `, + ); + + const run = await architect.scheduleTarget(target); + const output = await run.result; + await run.stop(); + + expect(output.success).toBe(true); + + const fileName = 'dist/index.html'; + const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); + expect(content).toMatch('Welcome to app'); + expect(content).toMatch('ng-server-context="app-shell"'); + }); + + it('works with route', async () => { + host.writeMultipleFiles(appShellRouteFiles); + const overrides = { route: 'shell' }; + + const run = await architect.scheduleTarget(target, overrides); + const output = await run.result; + await run.stop(); + + expect(output.success).toBe(true); + const fileName = 'dist/index.html'; + const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); + expect(content).toContain('app-shell works!'); + }); + + it('critical CSS is inlined', async () => { + host.writeMultipleFiles(appShellRouteFiles); + const overrides = { + route: 'shell', + browserTarget: 'app:build:production,inline-critical-css', + }; + + const run = await architect.scheduleTarget(target, overrides); + const output = await run.result; + await run.stop(); + + expect(output.success).toBe(true); + const fileName = 'dist/index.html'; + const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); + + expect(content).toContain('app-shell works!'); + expect(content).toContain('p{color:#000}'); + expect(content).toMatch( + //, + ); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/app-shell/index.ts b/packages/angular_devkit/build_angular/src/builders/app-shell/index.ts new file mode 100644 index 000000000000..208c3d5c611d --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/app-shell/index.ts @@ -0,0 +1,209 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { + BuilderContext, + BuilderOutput, + createBuilder, + targetFromTargetString, +} from '@angular-devkit/architect'; +import { JsonObject } from '@angular-devkit/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import Piscina from 'piscina'; +import { normalizeOptimization } from '../../utils'; +import { assertIsError } from '../../utils/error'; +import { InlineCriticalCssProcessor } from '../../utils/index-file/inline-critical-css'; +import { augmentAppWithServiceWorker } from '../../utils/service-worker'; +import { Spinner } from '../../utils/spinner'; +import { BrowserBuilderOutput } from '../browser'; +import { Schema as BrowserBuilderSchema } from '../browser/schema'; +import { ServerBuilderOutput } from '../server'; +import { Schema as BuildWebpackAppShellSchema } from './schema'; + +async function _renderUniversal( + options: BuildWebpackAppShellSchema, + context: BuilderContext, + browserResult: BrowserBuilderOutput, + serverResult: ServerBuilderOutput, + spinner: Spinner, +): Promise { + // Get browser target options. + const browserTarget = targetFromTargetString(options.browserTarget); + const rawBrowserOptions = (await context.getTargetOptions(browserTarget)) as JsonObject & + BrowserBuilderSchema; + const browserBuilderName = await context.getBuilderNameForTarget(browserTarget); + const browserOptions = await context.validateOptions( + rawBrowserOptions, + browserBuilderName, + ); + + // Locate zone.js to load in the render worker + const root = context.workspaceRoot; + const zonePackage = require.resolve('zone.js', { paths: [root] }); + + const projectName = context.target && context.target.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + + const projectMetadata = await context.getProjectMetadata(projectName); + const projectRoot = path.join(root, (projectMetadata.root as string | undefined) ?? ''); + + const { styles } = normalizeOptimization(browserOptions.optimization); + const inlineCriticalCssProcessor = styles.inlineCritical + ? new InlineCriticalCssProcessor({ + minify: styles.minify, + deployUrl: browserOptions.deployUrl, + }) + : undefined; + + const renderWorker = new Piscina({ + filename: require.resolve('./render-worker'), + maxThreads: 1, + workerData: { zonePackage }, + }); + + try { + for (const { path: outputPath, baseHref } of browserResult.outputs) { + const localeDirectory = path.relative(browserResult.baseOutputPath, outputPath); + const browserIndexOutputPath = path.join(outputPath, 'index.html'); + const indexHtml = await fs.promises.readFile(browserIndexOutputPath, 'utf8'); + const serverBundlePath = await _getServerModuleBundlePath( + options, + context, + serverResult, + localeDirectory, + ); + + let html: string = await renderWorker.run({ + serverBundlePath, + document: indexHtml, + url: options.route, + }); + + // Overwrite the client index file. + const outputIndexPath = options.outputIndexPath + ? path.join(root, options.outputIndexPath) + : browserIndexOutputPath; + + if (inlineCriticalCssProcessor) { + const { content, warnings, errors } = await inlineCriticalCssProcessor.process(html, { + outputPath, + }); + html = content; + + if (warnings.length || errors.length) { + spinner.stop(); + warnings.forEach((m) => context.logger.warn(m)); + errors.forEach((m) => context.logger.error(m)); + spinner.start(); + } + } + + await fs.promises.writeFile(outputIndexPath, html); + + if (browserOptions.serviceWorker) { + await augmentAppWithServiceWorker( + projectRoot, + root, + outputPath, + baseHref ?? '/', + browserOptions.ngswConfigPath, + ); + } + } + } finally { + await renderWorker.destroy(); + } + + return browserResult; +} + +async function _getServerModuleBundlePath( + options: BuildWebpackAppShellSchema, + context: BuilderContext, + serverResult: ServerBuilderOutput, + browserLocaleDirectory: string, +) { + if (options.appModuleBundle) { + return path.join(context.workspaceRoot, options.appModuleBundle); + } + + const { baseOutputPath = '' } = serverResult; + const outputPath = path.join(baseOutputPath, browserLocaleDirectory); + + if (!fs.existsSync(outputPath)) { + throw new Error(`Could not find server output directory: ${outputPath}.`); + } + + const re = /^main\.(?:[a-zA-Z0-9]{16}\.)?js$/; + const maybeMain = fs.readdirSync(outputPath).find((x) => re.test(x)); + + if (!maybeMain) { + throw new Error('Could not find the main bundle.'); + } + + return path.join(outputPath, maybeMain); +} + +async function _appShellBuilder( + options: BuildWebpackAppShellSchema, + context: BuilderContext, +): Promise { + const browserTarget = targetFromTargetString(options.browserTarget); + const serverTarget = targetFromTargetString(options.serverTarget); + + // Never run the browser target in watch mode. + // If service worker is needed, it will be added in _renderUniversal(); + const browserOptions = (await context.getTargetOptions(browserTarget)) as JsonObject & + BrowserBuilderSchema; + + const optimization = normalizeOptimization(browserOptions.optimization); + optimization.styles.inlineCritical = false; + + const browserTargetRun = await context.scheduleTarget(browserTarget, { + watch: false, + serviceWorker: false, + optimization: optimization as unknown as JsonObject, + }); + const serverTargetRun = await context.scheduleTarget(serverTarget, { + watch: false, + }); + + let spinner: Spinner | undefined; + + try { + const [browserResult, serverResult] = await Promise.all([ + browserTargetRun.result as Promise, + serverTargetRun.result as Promise, + ]); + + if (browserResult.success === false || browserResult.baseOutputPath === undefined) { + return browserResult; + } else if (serverResult.success === false) { + return serverResult; + } + + spinner = new Spinner(); + spinner.start('Generating application shell...'); + const result = await _renderUniversal(options, context, browserResult, serverResult, spinner); + spinner.succeed('Application shell generation complete.'); + + return result; + } catch (err) { + spinner?.fail('Application shell generation failed.'); + assertIsError(err); + + return { success: false, error: err.message }; + } finally { + await Promise.all([browserTargetRun.stop(), serverTargetRun.stop()]); + } +} + +export default createBuilder(_appShellBuilder); diff --git a/packages/angular_devkit/build_angular/src/builders/app-shell/render-worker.ts b/packages/angular_devkit/build_angular/src/builders/app-shell/render-worker.ts new file mode 100644 index 000000000000..28af48b5849c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/app-shell/render-worker.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { Type } from '@angular/core'; +import type * as platformServer from '@angular/platform-server'; +import assert from 'node:assert'; +import { workerData } from 'node:worker_threads'; + +/** + * The fully resolved path to the zone.js package that will be loaded during worker initialization. + * This is passed as workerData when setting up the worker via the `piscina` package. + */ +const { zonePackage } = workerData as { + zonePackage: string; +}; + +/** + * A request to render a Server bundle generate by the universal server builder. + */ +interface RenderRequest { + /** + * The path to the server bundle that should be loaded and rendered. + */ + serverBundlePath: string; + /** + * The existing HTML document as a string that will be augmented with the rendered application. + */ + document: string; + /** + * An optional URL path that represents the Angular route that should be rendered. + */ + url: string | undefined; +} + +/** + * Renders an application based on a provided server bundle path, initial document, and optional URL route. + * @param param0 A request to render a server bundle. + * @returns A promise that resolves to the render HTML document for the application. + */ +async function render({ serverBundlePath, document, url }: RenderRequest): Promise { + const { AppServerModule, renderModule, ɵSERVER_CONTEXT } = (await import(serverBundlePath)) as { + renderModule: typeof platformServer.renderModule | undefined; + ɵSERVER_CONTEXT: typeof platformServer.ɵSERVER_CONTEXT | undefined; + AppServerModule: Type | undefined; + }; + + assert(renderModule, `renderModule was not exported from: ${serverBundlePath}.`); + assert(AppServerModule, `AppServerModule was not exported from: ${serverBundlePath}.`); + assert(ɵSERVER_CONTEXT, `ɵSERVER_CONTEXT was not exported from: ${serverBundlePath}.`); + + // Render platform server module + const html = await renderModule(AppServerModule, { + document, + url, + extraProviders: [ + { + provide: ɵSERVER_CONTEXT, + useValue: 'app-shell', + }, + ], + }); + + return html; +} + +/** + * Initializes the worker when it is first created by loading the Zone.js package + * into the worker instance. + * + * @returns A promise resolving to the render function of the worker. + */ +async function initialize() { + // Setup Zone.js + await import(zonePackage); + + // Return the render function for use + return render; +} + +/** + * The default export will be the promise returned by the initialize function. + * This is awaited by piscina prior to using the Worker. + */ +export default initialize(); diff --git a/packages/angular_devkit/build_angular/src/builders/app-shell/schema.json b/packages/angular_devkit/build_angular/src/builders/app-shell/schema.json new file mode 100644 index 000000000000..027cc9f8acf9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/app-shell/schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "App Shell Target", + "description": "App Shell target options for Build Facade.", + "type": "object", + "properties": { + "browserTarget": { + "type": "string", + "description": "A browser builder target use for rendering the application shell in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "serverTarget": { + "type": "string", + "description": "A server builder target use for rendering the application shell in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "appModuleBundle": { + "type": "string", + "description": "Script that exports the Server AppModule to render. This should be the main JavaScript outputted by the server target. By default we will resolve the outputPath of the serverTarget and find a bundle named 'main' in it (whether or not there's a hash tag)." + }, + "route": { + "type": "string", + "description": "The route to render.", + "default": "/" + }, + "inputIndexPath": { + "type": "string", + "description": "The input path for the index.html file. By default uses the output index.html of the browser target." + }, + "outputIndexPath": { + "type": "string", + "description": "The output path of the index.html file. By default will overwrite the input file." + } + }, + "additionalProperties": false, + "required": ["browserTarget", "serverTarget"] +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/compiler-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/compiler-plugin.ts new file mode 100644 index 000000000000..5eac59ba54a1 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/compiler-plugin.ts @@ -0,0 +1,709 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { CompilerHost, NgtscProgram } from '@angular/compiler-cli'; +import { transformAsync } from '@babel/core'; +import type { + OnStartResult, + OutputFile, + PartialMessage, + PartialNote, + Plugin, + PluginBuild, +} from 'esbuild'; +import * as assert from 'node:assert'; +import * as fs from 'node:fs/promises'; +import { platform } from 'node:os'; +import * as path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import ts from 'typescript'; +import angularApplicationPreset from '../../babel/presets/application'; +import { requiresLinking } from '../../babel/webpack-loader'; +import { loadEsmModule } from '../../utils/load-esm'; +import { + logCumulativeDurations, + profileAsync, + profileSync, + resetCumulativeDurations, +} from './profiling'; +import { BundleStylesheetOptions, bundleStylesheetFile, bundleStylesheetText } from './stylesheets'; + +interface EmitFileResult { + content?: string; + map?: string; + dependencies: readonly string[]; + hash?: Uint8Array; +} +type FileEmitter = (file: string) => Promise; + +/** + * Converts TypeScript Diagnostic related information into an esbuild compatible note object. + * Related information is a subset of a full TypeScript Diagnostic and also used for diagnostic + * notes associated with the main Diagnostic. + * @param info The TypeScript diagnostic relative information to convert. + * @param host A TypeScript FormatDiagnosticsHost instance to use during conversion. + * @returns An esbuild diagnostic message as a PartialMessage object + */ +function convertTypeScriptDiagnosticInfo( + info: ts.DiagnosticRelatedInformation, + host: ts.FormatDiagnosticsHost, + textPrefix?: string, +): PartialNote { + let text = ts.flattenDiagnosticMessageText(info.messageText, host.getNewLine()); + if (textPrefix) { + text = textPrefix + text; + } + + const note: PartialNote = { text }; + + if (info.file) { + note.location = { + file: info.file.fileName, + length: info.length, + }; + + // Calculate the line/column location and extract the full line text that has the diagnostic + if (info.start) { + const { line, character } = ts.getLineAndCharacterOfPosition(info.file, info.start); + note.location.line = line + 1; + note.location.column = character; + + // The start position for the slice is the first character of the error line + const lineStartPosition = ts.getPositionOfLineAndCharacter(info.file, line, 0); + + // The end position for the slice is the first character of the next line or the length of + // the entire file if the line is the last line of the file (getPositionOfLineAndCharacter + // will error if a nonexistent line is passed). + const { line: lastLineOfFile } = ts.getLineAndCharacterOfPosition( + info.file, + info.file.text.length - 1, + ); + const lineEndPosition = + line < lastLineOfFile + ? ts.getPositionOfLineAndCharacter(info.file, line + 1, 0) + : info.file.text.length; + + note.location.lineText = info.file.text.slice(lineStartPosition, lineEndPosition).trimEnd(); + } + } + + return note; +} + +/** + * Converts a TypeScript Diagnostic message into an esbuild compatible message object. + * @param diagnostic The TypeScript diagnostic to convert. + * @param host A TypeScript FormatDiagnosticsHost instance to use during conversion. + * @returns An esbuild diagnostic message as a PartialMessage object + */ +function convertTypeScriptDiagnostic( + diagnostic: ts.Diagnostic, + host: ts.FormatDiagnosticsHost, +): PartialMessage { + let codePrefix = 'TS'; + let code = `${diagnostic.code}`; + if (diagnostic.source === 'ngtsc') { + codePrefix = 'NG'; + // Remove `-99` Angular prefix from diagnostic code + code = code.slice(3); + } + + const message: PartialMessage = { + ...convertTypeScriptDiagnosticInfo(diagnostic, host, `${codePrefix}${code}: `), + // Store original diagnostic for reference if needed downstream + detail: diagnostic, + }; + + if (diagnostic.relatedInformation?.length) { + message.notes = diagnostic.relatedInformation.map((info) => + convertTypeScriptDiagnosticInfo(info, host), + ); + } + + return message; +} + +const USING_WINDOWS = platform() === 'win32'; +const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g'); + +export class SourceFileCache extends Map { + readonly modifiedFiles = new Set(); + readonly babelFileCache = new Map(); + readonly typeScriptFileCache = new Map(); + + invalidate(files: Iterable): void { + this.modifiedFiles.clear(); + for (let file of files) { + this.babelFileCache.delete(file); + this.typeScriptFileCache.delete(pathToFileURL(file).href); + + // Normalize separators to allow matching TypeScript Host paths + if (USING_WINDOWS) { + file = file.replace(WINDOWS_SEP_REGEXP, path.posix.sep); + } + + this.delete(file); + this.modifiedFiles.add(file); + } + } +} + +export interface CompilerPluginOptions { + sourcemap: boolean; + tsconfig: string; + advancedOptimizations?: boolean; + thirdPartySourcemaps?: boolean; + fileReplacements?: Record; + sourceFileCache?: SourceFileCache; +} + +// This is a non-watch version of the compiler code from `@ngtools/webpack` augmented for esbuild +// eslint-disable-next-line max-lines-per-function +export function createCompilerPlugin( + pluginOptions: CompilerPluginOptions, + styleOptions: BundleStylesheetOptions, +): Plugin { + return { + name: 'angular-compiler', + // eslint-disable-next-line max-lines-per-function + async setup(build: PluginBuild): Promise { + let setupWarnings: PartialMessage[] | undefined; + + // This uses a wrapped dynamic import to load `@angular/compiler-cli` which is ESM. + // Once TypeScript provides support for retaining dynamic imports this workaround can be dropped. + const { GLOBAL_DEFS_FOR_TERSER_WITH_AOT, NgtscProgram, OptimizeFor, readConfiguration } = + await loadEsmModule('@angular/compiler-cli'); + + // Temporary deep import for transformer support + const { + mergeTransformers, + replaceBootstrap, + } = require('@ngtools/webpack/src/ivy/transformation'); + + // Setup defines based on the values provided by the Angular compiler-cli + build.initialOptions.define ??= {}; + for (const [key, value] of Object.entries(GLOBAL_DEFS_FOR_TERSER_WITH_AOT)) { + if (key in build.initialOptions.define) { + // Skip keys that have been manually provided + continue; + } + if (key === 'ngDevMode') { + // ngDevMode is already set based on the builder's script optimization option + continue; + } + // esbuild requires values to be a string (actual strings need to be quoted). + // In this case, all provided values are booleans. + build.initialOptions.define[key] = value.toString(); + } + + // The tsconfig is loaded in setup instead of in start to allow the esbuild target build option to be modified. + // esbuild build options can only be modified in setup prior to starting the build. + const { + options: compilerOptions, + rootNames, + errors: configurationDiagnostics, + } = profileSync('NG_READ_CONFIG', () => + readConfiguration(pluginOptions.tsconfig, { + noEmitOnError: false, + suppressOutputPathCheck: true, + outDir: undefined, + inlineSources: pluginOptions.sourcemap, + inlineSourceMap: pluginOptions.sourcemap, + sourceMap: false, + mapRoot: undefined, + sourceRoot: undefined, + declaration: false, + declarationMap: false, + allowEmptyCodegenFiles: false, + annotationsAs: 'decorators', + enableResourceInlining: false, + }), + ); + + if (compilerOptions.target === undefined || compilerOptions.target < ts.ScriptTarget.ES2022) { + // If 'useDefineForClassFields' is already defined in the users project leave the value as is. + // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995 + // which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well. + compilerOptions.target = ts.ScriptTarget.ES2022; + compilerOptions.useDefineForClassFields ??= false; + + (setupWarnings ??= []).push({ + text: + 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' + + '"false" respectively by the Angular CLI.', + location: { file: pluginOptions.tsconfig }, + notes: [ + { + text: + 'To control ECMA version and features use the Browerslist configuration. ' + + 'For more information, see https://angular.io/guide/build#configuring-browser-compatibility', + }, + ], + }); + } + + // The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files + let fileEmitter: FileEmitter | undefined; + + // The stylesheet resources from component stylesheets that will be added to the build results output files + let stylesheetResourceFiles: OutputFile[]; + + let previousBuilder: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined; + let previousAngularProgram: NgtscProgram | undefined; + const babelDataCache = new Map(); + const diagnosticCache = new WeakMap(); + + build.onStart(async () => { + const result: OnStartResult = { + warnings: setupWarnings, + }; + + // Reset the setup warnings so that they are only shown during the first build. + setupWarnings = undefined; + + // Reset debug performance tracking + resetCumulativeDurations(); + + // Reset stylesheet resource output files + stylesheetResourceFiles = []; + + // Create TypeScript compiler host + const host = ts.createIncrementalCompilerHost(compilerOptions); + + // Temporarily process external resources via readResource. + // The AOT compiler currently requires this hook to allow for a transformResource hook. + // Once the AOT compiler allows only a transformResource hook, this can be reevaluated. + (host as CompilerHost).readResource = async function (fileName) { + // Template resources (.html/.svg) files are not bundled or transformed + if (fileName.endsWith('.html') || fileName.endsWith('.svg')) { + return this.readFile(fileName) ?? ''; + } + + const { contents, resourceFiles, errors, warnings } = await bundleStylesheetFile( + fileName, + styleOptions, + ); + + (result.errors ??= []).push(...errors); + (result.warnings ??= []).push(...warnings); + stylesheetResourceFiles.push(...resourceFiles); + + return contents; + }; + + // Add an AOT compiler resource transform hook + (host as CompilerHost).transformResource = async function (data, context) { + // Only inline style resources are transformed separately currently + if (context.resourceFile || context.type !== 'style') { + return null; + } + + // The file with the resource content will either be an actual file (resourceFile) + // or the file containing the inline component style text (containingFile). + const file = context.resourceFile ?? context.containingFile; + + const { contents, resourceFiles, errors, warnings } = await bundleStylesheetText( + data, + { + resolvePath: path.dirname(file), + virtualName: file, + }, + styleOptions, + ); + + (result.errors ??= []).push(...errors); + (result.warnings ??= []).push(...warnings); + stylesheetResourceFiles.push(...resourceFiles); + + return { content: contents }; + }; + + // Temporary deep import for host augmentation support + const { + augmentHostWithCaching, + augmentHostWithReplacements, + augmentProgramWithVersioning, + } = require('@ngtools/webpack/src/ivy/host'); + + // Augment TypeScript Host for file replacements option + if (pluginOptions.fileReplacements) { + augmentHostWithReplacements(host, pluginOptions.fileReplacements); + } + + // Augment TypeScript Host with source file caching if provided + if (pluginOptions.sourceFileCache) { + augmentHostWithCaching(host, pluginOptions.sourceFileCache); + // Allow the AOT compiler to request the set of changed templates and styles + (host as CompilerHost).getModifiedResourceFiles = function () { + return pluginOptions.sourceFileCache?.modifiedFiles; + }; + } + + // Create the Angular specific program that contains the Angular compiler + const angularProgram = profileSync( + 'NG_CREATE_PROGRAM', + () => new NgtscProgram(rootNames, compilerOptions, host, previousAngularProgram), + ); + previousAngularProgram = angularProgram; + const angularCompiler = angularProgram.compiler; + const typeScriptProgram = angularProgram.getTsProgram(); + augmentProgramWithVersioning(typeScriptProgram); + + const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram( + typeScriptProgram, + host, + previousBuilder, + configurationDiagnostics, + ); + previousBuilder = builder; + + await profileAsync('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync()); + const affectedFiles = profileSync('NG_FIND_AFFECTED', () => + findAffectedFiles(builder, angularCompiler), + ); + + if (pluginOptions.sourceFileCache) { + for (const affected of affectedFiles) { + pluginOptions.sourceFileCache.typeScriptFileCache.delete( + pathToFileURL(affected.fileName).href, + ); + } + } + + function* collectDiagnostics(): Iterable { + // Collect program level diagnostics + yield* builder.getConfigFileParsingDiagnostics(); + yield* angularCompiler.getOptionDiagnostics(); + yield* builder.getOptionsDiagnostics(); + yield* builder.getGlobalDiagnostics(); + + // Collect source file specific diagnostics + const optimizeFor = + affectedFiles.size > 1 ? OptimizeFor.WholeProgram : OptimizeFor.SingleFile; + for (const sourceFile of builder.getSourceFiles()) { + if (angularCompiler.ignoreForDiagnostics.has(sourceFile)) { + continue; + } + + // TypeScript will use cached diagnostics for files that have not been + // changed or affected for this build when using incremental building. + yield* profileSync( + 'NG_DIAGNOSTICS_SYNTACTIC', + () => builder.getSyntacticDiagnostics(sourceFile), + true, + ); + yield* profileSync( + 'NG_DIAGNOSTICS_SEMANTIC', + () => builder.getSemanticDiagnostics(sourceFile), + true, + ); + + // Declaration files cannot have template diagnostics + if (sourceFile.isDeclarationFile) { + continue; + } + + // Only request Angular template diagnostics for affected files to avoid + // overhead of template diagnostics for unchanged files. + if (affectedFiles.has(sourceFile)) { + const angularDiagnostics = profileSync( + 'NG_DIAGNOSTICS_TEMPLATE', + () => angularCompiler.getDiagnosticsForFile(sourceFile, optimizeFor), + true, + ); + diagnosticCache.set(sourceFile, angularDiagnostics); + yield* angularDiagnostics; + } else { + const angularDiagnostics = diagnosticCache.get(sourceFile); + if (angularDiagnostics) { + yield* angularDiagnostics; + } + } + } + } + + profileSync('NG_DIAGNOSTICS_TOTAL', () => { + for (const diagnostic of collectDiagnostics()) { + const message = convertTypeScriptDiagnostic(diagnostic, host); + if (diagnostic.category === ts.DiagnosticCategory.Error) { + (result.errors ??= []).push(message); + } else { + (result.warnings ??= []).push(message); + } + } + }); + + fileEmitter = createFileEmitter( + builder, + mergeTransformers(angularCompiler.prepareEmit().transformers, { + before: [replaceBootstrap(() => builder.getProgram().getTypeChecker())], + }), + (sourceFile) => angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile), + ); + + return result; + }); + + build.onLoad( + { filter: compilerOptions.allowJs ? /\.[cm]?[jt]sx?$/ : /\.[cm]?tsx?$/ }, + (args) => + profileAsync( + 'NG_EMIT_TS*', + async () => { + assert.ok(fileEmitter, 'Invalid plugin execution order'); + + const request = pluginOptions.fileReplacements?.[args.path] ?? args.path; + + // The filename is currently used as a cache key. Since the cache is memory only, + // the options cannot change and do not need to be represented in the key. If the + // cache is later stored to disk, then the options that affect transform output + // would need to be added to the key as well as a check for any change of content. + let contents = pluginOptions.sourceFileCache?.typeScriptFileCache.get( + pathToFileURL(request).href, + ); + + if (contents === undefined) { + const typescriptResult = await fileEmitter(request); + if (!typescriptResult) { + // No TS result indicates the file is not part of the TypeScript program. + // If allowJs is enabled and the file is JS then defer to the next load hook. + if (compilerOptions.allowJs && /\.[cm]?js$/.test(request)) { + return undefined; + } + + // Otherwise return an error + return { + errors: [ + createMissingFileError( + request, + args.path, + build.initialOptions.absWorkingDir ?? '', + ), + ], + }; + } + + const data = typescriptResult.content ?? ''; + // The pre-transformed data is used as a cache key. Since the cache is memory only, + // the options cannot change and do not need to be represented in the key. If the + // cache is later stored to disk, then the options that affect transform output + // would need to be added to the key as well. + contents = babelDataCache.get(data); + if (contents === undefined) { + const transformedData = await transformWithBabel(request, data, pluginOptions); + contents = Buffer.from(transformedData, 'utf-8'); + babelDataCache.set(data, contents); + } + + pluginOptions.sourceFileCache?.typeScriptFileCache.set( + pathToFileURL(request).href, + contents, + ); + } + + return { + contents, + loader: 'js', + }; + }, + true, + ), + ); + + build.onLoad({ filter: /\.[cm]?js$/ }, (args) => + profileAsync( + 'NG_EMIT_JS*', + async () => { + // The filename is currently used as a cache key. Since the cache is memory only, + // the options cannot change and do not need to be represented in the key. If the + // cache is later stored to disk, then the options that affect transform output + // would need to be added to the key as well as a check for any change of content. + let contents = pluginOptions.sourceFileCache?.babelFileCache.get(args.path); + if (contents === undefined) { + const data = await fs.readFile(args.path, 'utf-8'); + const transformedData = await transformWithBabel(args.path, data, pluginOptions); + contents = Buffer.from(transformedData, 'utf-8'); + pluginOptions.sourceFileCache?.babelFileCache.set(args.path, contents); + } + + return { + contents, + loader: 'js', + }; + }, + true, + ), + ); + + build.onEnd((result) => { + if (stylesheetResourceFiles.length) { + result.outputFiles?.push(...stylesheetResourceFiles); + } + + logCumulativeDurations(); + }); + }, + }; +} + +function createFileEmitter( + program: ts.BuilderProgram, + transformers: ts.CustomTransformers = {}, + onAfterEmit?: (sourceFile: ts.SourceFile) => void, +): FileEmitter { + return async (file: string) => { + const sourceFile = program.getSourceFile(file); + if (!sourceFile) { + return undefined; + } + + let content: string | undefined; + program.emit( + sourceFile, + (filename, data) => { + if (/\.[cm]?js$/.test(filename)) { + content = data; + } + }, + undefined /* cancellationToken */, + undefined /* emitOnlyDtsFiles */, + transformers, + ); + + onAfterEmit?.(sourceFile); + + return { content, dependencies: [] }; + }; +} + +async function transformWithBabel( + filename: string, + data: string, + pluginOptions: CompilerPluginOptions, +): Promise { + const forceAsyncTransformation = + !/[\\/][_f]?esm2015[\\/]/.test(filename) && /async\s+function\s*\*/.test(data); + const shouldLink = await requiresLinking(filename, data); + const useInputSourcemap = + pluginOptions.sourcemap && + (!!pluginOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); + + // If no additional transformations are needed, return the data directly + if (!forceAsyncTransformation && !pluginOptions.advancedOptimizations && !shouldLink) { + // Strip sourcemaps if they should not be used + return useInputSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''); + } + + const angularPackage = /[\\/]node_modules[\\/]@angular[\\/]/.test(filename); + + const linkerPluginCreator = shouldLink + ? ( + await loadEsmModule( + '@angular/compiler-cli/linker/babel', + ) + ).createEs2015LinkerPlugin + : undefined; + + const result = await transformAsync(data, { + filename, + inputSourceMap: (useInputSourcemap ? undefined : false) as undefined, + sourceMaps: pluginOptions.sourcemap ? 'inline' : false, + compact: false, + configFile: false, + babelrc: false, + browserslistConfigFile: false, + plugins: [], + presets: [ + [ + angularApplicationPreset, + { + angularLinker: { + shouldLink, + jitMode: false, + linkerPluginCreator, + }, + forceAsyncTransformation, + optimize: pluginOptions.advancedOptimizations && { + looseEnums: angularPackage, + pureTopLevel: angularPackage, + }, + }, + ], + ], + }); + + return result?.code ?? data; +} + +function findAffectedFiles( + builder: ts.EmitAndSemanticDiagnosticsBuilderProgram, + { ignoreForDiagnostics, ignoreForEmit, incrementalCompilation }: NgtscProgram['compiler'], +): Set { + const affectedFiles = new Set(); + + // eslint-disable-next-line no-constant-condition + while (true) { + const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => { + // If the affected file is a TTC shim, add the shim's original source file. + // This ensures that changes that affect TTC are typechecked even when the changes + // are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes. + // For example, changing @Input property types of a directive used in another component's + // template. + // A TTC shim is a file that has been ignored for diagnostics and has a filename ending in `.ngtypecheck.ts`. + if (ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith('.ngtypecheck.ts')) { + // This file name conversion relies on internal compiler logic and should be converted + // to an official method when available. 15 is length of `.ngtypecheck.ts` + const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts'; + const originalSourceFile = builder.getSourceFile(originalFilename); + if (originalSourceFile) { + affectedFiles.add(originalSourceFile); + } + + return true; + } + + return false; + }); + + if (!result) { + break; + } + + affectedFiles.add(result.affected as ts.SourceFile); + } + + // A file is also affected if the Angular compiler requires it to be emitted + for (const sourceFile of builder.getSourceFiles()) { + if (ignoreForEmit.has(sourceFile) || incrementalCompilation.safeToSkipEmit(sourceFile)) { + continue; + } + + affectedFiles.add(sourceFile); + } + + return affectedFiles; +} + +function createMissingFileError(request: string, original: string, root: string): PartialMessage { + const error = { + text: `File '${path.relative(root, request)}' is missing from the TypeScript compilation.`, + notes: [ + { + text: `Ensure the file is part of the TypeScript program via the 'files' or 'include' property.`, + }, + ], + }; + + if (request !== original) { + error.notes.push({ + text: `File is requested from a file replacement of '${path.relative(root, original)}'.`, + }); + } + + return error; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/css-resource-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/css-resource-plugin.ts new file mode 100644 index 000000000000..5cdad4e52e15 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/css-resource-plugin.ts @@ -0,0 +1,71 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { Plugin, PluginBuild } from 'esbuild'; +import { readFile } from 'fs/promises'; + +/** + * Symbol marker used to indicate CSS resource resolution is being attempted. + * This is used to prevent an infinite loop within the plugin's resolve hook. + */ +const CSS_RESOURCE_RESOLUTION = Symbol('CSS_RESOURCE_RESOLUTION'); + +/** + * Creates an esbuild {@link Plugin} that loads all CSS url token references using the + * built-in esbuild `file` loader. A plugin is used to allow for all file extensions + * and types to be supported without needing to manually specify all extensions + * within the build configuration. + * + * @returns An esbuild {@link Plugin} instance. + */ +export function createCssResourcePlugin(): Plugin { + return { + name: 'angular-css-resource', + setup(build: PluginBuild): void { + build.onResolve({ filter: /.*/ }, async (args) => { + // Only attempt to resolve url tokens which only exist inside CSS. + // Also, skip this plugin if already attempting to resolve the url-token. + if (args.kind !== 'url-token' || args.pluginData?.[CSS_RESOURCE_RESOLUTION]) { + return null; + } + + // If root-relative, absolute or protocol relative url, mark as external to leave the + // path/URL in place. + if (/^((?:\w+:)?\/\/|data:|chrome:|#|\/)/.test(args.path)) { + return { + path: args.path, + external: true, + }; + } + + const { importer, kind, resolveDir, namespace, pluginData = {} } = args; + pluginData[CSS_RESOURCE_RESOLUTION] = true; + + const result = await build.resolve(args.path, { + importer, + kind, + namespace, + pluginData, + resolveDir, + }); + + return { + ...result, + namespace: 'css-resource', + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'css-resource' }, async (args) => { + return { + contents: await readFile(args.path), + loader: 'file', + }; + }); + }, + }; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts new file mode 100644 index 000000000000..984c145117c4 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/esbuild.ts @@ -0,0 +1,105 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import { + BuildFailure, + BuildInvalidate, + BuildOptions, + BuildResult, + Message, + OutputFile, + build, + formatMessages, +} from 'esbuild'; +import { basename, extname, relative } from 'node:path'; +import { FileInfo } from '../../utils/index-file/augment-index-html'; + +/** + * Determines if an unknown value is an esbuild BuildFailure error object thrown by esbuild. + * @param value A potential esbuild BuildFailure error object. + * @returns `true` if the object is determined to be a BuildFailure object; otherwise, `false`. + */ +export function isEsBuildFailure(value: unknown): value is BuildFailure { + return !!value && typeof value === 'object' && 'errors' in value && 'warnings' in value; +} + +/** + * Executes the esbuild build function and normalizes the build result in the event of a + * build failure that results in no output being generated. + * All builds use the `write` option with a value of `false` to allow for the output files + * build result array to be populated. + * + * @param optionsOrInvalidate The esbuild options object to use when building or the invalidate object + * returned from an incremental build to perform an additional incremental build. + * @returns If output files are generated, the full esbuild BuildResult; if not, the + * warnings and errors for the attempted build. + */ +export async function bundle( + workspaceRoot: string, + optionsOrInvalidate: BuildOptions | BuildInvalidate, +): Promise< + | (BuildResult & { outputFiles: OutputFile[]; initialFiles: FileInfo[] }) + | (BuildFailure & { outputFiles?: never }) +> { + let result; + try { + if (typeof optionsOrInvalidate === 'function') { + result = (await optionsOrInvalidate()) as BuildResult & { outputFiles: OutputFile[] }; + } else { + result = await build({ + ...optionsOrInvalidate, + metafile: true, + write: false, + }); + } + } catch (failure) { + // Build failures will throw an exception which contains errors/warnings + if (isEsBuildFailure(failure)) { + return failure; + } else { + throw failure; + } + } + + const initialFiles: FileInfo[] = []; + for (const outputFile of result.outputFiles) { + // Entries in the metafile are relative to the `absWorkingDir` option which is set to the workspaceRoot + const relativeFilePath = relative(workspaceRoot, outputFile.path); + const entryPoint = result.metafile?.outputs[relativeFilePath]?.entryPoint; + + outputFile.path = relativeFilePath; + + if (entryPoint) { + // An entryPoint value indicates an initial file + initialFiles.push({ + file: outputFile.path, + // The first part of the filename is the name of file (e.g., "polyfills" for "polyfills.7S5G3MDY.js") + name: basename(outputFile.path).split('.')[0], + extension: extname(outputFile.path), + }); + } + } + + return { ...result, initialFiles }; +} + +export async function logMessages( + context: BuilderContext, + { errors, warnings }: { errors: Message[]; warnings: Message[] }, +): Promise { + if (warnings.length) { + const warningMessages = await formatMessages(warnings, { kind: 'warning', color: true }); + context.logger.warn(warningMessages.join('\n')); + } + + if (errors.length) { + const errorMessages = await formatMessages(errors, { kind: 'error', color: true }); + context.logger.error(errorMessages.join('\n')); + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/experimental-warnings.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/experimental-warnings.ts new file mode 100644 index 000000000000..dac13da1e40e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/experimental-warnings.ts @@ -0,0 +1,71 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import { Schema as BrowserBuilderOptions } from '../browser/schema'; + +const UNSUPPORTED_OPTIONS: Array = [ + 'allowedCommonJsDependencies', + 'budgets', + 'extractLicenses', + 'progress', + 'scripts', + 'statsJson', + + // * i18n support + 'localize', + // The following two have no effect when localize is not enabled + // 'i18nDuplicateTranslation', + // 'i18nMissingTranslation', + + // * Stylesheet preprocessor support + 'inlineStyleLanguage', + // The following option has no effect until preprocessors are supported + // 'stylePreprocessorOptions', + + // * Deprecated + 'deployUrl', + + // * Always enabled with esbuild + // 'commonChunk', + + // * Currently unsupported by esbuild + 'namedChunks', + 'vendorChunk', + 'webWorkerTsConfig', +]; + +export function logExperimentalWarnings(options: BrowserBuilderOptions, context: BuilderContext) { + // Warn about experimental status of this builder + context.logger.warn( + `The esbuild browser application builder ('browser-esbuild') is currently experimental.`, + ); + + // Validate supported options + // Currently only a subset of the Webpack-based browser builder options are supported. + for (const unsupportedOption of UNSUPPORTED_OPTIONS) { + const value = options[unsupportedOption]; + + if (value === undefined || value === false) { + continue; + } + if (Array.isArray(value) && value.length === 0) { + continue; + } + if (typeof value === 'object' && Object.keys(value).length === 0) { + continue; + } + if (unsupportedOption === 'inlineStyleLanguage' && value === 'css') { + continue; + } + + context.logger.warn( + `The '${unsupportedOption}' option is currently unsupported by this experimental builder and will be ignored.`, + ); + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts new file mode 100644 index 000000000000..6d15beaf1d78 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts @@ -0,0 +1,522 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import type { BuildInvalidate, BuildOptions, OutputFile } from 'esbuild'; +import assert from 'node:assert'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { deleteOutputDir } from '../../utils'; +import { copyAssets } from '../../utils/copy-assets'; +import { assertIsError } from '../../utils/error'; +import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets'; +import { FileInfo } from '../../utils/index-file/augment-index-html'; +import { IndexHtmlGenerator } from '../../utils/index-file/index-html-generator'; +import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker'; +import { getSupportedBrowsers } from '../../utils/supported-browsers'; +import { SourceFileCache, createCompilerPlugin } from './compiler-plugin'; +import { bundle, logMessages } from './esbuild'; +import { logExperimentalWarnings } from './experimental-warnings'; +import { NormalizedBrowserOptions, normalizeOptions } from './options'; +import { shutdownSassWorkerPool } from './sass-plugin'; +import { Schema as BrowserBuilderOptions } from './schema'; +import { createStylesheetBundleOptions } from './stylesheets'; +import { ChangedFiles, createWatcher } from './watcher'; + +interface RebuildState { + codeRebuild?: BuildInvalidate; + globalStylesRebuild?: BuildInvalidate; + codeBundleCache?: SourceFileCache; + fileChanges: ChangedFiles; +} + +/** + * Represents the result of a single builder execute call. + */ +class ExecutionResult { + constructor( + private success: boolean, + private codeRebuild?: BuildInvalidate, + private globalStylesRebuild?: BuildInvalidate, + private codeBundleCache?: SourceFileCache, + ) {} + + get output() { + return { + success: this.success, + }; + } + + createRebuildState(fileChanges: ChangedFiles): RebuildState { + this.codeBundleCache?.invalidate([...fileChanges.modified, ...fileChanges.removed]); + + return { + codeRebuild: this.codeRebuild, + globalStylesRebuild: this.globalStylesRebuild, + codeBundleCache: this.codeBundleCache, + fileChanges, + }; + } + + dispose(): void { + this.codeRebuild?.dispose(); + } +} + +async function execute( + options: NormalizedBrowserOptions, + context: BuilderContext, + rebuildState?: RebuildState, +): Promise { + const startTime = process.hrtime.bigint(); + + const { + projectRoot, + workspaceRoot, + optimizationOptions, + outputPath, + assets, + serviceWorkerOptions, + indexHtmlOptions, + } = options; + + const target = transformSupportedBrowsersToTargets( + getSupportedBrowsers(projectRoot, context.logger), + ); + + const codeBundleCache = options.watch + ? rebuildState?.codeBundleCache ?? new SourceFileCache() + : undefined; + + const [codeResults, styleResults] = await Promise.all([ + // Execute esbuild to bundle the application code + bundle( + workspaceRoot, + rebuildState?.codeRebuild ?? createCodeBundleOptions(options, target, codeBundleCache), + ), + // Execute esbuild to bundle the global stylesheets + bundle( + workspaceRoot, + rebuildState?.globalStylesRebuild ?? createGlobalStylesBundleOptions(options, target), + ), + ]); + + // Log all warnings and errors generated during bundling + await logMessages(context, { + errors: [...codeResults.errors, ...styleResults.errors], + warnings: [...codeResults.warnings, ...styleResults.warnings], + }); + + // Return if the bundling failed to generate output files or there are errors + if (!codeResults.outputFiles || codeResults.errors.length) { + return new ExecutionResult( + false, + rebuildState?.codeRebuild, + (styleResults.outputFiles && styleResults.rebuild) ?? rebuildState?.globalStylesRebuild, + codeBundleCache, + ); + } + + // Return if the global stylesheet bundling has errors + if (!styleResults.outputFiles || styleResults.errors.length) { + return new ExecutionResult( + false, + codeResults.rebuild, + rebuildState?.globalStylesRebuild, + codeBundleCache, + ); + } + + // Filter global stylesheet initial files + styleResults.initialFiles = styleResults.initialFiles.filter( + ({ name }) => options.globalStyles.find((style) => style.name === name)?.initial, + ); + + // Combine the bundling output files + const initialFiles: FileInfo[] = [...codeResults.initialFiles, ...styleResults.initialFiles]; + const outputFiles: OutputFile[] = [...codeResults.outputFiles, ...styleResults.outputFiles]; + + // Generate index HTML file + if (indexHtmlOptions) { + // Create an index HTML generator that reads from the in-memory output files + const indexHtmlGenerator = new IndexHtmlGenerator({ + indexPath: indexHtmlOptions.input, + entrypoints: indexHtmlOptions.insertionOrder, + sri: options.subresourceIntegrity, + optimization: optimizationOptions, + crossOrigin: options.crossOrigin, + }); + + /** Virtual output path to support reading in-memory files. */ + const virtualOutputPath = '/'; + indexHtmlGenerator.readAsset = async function (filePath: string): Promise { + // Remove leading directory separator + const relativefilePath = path.relative(virtualOutputPath, filePath); + const file = outputFiles.find((file) => file.path === relativefilePath); + if (file) { + return file.text; + } + + throw new Error(`Output file does not exist: ${path}`); + }; + + const { content, warnings, errors } = await indexHtmlGenerator.process({ + baseHref: options.baseHref, + lang: undefined, + outputPath: virtualOutputPath, + files: initialFiles, + }); + + for (const error of errors) { + context.logger.error(error); + } + for (const warning of warnings) { + context.logger.warn(warning); + } + + outputFiles.push(createOutputFileFromText(indexHtmlOptions.output, content)); + } + + // Copy assets + if (assets) { + await copyAssets(assets, [outputPath], workspaceRoot); + } + + // Write output files + await Promise.all( + outputFiles.map((file) => fs.writeFile(path.join(outputPath, file.path), file.contents)), + ); + + // Augment the application with service worker support + // TODO: This should eventually operate on the in-memory files prior to writing the output files + if (serviceWorkerOptions) { + try { + await augmentAppWithServiceWorkerEsbuild( + workspaceRoot, + serviceWorkerOptions, + outputPath, + options.baseHref || '/', + ); + } catch (error) { + context.logger.error(error instanceof Error ? error.message : `${error}`); + + return new ExecutionResult(false, codeResults.rebuild, styleResults.rebuild, codeBundleCache); + } + } + + const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9; + context.logger.info(`Complete. [${buildTime.toFixed(3)} seconds]`); + + return new ExecutionResult(true, codeResults.rebuild, styleResults.rebuild, codeBundleCache); +} + +function createOutputFileFromText(path: string, text: string): OutputFile { + return { + path, + text, + get contents() { + return Buffer.from(this.text, 'utf-8'); + }, + }; +} + +function createCodeBundleOptions( + options: NormalizedBrowserOptions, + target: string[], + sourceFileCache?: SourceFileCache, +): BuildOptions { + const { + workspaceRoot, + entryPoints, + optimizationOptions, + sourcemapOptions, + tsconfig, + outputNames, + fileReplacements, + externalDependencies, + preserveSymlinks, + stylePreprocessorOptions, + advancedOptimizations, + } = options; + + return { + absWorkingDir: workspaceRoot, + bundle: true, + incremental: options.watch, + format: 'esm', + entryPoints, + entryNames: outputNames.bundles, + assetNames: outputNames.media, + target, + supported: getFeatureSupport(target), + mainFields: ['es2020', 'browser', 'module', 'main'], + conditions: ['es2020', 'es2015', 'module'], + resolveExtensions: ['.ts', '.tsx', '.mjs', '.js'], + logLevel: options.verbose ? 'debug' : 'silent', + minify: optimizationOptions.scripts, + pure: ['forwardRef'], + outdir: workspaceRoot, + sourcemap: sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true), + splitting: true, + tsconfig, + external: externalDependencies, + write: false, + platform: 'browser', + preserveSymlinks, + plugins: [ + createCompilerPlugin( + // JS/TS options + { + sourcemap: !!sourcemapOptions.scripts, + thirdPartySourcemaps: sourcemapOptions.vendor, + tsconfig, + advancedOptimizations, + fileReplacements, + sourceFileCache, + }, + // Component stylesheet options + { + workspaceRoot, + optimization: !!optimizationOptions.styles.minify, + sourcemap: + // Hidden component stylesheet sourcemaps are inaccessible which is effectively + // the same as being disabled. Disabling has the advantage of avoiding the overhead + // of sourcemap processing. + !!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'), + outputNames, + includePaths: stylePreprocessorOptions?.includePaths, + externalDependencies, + target, + }, + ), + ], + define: { + // Only set to false when script optimizations are enabled. It should not be set to true because + // Angular turns `ngDevMode` into an object for development debugging purposes when not defined + // which a constant true value would break. + ...(optimizationOptions.scripts ? { 'ngDevMode': 'false' } : undefined), + // Only AOT mode is supported currently + 'ngJitMode': 'false', + }, + }; +} + +/** + * Generates a syntax feature object map for Angular applications based on a list of targets. + * A full set of feature names can be found here: https://esbuild.github.io/api/#supported + * @param target An array of browser/engine targets in the format accepted by the esbuild `target` option. + * @returns An object that can be used with the esbuild build `supported` option. + */ +function getFeatureSupport(target: string[]): BuildOptions['supported'] { + const supported: Record = { + // Native async/await is not supported with Zone.js. Disabling support here will cause + // esbuild to downlevel async/await and for await...of to a Zone.js supported form. However, esbuild + // does not currently support downleveling async generators. Instead babel is used within the JS/TS + // loader to perform the downlevel transformation. + // NOTE: If esbuild adds support in the future, the babel support for async generators can be disabled. + 'async-await': false, + // V8 currently has a performance defect involving object spread operations that can cause signficant + // degradation in runtime performance. By not supporting the language feature here, a downlevel form + // will be used instead which provides a workaround for the performance issue. + // For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536 + 'object-rest-spread': false, + }; + + // Detect Safari browser versions that have a class field behavior bug + // See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033 + // See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2 + let safariClassFieldScopeBug = false; + for (const browser of target) { + let majorVersion; + if (browser.startsWith('ios')) { + majorVersion = Number(browser.slice(3, 5)); + } else if (browser.startsWith('safari')) { + majorVersion = Number(browser.slice(6, 8)); + } else { + continue; + } + // Technically, 14.0 is not broken but rather does not have support. However, the behavior + // is identical since it would be set to false by esbuild if present as a target. + if (majorVersion === 14 || majorVersion === 15) { + safariClassFieldScopeBug = true; + break; + } + } + // If class field support cannot be used set to false; otherwise leave undefined to allow + // esbuild to use `target` to determine support. + if (safariClassFieldScopeBug) { + supported['class-field'] = false; + supported['class-static-field'] = false; + } + + return supported; +} + +function createGlobalStylesBundleOptions( + options: NormalizedBrowserOptions, + target: string[], +): BuildOptions { + const { + workspaceRoot, + optimizationOptions, + sourcemapOptions, + outputNames, + globalStyles, + preserveSymlinks, + externalDependencies, + stylePreprocessorOptions, + watch, + } = options; + + const buildOptions = createStylesheetBundleOptions({ + workspaceRoot, + optimization: !!optimizationOptions.styles.minify, + sourcemap: !!sourcemapOptions.styles, + preserveSymlinks, + target, + externalDependencies, + outputNames, + includePaths: stylePreprocessorOptions?.includePaths, + }); + buildOptions.incremental = watch; + + const namespace = 'angular:styles/global'; + buildOptions.entryPoints = {}; + for (const { name } of globalStyles) { + buildOptions.entryPoints[name] = `${namespace};${name}`; + } + + buildOptions.plugins.unshift({ + name: 'angular-global-styles', + setup(build) { + build.onResolve({ filter: /^angular:styles\/global;/ }, (args) => { + if (args.kind !== 'entry-point') { + return null; + } + + return { + path: args.path.split(';', 2)[1], + namespace, + }; + }); + build.onLoad({ filter: /./, namespace }, (args) => { + const files = globalStyles.find(({ name }) => name === args.path)?.files; + assert(files, `global style name should always be found [${args.path}]`); + + return { + contents: files.map((file) => `@import 'https://github.com/$%7Bfile.replace(////g,'/')}';`).join('\n'), + loader: 'css', + resolveDir: workspaceRoot, + }; + }); + }, + }); + + return buildOptions; +} + +/** + * Main execution function for the esbuild-based application builder. + * The options are compatible with the Webpack-based builder. + * @param initialOptions The browser builder options to use when setting up the application build + * @param context The Architect builder context object + * @returns An async iterable with the builder result output + */ +export async function* buildEsbuildBrowser( + initialOptions: BrowserBuilderOptions, + context: BuilderContext, +): AsyncIterable { + // Only AOT is currently supported + if (initialOptions.aot !== true) { + context.logger.error( + 'JIT mode is currently not supported by this experimental builder. AOT mode must be used.', + ); + + return { success: false }; + } + + // Inform user of experimental status of builder and options + logExperimentalWarnings(initialOptions, context); + + // Determine project name from builder context target + const projectName = context.target?.project; + if (!projectName) { + context.logger.error(`The 'browser-esbuild' builder requires a target to be specified.`); + + return { success: false }; + } + + const normalizedOptions = await normalizeOptions(context, projectName, initialOptions); + + // Clean output path if enabled + if (initialOptions.deleteOutputPath) { + deleteOutputDir(normalizedOptions.workspaceRoot, initialOptions.outputPath); + } + + // Create output directory if needed + try { + await fs.mkdir(normalizedOptions.outputPath, { recursive: true }); + } catch (e) { + assertIsError(e); + context.logger.error('Unable to create output directory: ' + e.message); + + return { success: false }; + } + + // Initial build + let result = await execute(normalizedOptions, context); + yield result.output; + + // Finish if watch mode is not enabled + if (!initialOptions.watch) { + shutdownSassWorkerPool(); + + return; + } + + context.logger.info('Watch mode enabled. Watching for file changes...'); + + // Setup a watcher + const watcher = createWatcher({ + polling: typeof initialOptions.poll === 'number', + interval: initialOptions.poll, + // Ignore the output and cache paths to avoid infinite rebuild cycles + ignored: [normalizedOptions.outputPath, normalizedOptions.cacheOptions.basePath], + }); + + // Temporarily watch the entire project + watcher.add(normalizedOptions.projectRoot); + + // Watch workspace root node modules + // Includes Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/) + watcher.add(path.join(normalizedOptions.workspaceRoot, 'node_modules')); + watcher.add(path.join(normalizedOptions.workspaceRoot, '.pnp.cjs')); + watcher.add(path.join(normalizedOptions.workspaceRoot, '.pnp.data.json')); + + // Wait for changes and rebuild as needed + try { + for await (const changes of watcher) { + context.logger.info('Changes detected. Rebuilding...'); + + if (initialOptions.verbose) { + context.logger.info(changes.toDebugString()); + } + + result = await execute(normalizedOptions, context, result.createRebuildState(changes)); + yield result.output; + } + } finally { + // Stop the watcher + await watcher.close(); + // Cleanup incremental rebuild state + result.dispose(); + shutdownSassWorkerPool(); + } +} + +export default createBuilder(buildEsbuildBrowser); diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts new file mode 100644 index 000000000000..649b970a048c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts @@ -0,0 +1,175 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import * as path from 'path'; +import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; +import { normalizePolyfills } from '../../utils/normalize-polyfills'; +import { generateEntryPoints } from '../../utils/package-chunk-sort'; +import { getIndexInputFile, getIndexOutputFile } from '../../utils/webpack-browser-config'; +import { normalizeGlobalStyles } from '../../webpack/utils/helpers'; +import { Schema as BrowserBuilderOptions, OutputHashing } from './schema'; + +export type NormalizedBrowserOptions = Awaited>; + +/** + * Normalize the user provided options by creating full paths for all path based options + * and converting multi-form options into a single form that can be directly used + * by the build process. + * + * @param context The context for current builder execution. + * @param projectName The name of the project for the current execution. + * @param options An object containing the options to use for the build. + * @returns An object containing normalized options required to perform the build. + */ +export async function normalizeOptions( + context: BuilderContext, + projectName: string, + options: BrowserBuilderOptions, +) { + const workspaceRoot = context.workspaceRoot; + const projectMetadata = await context.getProjectMetadata(projectName); + const projectRoot = path.join(workspaceRoot, (projectMetadata.root as string | undefined) ?? ''); + const projectSourceRoot = path.join( + workspaceRoot, + (projectMetadata.sourceRoot as string | undefined) ?? 'src', + ); + + const cacheOptions = normalizeCacheOptions(projectMetadata, workspaceRoot); + + const mainEntryPoint = path.join(workspaceRoot, options.main); + + // Currently esbuild do not support multiple files per entry-point + const [polyfillsEntryPoint, ...remainingPolyfills] = normalizePolyfills( + options.polyfills, + workspaceRoot, + ); + + if (remainingPolyfills.length) { + context.logger.warn( + `The 'polyfills' option currently does not support multiple entries by this experimental builder. The first entry will be used.`, + ); + } + + const tsconfig = path.join(workspaceRoot, options.tsConfig); + const outputPath = path.join(workspaceRoot, options.outputPath); + const optimizationOptions = normalizeOptimization(options.optimization); + const sourcemapOptions = normalizeSourceMaps(options.sourceMap ?? false); + const assets = options.assets?.length + ? normalizeAssetPatterns(options.assets, workspaceRoot, projectRoot, projectSourceRoot) + : undefined; + + const outputNames = { + bundles: + options.outputHashing === OutputHashing.All || options.outputHashing === OutputHashing.Bundles + ? '[name].[hash]' + : '[name]', + media: + options.outputHashing === OutputHashing.All || options.outputHashing === OutputHashing.Media + ? '[name].[hash]' + : '[name]', + }; + if (options.resourcesOutputPath) { + outputNames.media = path.join(options.resourcesOutputPath, outputNames.media); + } + + let fileReplacements: Record | undefined; + if (options.fileReplacements) { + for (const replacement of options.fileReplacements) { + fileReplacements ??= {}; + fileReplacements[path.join(workspaceRoot, replacement.replace)] = path.join( + workspaceRoot, + replacement.with, + ); + } + } + + const globalStyles: { name: string; files: string[]; initial: boolean }[] = []; + if (options.styles?.length) { + const { entryPoints: stylesheetEntrypoints, noInjectNames } = normalizeGlobalStyles( + options.styles || [], + ); + for (const [name, files] of Object.entries(stylesheetEntrypoints)) { + globalStyles.push({ name, files, initial: !noInjectNames.includes(name) }); + } + } + + let serviceWorkerOptions; + if (options.serviceWorker) { + // If ngswConfigPath is not specified, the default is 'ngsw-config.json' within the project root + serviceWorkerOptions = options.ngswConfigPath + ? path.join(workspaceRoot, options.ngswConfigPath) + : path.join(projectRoot, 'ngsw-config.json'); + } + + // Setup bundler entry points + const entryPoints: Record = { + main: mainEntryPoint, + }; + if (polyfillsEntryPoint) { + entryPoints['polyfills'] = polyfillsEntryPoint; + } + + let indexHtmlOptions; + if (options.index) { + indexHtmlOptions = { + input: path.join(workspaceRoot, getIndexInputFile(options.index)), + // The output file will be created within the configured output path + output: getIndexOutputFile(options.index), + // TODO: Use existing information from above to create the insertion order + insertionOrder: generateEntryPoints({ + scripts: options.scripts ?? [], + styles: options.styles ?? [], + }), + }; + } + + // Initial options to keep + const { + baseHref, + buildOptimizer, + crossOrigin, + externalDependencies, + poll, + preserveSymlinks, + stylePreprocessorOptions, + subresourceIntegrity, + verbose, + watch, + } = options; + + // Return all the normalized options + return { + advancedOptimizations: buildOptimizer, + baseHref, + cacheOptions, + crossOrigin, + externalDependencies, + poll, + // If not explicitly set, default to the Node.js process argument + preserveSymlinks: preserveSymlinks ?? process.execArgv.includes('--preserve-symlinks'), + stylePreprocessorOptions, + subresourceIntegrity, + verbose, + watch, + workspaceRoot, + entryPoints, + optimizationOptions, + outputPath, + sourcemapOptions, + tsconfig, + projectRoot, + assets, + outputNames, + fileReplacements, + globalStyles, + serviceWorkerOptions, + indexHtmlOptions, + }; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/profiling.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/profiling.ts new file mode 100644 index 000000000000..82b852e997ee --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/profiling.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { debugPerformance } from '../../utils/environment-options'; + +let cumulativeDurations: Map | undefined; + +export function resetCumulativeDurations(): void { + cumulativeDurations?.clear(); +} + +export function logCumulativeDurations(): void { + if (!debugPerformance || !cumulativeDurations) { + return; + } + + for (const [name, durations] of cumulativeDurations) { + let total = 0; + let min; + let max; + for (const duration of durations) { + total += duration; + if (min === undefined || duration < min) { + min = duration; + } + if (max === undefined || duration > max) { + max = duration; + } + } + const average = total / durations.length; + // eslint-disable-next-line no-console + console.log( + `DURATION[${name}]: ${total.toFixed(9)}s [count: ${durations.length}; avg: ${average.toFixed( + 9, + )}s; min: ${min?.toFixed(9)}s; max: ${max?.toFixed(9)}s]`, + ); + } +} + +function recordDuration(name: string, startTime: bigint, cumulative?: boolean): void { + const duration = Number(process.hrtime.bigint() - startTime) / 10 ** 9; + if (cumulative) { + cumulativeDurations ??= new Map(); + const durations = cumulativeDurations.get(name) ?? []; + durations.push(duration); + cumulativeDurations.set(name, durations); + } else { + // eslint-disable-next-line no-console + console.log(`DURATION[${name}]: ${duration.toFixed(9)}s`); + } +} + +export async function profileAsync( + name: string, + action: () => Promise, + cumulative?: boolean, +): Promise { + if (!debugPerformance) { + return action(); + } + + const startTime = process.hrtime.bigint(); + try { + return await action(); + } finally { + recordDuration(name, startTime, cumulative); + } +} + +export function profileSync(name: string, action: () => T, cumulative?: boolean): T { + if (!debugPerformance) { + return action(); + } + + const startTime = process.hrtime.bigint(); + try { + return action(); + } finally { + recordDuration(name, startTime, cumulative); + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts new file mode 100644 index 000000000000..1ef2613e1302 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/sass-plugin.ts @@ -0,0 +1,168 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { PartialMessage, Plugin, PluginBuild } from 'esbuild'; +import { readFile } from 'node:fs/promises'; +import { dirname, join, relative } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import type { CompileResult, Exception } from 'sass'; +import { + FileImporterWithRequestContextOptions, + SassWorkerImplementation, +} from '../../sass/sass-service'; + +let sassWorkerPool: SassWorkerImplementation | undefined; + +function isSassException(error: unknown): error is Exception { + return !!error && typeof error === 'object' && 'sassMessage' in error; +} + +export function shutdownSassWorkerPool(): void { + sassWorkerPool?.close(); + sassWorkerPool = undefined; +} + +export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: string[] }): Plugin { + return { + name: 'angular-sass', + setup(build: PluginBuild): void { + const resolveUrl = async (url: string, previousResolvedModules?: Set) => { + let result = await build.resolve(url, { + kind: 'import-rule', + // This should ideally be the directory of the importer file from Sass + // but that is not currently available from the Sass importer API. + resolveDir: build.initialOptions.absWorkingDir, + }); + + // Workaround to support Yarn PnP without access to the importer file from Sass + if (!result.path && previousResolvedModules?.size) { + for (const previous of previousResolvedModules) { + result = await build.resolve(url, { + kind: 'import-rule', + resolveDir: previous, + }); + if (result.path) { + break; + } + } + } + + return result; + }; + + build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => { + // Lazily load Sass when a Sass file is found + sassWorkerPool ??= new SassWorkerImplementation(true); + + const warnings: PartialMessage[] = []; + try { + const data = await readFile(args.path, 'utf-8'); + const { css, sourceMap, loadedUrls } = await sassWorkerPool.compileStringAsync(data, { + url: pathToFileURL(args.path), + style: 'expanded', + loadPaths: options.loadPaths, + sourceMap: options.sourcemap, + sourceMapIncludeSources: options.sourcemap, + quietDeps: true, + importers: [ + { + findFileUrl: async ( + url, + { previousResolvedModules }: FileImporterWithRequestContextOptions, + ): Promise => { + const result = await resolveUrl(url, previousResolvedModules); + + // Check for package deep imports + if (!result.path) { + const parts = url.split('/'); + const hasScope = parts.length >= 2 && parts[0].startsWith('@'); + const [nameOrScope, nameOrFirstPath, ...pathPart] = parts; + const packageName = hasScope + ? `${nameOrScope}/${nameOrFirstPath}` + : nameOrScope; + + const packageResult = await resolveUrl( + packageName + '/package.json', + previousResolvedModules, + ); + + if (packageResult.path) { + return pathToFileURL( + join( + dirname(packageResult.path), + !hasScope ? nameOrFirstPath : '', + ...pathPart, + ), + ); + } + } + + return result.path ? pathToFileURL(result.path) : null; + }, + }, + ], + logger: { + warn: (text, { deprecation, span }) => { + warnings.push({ + text: deprecation ? 'Deprecation' : text, + location: span && { + file: span.url && fileURLToPath(span.url), + lineText: span.context, + // Sass line numbers are 0-based while esbuild's are 1-based + line: span.start.line + 1, + column: span.start.column, + }, + notes: deprecation ? [{ text }] : undefined, + }); + }, + }, + }); + + return { + loader: 'css', + contents: sourceMap + ? `${css}\n${sourceMapToUrlComment(sourceMap, dirname(args.path))}` + : css, + watchFiles: loadedUrls.map((url) => fileURLToPath(url)), + warnings, + }; + } catch (error) { + if (isSassException(error)) { + const file = error.span.url ? fileURLToPath(error.span.url) : undefined; + + return { + loader: 'css', + errors: [ + { + text: error.message, + }, + ], + warnings, + watchFiles: file ? [file] : undefined, + }; + } + + throw error; + } + }); + }, + }; +} + +function sourceMapToUrlComment( + sourceMap: Exclude, + root: string, +): string { + // Remove `file` protocol from all sourcemap sources and adjust to be relative to the input file. + // This allows esbuild to correctly process the paths. + sourceMap.sources = sourceMap.sources.map((source) => relative(root, fileURLToPath(source))); + + const urlSourceMap = Buffer.from(JSON.stringify(sourceMap), 'utf-8').toString('base64'); + + return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${urlSourceMap} */`; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json new file mode 100644 index 000000000000..0c184a854d5d --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json @@ -0,0 +1,537 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Esbuild browser schema for Build Facade.", + "description": "Browser target options", + "type": "object", + "properties": { + "assets": { + "type": "array", + "description": "List of static application assets.", + "default": [], + "items": { + "$ref": "#/definitions/assetPattern" + } + }, + "main": { + "type": "string", + "description": "The full path for the main entry point to the app, relative to the current workspace." + }, + "polyfills": { + "description": "Polyfills to be included in the build.", + "oneOf": [ + { + "type": "array", + "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + { + "type": "string", + "description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'." + } + ] + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, + "scripts": { + "description": "Global scripts to be included in the build.", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + } + ] + } + }, + "styles": { + "description": "Global styles to be included in the build.", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less)$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less)$" + } + ] + } + }, + "inlineStyleLanguage": { + "description": "The stylesheet language to use for the application's inline component styles.", + "type": "string", + "default": "css", + "enum": ["css", "less", "sass", "scss"] + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false + }, + "externalDependencies": { + "description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "optimization": { + "description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.", + "default": true, + "x-user-analytics": "ep.ng_optimization", + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Enables optimization of the scripts output.", + "default": true + }, + "styles": { + "description": "Enables optimization of the styles output.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "minify": { + "type": "boolean", + "description": "Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers and minimizing values.", + "default": true + }, + "inlineCritical": { + "type": "boolean", + "description": "Extract and inline critical CSS definitions to improve first paint time.", + "default": true + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "fonts": { + "description": "Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "inline": { + "type": "boolean", + "description": "Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.", + "default": true + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "fileReplacements": { + "description": "Replace compilation source files with other compilation source files in the build.", + "type": "array", + "items": { + "$ref": "#/definitions/fileReplacement" + }, + "default": [] + }, + "outputPath": { + "type": "string", + "description": "The full path for the new output directory, relative to the current workspace.\nBy default, writes output to a folder named dist/ in the current project." + }, + "resourcesOutputPath": { + "type": "string", + "description": "The path where style resources will be placed, relative to outputPath." + }, + "aot": { + "type": "boolean", + "description": "Build using Ahead of Time compilation.", + "x-user-analytics": "ep.ng_aot", + "default": true + }, + "sourceMap": { + "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", + "default": false, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output source maps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output source maps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output source maps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages source maps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "vendorChunk": { + "type": "boolean", + "description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.", + "default": false + }, + "commonChunk": { + "type": "boolean", + "description": "Generate a seperate bundle containing code used across multiple bundles.", + "default": true + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + }, + "deployUrl": { + "type": "string", + "description": "URL where files will be deployed.", + "x-deprecated": "Use \"baseHref\" option, \"APP_BASE_HREF\" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url." + }, + "verbose": { + "type": "boolean", + "description": "Adds more details to output logging.", + "default": false + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console while building.", + "default": true + }, + "i18nMissingTranslation": { + "type": "string", + "description": "How to handle missing translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "i18nDuplicateTranslation": { + "type": "string", + "description": "How to handle duplicate translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "localize": { + "description": "Translate the bundles in one or more locales.", + "oneOf": [ + { + "type": "boolean", + "description": "Translate all locales." + }, + { + "type": "array", + "description": "List of locales ID's to translate.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$" + } + } + ] + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + }, + "outputHashing": { + "type": "string", + "description": "Define the output filename cache-busting hashing mode.", + "default": "none", + "enum": ["none", "all", "media", "bundles"] + }, + "poll": { + "type": "number", + "description": "Enable and define the file watching poll time period in milliseconds." + }, + "deleteOutputPath": { + "type": "boolean", + "description": "Delete the output path before building.", + "default": true + }, + "preserveSymlinks": { + "type": "boolean", + "description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set." + }, + "extractLicenses": { + "type": "boolean", + "description": "Extract all licenses in a separate file.", + "default": true + }, + "buildOptimizer": { + "type": "boolean", + "description": "Enables advanced build optimizations when using the 'aot' option.", + "default": true + }, + "namedChunks": { + "type": "boolean", + "description": "Use file name for lazy loaded chunks.", + "default": false + }, + "subresourceIntegrity": { + "type": "boolean", + "description": "Enables the use of subresource integrity validation.", + "default": false + }, + "serviceWorker": { + "type": "boolean", + "description": "Generates a service worker config for production builds.", + "default": false + }, + "ngswConfigPath": { + "type": "string", + "description": "Path to ngsw-config.json." + }, + "index": { + "description": "Configures the generation of the application's HTML index.", + "oneOf": [ + { + "type": "string", + "description": "The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path." + }, + { + "type": "object", + "description": "", + "properties": { + "input": { + "type": "string", + "minLength": 1, + "description": "The path of a file to use for the application's generated HTML index." + }, + "output": { + "type": "string", + "minLength": 1, + "default": "index.html", + "description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path." + } + }, + "required": ["input"] + } + ] + }, + "statsJson": { + "type": "boolean", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", + "default": false + }, + "budgets": { + "description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.", + "type": "array", + "items": { + "$ref": "#/definitions/budget" + }, + "default": [] + }, + "webWorkerTsConfig": { + "type": "string", + "description": "TypeScript configuration for Web Worker modules." + }, + "crossOrigin": { + "type": "string", + "description": "Define the crossorigin attribute setting of elements that provide CORS support.", + "default": "none", + "enum": ["none", "anonymous", "use-credentials"] + }, + "allowedCommonJsDependencies": { + "description": "A list of CommonJS packages that are allowed to be used without a build time warning.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false, + "required": ["outputPath", "index", "main", "tsConfig"], + "definitions": { + "assetPattern": { + "oneOf": [ + { + "type": "object", + "properties": { + "followSymlinks": { + "type": "boolean", + "default": false, + "description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched." + }, + "glob": { + "type": "string", + "description": "The pattern to match." + }, + "input": { + "type": "string", + "description": "The input directory path in which to apply 'glob'. Defaults to the project root." + }, + "ignore": { + "description": "An array of globs to ignore.", + "type": "array", + "items": { + "type": "string" + } + }, + "output": { + "type": "string", + "description": "Absolute path within the output." + } + }, + "additionalProperties": false, + "required": ["glob", "input", "output"] + }, + { + "type": "string" + } + ] + }, + "fileReplacement": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + }, + "with": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + } + }, + "additionalProperties": false, + "required": ["replace", "with"] + }, + "budget": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of budget.", + "enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"] + }, + "name": { + "type": "string", + "description": "The name of the bundle." + }, + "baseline": { + "type": "string", + "description": "The baseline size for comparison." + }, + "maximumWarning": { + "type": "string", + "description": "The maximum threshold for warning relative to the baseline." + }, + "maximumError": { + "type": "string", + "description": "The maximum threshold for error relative to the baseline." + }, + "minimumWarning": { + "type": "string", + "description": "The minimum threshold for warning relative to the baseline." + }, + "minimumError": { + "type": "string", + "description": "The minimum threshold for error relative to the baseline." + }, + "warning": { + "type": "string", + "description": "The threshold for warning relative to the baseline (min & max)." + }, + "error": { + "type": "string", + "description": "The threshold for error relative to the baseline (min & max)." + } + }, + "additionalProperties": false, + "required": ["type"] + } + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts new file mode 100644 index 000000000000..a34a624cea76 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts @@ -0,0 +1,129 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { BuildOptions, OutputFile } from 'esbuild'; +import * as path from 'path'; +import { createCssResourcePlugin } from './css-resource-plugin'; +import { bundle } from './esbuild'; +import { createSassPlugin } from './sass-plugin'; + +export interface BundleStylesheetOptions { + workspaceRoot: string; + optimization: boolean; + preserveSymlinks?: boolean; + sourcemap: boolean | 'external' | 'inline'; + outputNames?: { bundles?: string; media?: string }; + includePaths?: string[]; + externalDependencies?: string[]; + target: string[]; +} + +export function createStylesheetBundleOptions( + options: BundleStylesheetOptions, +): BuildOptions & { plugins: NonNullable } { + return { + absWorkingDir: options.workspaceRoot, + bundle: true, + entryNames: options.outputNames?.bundles, + assetNames: options.outputNames?.media, + logLevel: 'silent', + minify: options.optimization, + sourcemap: options.sourcemap, + outdir: options.workspaceRoot, + write: false, + platform: 'browser', + target: options.target, + preserveSymlinks: options.preserveSymlinks, + external: options.externalDependencies, + conditions: ['style', 'sass'], + mainFields: ['style', 'sass'], + plugins: [ + createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths: options.includePaths }), + createCssResourcePlugin(), + ], + }; +} + +async function bundleStylesheet( + entry: Required | Pick>, + options: BundleStylesheetOptions, +) { + // Execute esbuild + const result = await bundle(options.workspaceRoot, { + ...createStylesheetBundleOptions(options), + ...entry, + }); + + // Extract the result of the bundling from the output files + let contents = ''; + let map; + let outputPath; + const resourceFiles: OutputFile[] = []; + if (result.outputFiles) { + for (const outputFile of result.outputFiles) { + const filename = path.basename(outputFile.path); + if (filename.endsWith('.css')) { + outputPath = outputFile.path; + contents = outputFile.text; + } else if (filename.endsWith('.css.map')) { + map = outputFile.text; + } else { + // The output files could also contain resources (images/fonts/etc.) that were referenced + resourceFiles.push(outputFile); + } + } + } + + return { + errors: result.errors, + warnings: result.warnings, + contents, + map, + path: outputPath, + resourceFiles, + }; +} + +/** + * Bundle a stylesheet that exists as a file on the filesystem. + * + * @param filename The path to the file to bundle. + * @param options The stylesheet bundling options to use. + * @returns The bundle result object. + */ +export async function bundleStylesheetFile(filename: string, options: BundleStylesheetOptions) { + return bundleStylesheet({ entryPoints: [filename] }, options); +} + +/** + * Bundle stylesheet text data from a string. + * + * @param data The string content of a stylesheet to bundle. + * @param dataOptions The options to use to resolve references and name output of the stylesheet data. + * @param bundleOptions The stylesheet bundling options to use. + * @returns The bundle result object. + */ +export async function bundleStylesheetText( + data: string, + dataOptions: { resolvePath: string; virtualName?: string }, + bundleOptions: BundleStylesheetOptions, +) { + const result = bundleStylesheet( + { + stdin: { + contents: data, + sourcefile: dataOptions.virtualName, + resolveDir: dataOptions.resolvePath, + loader: 'css', + }, + }, + bundleOptions, + ); + + return result; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/external-dependencies_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/external-dependencies_spec.ts new file mode 100644 index 000000000000..fd3d4f5c87d5 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/external-dependencies_spec.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildEsbuildBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "externalDependencies"', () => { + it('should not externalize any dependency when option is not set', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('dist/main.js').content.not.toMatch(/from ['"]@angular\/core['"]/); + harness.expectFile('dist/main.js').content.not.toMatch(/from ['"]@angular\/common['"]/); + }); + + it('should only externalize the listed depedencies when option is set', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + externalDependencies: ['@angular/core'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/main.js').content.toMatch(/from ['"]@angular\/core['"]/); + harness.expectFile('dist/main.js').content.not.toMatch(/from ['"]@angular\/common['"]/); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/setup.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/setup.ts new file mode 100644 index 000000000000..fc69d7f8d9a7 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/setup.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Schema } from '../schema'; + +export { describeBuilder } from '../../../testing'; + +export const BROWSER_BUILDER_INFO = Object.freeze({ + name: '@angular-devkit/build-angular:browser-esbuild', + schemaPath: __dirname + '/../schema.json', +}); + +/** + * Contains all required browser builder fields. + * Also disables progress reporting to minimize logging output. + */ +export const BASE_OPTIONS = Object.freeze({ + index: 'src/index.html', + main: 'src/main.ts', + outputPath: 'dist', + tsConfig: 'src/tsconfig.app.json', + progress: false, + + // Disable optimizations + optimization: false, + buildOptimizer: false, +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts new file mode 100644 index 000000000000..2fd26ee56f2e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/watcher.ts @@ -0,0 +1,121 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { FSWatcher } from 'chokidar'; + +export class ChangedFiles { + readonly added = new Set(); + readonly modified = new Set(); + readonly removed = new Set(); + + toDebugString(): string { + const content = { + added: Array.from(this.added), + modified: Array.from(this.modified), + removed: Array.from(this.removed), + }; + + return JSON.stringify(content, null, 2); + } +} + +export interface BuildWatcher extends AsyncIterableIterator { + add(paths: string | string[]): void; + remove(paths: string | string[]): void; + close(): Promise; +} + +export function createWatcher(options?: { + polling?: boolean; + interval?: number; + ignored?: string[]; +}): BuildWatcher { + const watcher = new FSWatcher({ + ...options, + disableGlobbing: true, + ignoreInitial: true, + }); + + const nextQueue: ((value?: ChangedFiles) => void)[] = []; + let currentChanges: ChangedFiles | undefined; + let nextWaitTimeout: NodeJS.Timeout | undefined; + + watcher.on('all', (event, path) => { + switch (event) { + case 'add': + currentChanges ??= new ChangedFiles(); + currentChanges.added.add(path); + break; + case 'change': + currentChanges ??= new ChangedFiles(); + currentChanges.modified.add(path); + break; + case 'unlink': + currentChanges ??= new ChangedFiles(); + currentChanges.removed.add(path); + break; + default: + return; + } + + // Wait 250ms from next change to better capture groups of file save operations. + if (!nextWaitTimeout) { + nextWaitTimeout = setTimeout(() => { + nextWaitTimeout = undefined; + const next = nextQueue.shift(); + if (next) { + const value = currentChanges; + currentChanges = undefined; + next(value); + } + }, 250); + nextWaitTimeout?.unref(); + } + }); + + return { + [Symbol.asyncIterator]() { + return this; + }, + + async next() { + if (currentChanges && nextQueue.length === 0) { + const result = { value: currentChanges }; + currentChanges = undefined; + + return result; + } + + return new Promise((resolve) => { + nextQueue.push((value) => resolve(value ? { value } : { done: true, value })); + }); + }, + + add(paths) { + watcher.add(paths); + }, + + remove(paths) { + watcher.unwatch(paths); + }, + + async close() { + try { + await watcher.close(); + if (nextWaitTimeout) { + clearTimeout(nextWaitTimeout); + } + } finally { + let next; + while ((next = nextQueue.shift()) !== undefined) { + next(); + } + } + }, + }; +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser/index.ts b/packages/angular_devkit/build_angular/src/builders/browser/index.ts new file mode 100644 index 000000000000..30bf61c8896c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/index.ts @@ -0,0 +1,479 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { EmittedFiles, WebpackLoggingCallback, runWebpack } from '@angular-devkit/build-webpack'; +import * as fs from 'fs'; +import * as path from 'path'; +import { Observable, from } from 'rxjs'; +import { concatMap, map, switchMap } from 'rxjs/operators'; +import webpack, { StatsCompilation } from 'webpack'; +import { ExecutionTransformer } from '../../transforms'; +import { + deleteOutputDir, + normalizeAssetPatterns, + normalizeOptimization, + urlJoin, +} from '../../utils'; +import { + BudgetCalculatorResult, + ThresholdSeverity, + checkBudgets, +} from '../../utils/bundle-calculator'; +import { colors } from '../../utils/color'; +import { copyAssets } from '../../utils/copy-assets'; +import { assertIsError } from '../../utils/error'; +import { i18nInlineEmittedFiles } from '../../utils/i18n-inlining'; +import { I18nOptions } from '../../utils/i18n-options'; +import { FileInfo } from '../../utils/index-file/augment-index-html'; +import { + IndexHtmlGenerator, + IndexHtmlTransform, +} from '../../utils/index-file/index-html-generator'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; +import { ensureOutputPaths } from '../../utils/output-paths'; +import { generateEntryPoints } from '../../utils/package-chunk-sort'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { augmentAppWithServiceWorker } from '../../utils/service-worker'; +import { Spinner } from '../../utils/spinner'; +import { assertCompatibleAngularVersion } from '../../utils/version'; +import { + generateI18nBrowserWebpackConfigFromContext, + getIndexInputFile, + getIndexOutputFile, +} from '../../utils/webpack-browser-config'; +import { getCommonConfig, getStylesConfig } from '../../webpack/configs'; +import { markAsyncChunksNonInitial } from '../../webpack/utils/async-chunks'; +import { normalizeExtraEntryPoints } from '../../webpack/utils/helpers'; +import { + BuildEventStats, + generateBuildEventStats, + statsErrorsToString, + statsHasErrors, + statsHasWarnings, + statsWarningsToString, + webpackStatsLogger, +} from '../../webpack/utils/stats'; +import { Schema as BrowserBuilderSchema } from './schema'; + +/** + * @experimental Direct usage of this type is considered experimental. + */ +export type BrowserBuilderOutput = BuilderOutput & { + stats: BuildEventStats; + + baseOutputPath: string; + /** + * @deprecated in version 14. Use 'outputs' instead. + */ + outputPaths: string[]; + /** + * @deprecated in version 9. Use 'outputs' instead. + */ + outputPath: string; + + outputs: { + locale?: string; + path: string; + baseHref?: string; + }[]; +}; + +/** + * Maximum time in milliseconds for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 30_000; + +async function initialize( + options: BrowserBuilderSchema, + context: BuilderContext, + webpackConfigurationTransform?: ExecutionTransformer, +): Promise<{ + config: webpack.Configuration; + projectRoot: string; + projectSourceRoot?: string; + i18n: I18nOptions; +}> { + const originalOutputPath = options.outputPath; + + // Assets are processed directly by the builder except when watching + const adjustedOptions = options.watch ? options : { ...options, assets: [] }; + + const { config, projectRoot, projectSourceRoot, i18n } = + await generateI18nBrowserWebpackConfigFromContext(adjustedOptions, context, (wco) => [ + getCommonConfig(wco), + getStylesConfig(wco), + ]); + + // Validate asset option values if processed directly + if (options.assets?.length && !adjustedOptions.assets?.length) { + normalizeAssetPatterns( + options.assets, + context.workspaceRoot, + projectRoot, + projectSourceRoot, + ).forEach(({ output }) => { + if (output.startsWith('..')) { + throw new Error('An asset cannot be written to a location outside of the output path.'); + } + }); + } + + let transformedConfig; + if (webpackConfigurationTransform) { + transformedConfig = await webpackConfigurationTransform(config); + } + + if (options.deleteOutputPath) { + deleteOutputDir(context.workspaceRoot, originalOutputPath); + } + + return { config: transformedConfig || config, projectRoot, projectSourceRoot, i18n }; +} + +/** + * @experimental Direct usage of this function is considered experimental. + */ +// eslint-disable-next-line max-lines-per-function +export function buildWebpackBrowser( + options: BrowserBuilderSchema, + context: BuilderContext, + transforms: { + webpackConfiguration?: ExecutionTransformer; + logging?: WebpackLoggingCallback; + indexHtml?: IndexHtmlTransform; + } = {}, +): Observable { + const projectName = context.target?.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + + const baseOutputPath = path.resolve(context.workspaceRoot, options.outputPath); + let outputPaths: undefined | Map; + + // Check Angular version. + assertCompatibleAngularVersion(context.workspaceRoot); + + return from(context.getProjectMetadata(projectName)).pipe( + switchMap(async (projectMetadata) => { + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + // Initialize builder + const initialization = await initialize(options, context, transforms.webpackConfiguration); + + // Add index file to watched files. + if (options.watch) { + const indexInputFile = path.join(context.workspaceRoot, getIndexInputFile(options.index)); + initialization.config.plugins ??= []; + initialization.config.plugins.push({ + apply: (compiler: webpack.Compiler) => { + compiler.hooks.thisCompilation.tap('build-angular', (compilation) => { + compilation.fileDependencies.add(indexInputFile); + }); + }, + }); + } + + return { + ...initialization, + cacheOptions: normalizeCacheOptions(projectMetadata, context.workspaceRoot), + }; + }), + switchMap( + // eslint-disable-next-line max-lines-per-function + ({ config, projectRoot, projectSourceRoot, i18n, cacheOptions }) => { + const normalizedOptimization = normalizeOptimization(options.optimization); + + return runWebpack(config, context, { + webpackFactory: require('webpack') as typeof webpack, + logging: + transforms.logging || + ((stats, config) => { + if (options.verbose) { + context.logger.info(stats.toString(config.stats)); + } + }), + }).pipe( + concatMap( + // eslint-disable-next-line max-lines-per-function + async ( + buildEvent, + ): Promise<{ output: BuilderOutput; webpackStats: StatsCompilation }> => { + const spinner = new Spinner(); + spinner.enabled = options.progress !== false; + + const { success, emittedFiles = [], outputPath: webpackOutputPath } = buildEvent; + const webpackRawStats = buildEvent.webpackStats; + if (!webpackRawStats) { + throw new Error('Webpack stats build result is required.'); + } + + // Fix incorrectly set `initial` value on chunks. + const extraEntryPoints = [ + ...normalizeExtraEntryPoints(options.styles || [], 'styles'), + ...normalizeExtraEntryPoints(options.scripts || [], 'scripts'), + ]; + + const webpackStats = { + ...webpackRawStats, + chunks: markAsyncChunksNonInitial(webpackRawStats, extraEntryPoints), + }; + + if (!success) { + // If using bundle downleveling then there is only one build + // If it fails show any diagnostic messages and bail + if (statsHasWarnings(webpackStats)) { + context.logger.warn(statsWarningsToString(webpackStats, { colors: true })); + } + if (statsHasErrors(webpackStats)) { + context.logger.error(statsErrorsToString(webpackStats, { colors: true })); + } + + return { + webpackStats: webpackRawStats, + output: { success: false }, + }; + } else { + outputPaths = ensureOutputPaths(baseOutputPath, i18n); + + const scriptsEntryPointName = normalizeExtraEntryPoints( + options.scripts || [], + 'scripts', + ).map((x) => x.bundleName); + + if (i18n.shouldInline) { + const success = await i18nInlineEmittedFiles( + context, + emittedFiles, + i18n, + baseOutputPath, + Array.from(outputPaths.values()), + scriptsEntryPointName, + webpackOutputPath, + options.i18nMissingTranslation, + ); + if (!success) { + return { + webpackStats: webpackRawStats, + output: { success: false }, + }; + } + } + + // Check for budget errors and display them to the user. + const budgets = options.budgets; + let budgetFailures: BudgetCalculatorResult[] | undefined; + if (budgets?.length) { + budgetFailures = [...checkBudgets(budgets, webpackStats)]; + for (const { severity, message } of budgetFailures) { + switch (severity) { + case ThresholdSeverity.Warning: + webpackStats.warnings?.push({ message }); + break; + case ThresholdSeverity.Error: + webpackStats.errors?.push({ message }); + break; + default: + assertNever(severity); + } + } + } + + const buildSuccess = success && !statsHasErrors(webpackStats); + if (buildSuccess) { + // Copy assets + if (!options.watch && options.assets?.length) { + spinner.start('Copying assets...'); + try { + await copyAssets( + normalizeAssetPatterns( + options.assets, + context.workspaceRoot, + projectRoot, + projectSourceRoot, + ), + Array.from(outputPaths.values()), + context.workspaceRoot, + ); + spinner.succeed('Copying assets complete.'); + } catch (err) { + spinner.fail(colors.redBright('Copying of assets failed.')); + assertIsError(err); + + return { + output: { + success: false, + error: 'Unable to copy assets: ' + err.message, + }, + webpackStats: webpackRawStats, + }; + } + } + + if (options.index) { + spinner.start('Generating index html...'); + + const entrypoints = generateEntryPoints({ + scripts: options.scripts ?? [], + styles: options.styles ?? [], + }); + + const indexHtmlGenerator = new IndexHtmlGenerator({ + cache: cacheOptions, + indexPath: path.join(context.workspaceRoot, getIndexInputFile(options.index)), + entrypoints, + deployUrl: options.deployUrl, + sri: options.subresourceIntegrity, + optimization: normalizedOptimization, + crossOrigin: options.crossOrigin, + postTransform: transforms.indexHtml, + }); + + let hasErrors = false; + for (const [locale, outputPath] of outputPaths.entries()) { + try { + const { content, warnings, errors } = await indexHtmlGenerator.process({ + baseHref: getLocaleBaseHref(i18n, locale) ?? options.baseHref, + // i18nLocale is used when Ivy is disabled + lang: locale || undefined, + outputPath, + files: mapEmittedFilesToFileInfo(emittedFiles), + }); + + if (warnings.length || errors.length) { + spinner.stop(); + warnings.forEach((m) => context.logger.warn(m)); + errors.forEach((m) => { + context.logger.error(m); + hasErrors = true; + }); + spinner.start(); + } + + const indexOutput = path.join( + outputPath, + getIndexOutputFile(options.index), + ); + await fs.promises.mkdir(path.dirname(indexOutput), { recursive: true }); + await fs.promises.writeFile(indexOutput, content); + } catch (error) { + spinner.fail('Index html generation failed.'); + assertIsError(error); + + return { + webpackStats: webpackRawStats, + output: { success: false, error: error.message }, + }; + } + } + + if (hasErrors) { + spinner.fail('Index html generation failed.'); + + return { + webpackStats: webpackRawStats, + output: { success: false }, + }; + } else { + spinner.succeed('Index html generation complete.'); + } + } + + if (options.serviceWorker) { + spinner.start('Generating service worker...'); + for (const [locale, outputPath] of outputPaths.entries()) { + try { + await augmentAppWithServiceWorker( + projectRoot, + context.workspaceRoot, + outputPath, + getLocaleBaseHref(i18n, locale) ?? options.baseHref ?? '/', + options.ngswConfigPath, + ); + } catch (error) { + spinner.fail('Service worker generation failed.'); + assertIsError(error); + + return { + webpackStats: webpackRawStats, + output: { success: false, error: error.message }, + }; + } + } + + spinner.succeed('Service worker generation complete.'); + } + } + + webpackStatsLogger(context.logger, webpackStats, config, budgetFailures); + + return { + webpackStats: webpackRawStats, + output: { success: buildSuccess }, + }; + } + }, + ), + map( + ({ output: event, webpackStats }) => + ({ + ...event, + stats: generateBuildEventStats(webpackStats, options), + baseOutputPath, + outputPath: baseOutputPath, + outputPaths: (outputPaths && Array.from(outputPaths.values())) || [baseOutputPath], + outputs: (outputPaths && + [...outputPaths.entries()].map(([locale, path]) => ({ + locale, + path, + baseHref: getLocaleBaseHref(i18n, locale) ?? options.baseHref, + }))) || { + path: baseOutputPath, + baseHref: options.baseHref, + }, + } as BrowserBuilderOutput), + ), + ); + }, + ), + ); + + function getLocaleBaseHref(i18n: I18nOptions, locale: string): string | undefined { + if (i18n.locales[locale] && i18n.locales[locale]?.baseHref !== '') { + return urlJoin(options.baseHref || '', i18n.locales[locale].baseHref ?? `/${locale}/`); + } + + return undefined; + } +} + +function assertNever(input: never): never { + throw new Error( + `Unexpected call to assertNever() with input: ${JSON.stringify( + input, + null /* replacer */, + 4 /* tabSize */, + )}`, + ); +} + +function mapEmittedFilesToFileInfo(files: EmittedFiles[] = []): FileInfo[] { + const filteredFiles: FileInfo[] = []; + for (const { file, name, extension, initial } of files) { + if (name && initial) { + filteredFiles.push({ file, extension, name }); + } + } + + return filteredFiles; +} + +export default createBuilder(buildWebpackBrowser); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/schema.json b/packages/angular_devkit/build_angular/src/builders/browser/schema.json new file mode 100644 index 000000000000..b45065bb7df0 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/schema.json @@ -0,0 +1,548 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Webpack browser schema for Build Facade.", + "description": "Browser target options", + "type": "object", + "properties": { + "assets": { + "type": "array", + "description": "List of static application assets.", + "default": [], + "items": { + "$ref": "#/definitions/assetPattern" + } + }, + "main": { + "type": "string", + "description": "The full path for the main entry point to the app, relative to the current workspace." + }, + "polyfills": { + "description": "Polyfills to be included in the build.", + "oneOf": [ + { + "type": "array", + "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + { + "type": "string", + "description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'." + } + ] + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, + "scripts": { + "description": "Global scripts to be included in the build.", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + } + ] + } + }, + "styles": { + "description": "Global styles to be included in the build.", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less)$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less)$" + } + ] + } + }, + "inlineStyleLanguage": { + "description": "The stylesheet language to use for the application's inline component styles.", + "type": "string", + "default": "css", + "enum": ["css", "less", "sass", "scss"] + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false + }, + "optimization": { + "description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.", + "default": true, + "x-user-analytics": "ep.ng_optimization", + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Enables optimization of the scripts output.", + "default": true + }, + "styles": { + "description": "Enables optimization of the styles output.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "minify": { + "type": "boolean", + "description": "Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers and minimizing values.", + "default": true + }, + "inlineCritical": { + "type": "boolean", + "description": "Extract and inline critical CSS definitions to improve first paint time.", + "default": true + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "fonts": { + "description": "Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "inline": { + "type": "boolean", + "description": "Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.", + "default": true + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "fileReplacements": { + "description": "Replace compilation source files with other compilation source files in the build.", + "type": "array", + "items": { + "$ref": "#/definitions/fileReplacement" + }, + "default": [] + }, + "outputPath": { + "type": "string", + "description": "The full path for the new output directory, relative to the current workspace.\nBy default, writes output to a folder named dist/ in the current project." + }, + "resourcesOutputPath": { + "type": "string", + "description": "The path where style resources will be placed, relative to outputPath." + }, + "aot": { + "type": "boolean", + "description": "Build using Ahead of Time compilation.", + "x-user-analytics": "ep.ng_aot", + "default": true + }, + "sourceMap": { + "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", + "default": false, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output source maps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output source maps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output source maps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages source maps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "vendorChunk": { + "type": "boolean", + "description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.", + "default": false + }, + "commonChunk": { + "type": "boolean", + "description": "Generate a seperate bundle containing code used across multiple bundles.", + "default": true + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + }, + "deployUrl": { + "type": "string", + "description": "URL where files will be deployed.", + "x-deprecated": "Use \"baseHref\" option, \"APP_BASE_HREF\" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url." + }, + "verbose": { + "type": "boolean", + "description": "Adds more details to output logging.", + "default": false + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console while building.", + "default": true + }, + "i18nMissingTranslation": { + "type": "string", + "description": "How to handle missing translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "i18nDuplicateTranslation": { + "type": "string", + "description": "How to handle duplicate translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "localize": { + "description": "Translate the bundles in one or more locales.", + "oneOf": [ + { + "type": "boolean", + "description": "Translate all locales." + }, + { + "type": "array", + "description": "List of locales ID's to translate.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$" + } + } + ] + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + }, + "outputHashing": { + "type": "string", + "description": "Define the output filename cache-busting hashing mode.", + "default": "none", + "enum": ["none", "all", "media", "bundles"] + }, + "poll": { + "type": "number", + "description": "Enable and define the file watching poll time period in milliseconds." + }, + "deleteOutputPath": { + "type": "boolean", + "description": "Delete the output path before building.", + "default": true + }, + "preserveSymlinks": { + "type": "boolean", + "description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set." + }, + "extractLicenses": { + "type": "boolean", + "description": "Extract all licenses in a separate file.", + "default": true + }, + "buildOptimizer": { + "type": "boolean", + "description": "Enables advanced build optimizations when using the 'aot' option.", + "default": true + }, + "namedChunks": { + "type": "boolean", + "description": "Use file name for lazy loaded chunks.", + "default": false + }, + "subresourceIntegrity": { + "type": "boolean", + "description": "Enables the use of subresource integrity validation.", + "default": false + }, + "serviceWorker": { + "type": "boolean", + "description": "Generates a service worker config for production builds.", + "default": false + }, + "ngswConfigPath": { + "type": "string", + "description": "Path to ngsw-config.json." + }, + "index": { + "description": "Configures the generation of the application's HTML index.", + "oneOf": [ + { + "type": "string", + "description": "The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path." + }, + { + "type": "object", + "description": "", + "properties": { + "input": { + "type": "string", + "minLength": 1, + "description": "The path of a file to use for the application's generated HTML index." + }, + "output": { + "type": "string", + "minLength": 1, + "default": "index.html", + "description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path." + } + }, + "required": ["input"] + } + ] + }, + "statsJson": { + "type": "boolean", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", + "default": false + }, + "budgets": { + "description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.", + "type": "array", + "items": { + "$ref": "#/definitions/budget" + }, + "default": [] + }, + "webWorkerTsConfig": { + "type": "string", + "description": "TypeScript configuration for Web Worker modules." + }, + "crossOrigin": { + "type": "string", + "description": "Define the crossorigin attribute setting of elements that provide CORS support.", + "default": "none", + "enum": ["none", "anonymous", "use-credentials"] + }, + "allowedCommonJsDependencies": { + "description": "A list of CommonJS packages that are allowed to be used without a build time warning.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false, + "required": ["outputPath", "index", "main", "tsConfig"], + "definitions": { + "assetPattern": { + "oneOf": [ + { + "type": "object", + "properties": { + "followSymlinks": { + "type": "boolean", + "default": false, + "description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched." + }, + "glob": { + "type": "string", + "description": "The pattern to match." + }, + "input": { + "type": "string", + "description": "The input directory path in which to apply 'glob'. Defaults to the project root." + }, + "ignore": { + "description": "An array of globs to ignore.", + "type": "array", + "items": { + "type": "string" + } + }, + "output": { + "type": "string", + "description": "Absolute path within the output." + } + }, + "additionalProperties": false, + "required": ["glob", "input", "output"] + }, + { + "type": "string" + } + ] + }, + "fileReplacement": { + "oneOf": [ + { + "type": "object", + "properties": { + "src": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + }, + "replaceWith": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + } + }, + "additionalProperties": false, + "required": ["src", "replaceWith"] + }, + { + "type": "object", + "properties": { + "replace": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + }, + "with": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + } + }, + "additionalProperties": false, + "required": ["replace", "with"] + } + ] + }, + "budget": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of budget.", + "enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"] + }, + "name": { + "type": "string", + "description": "The name of the bundle." + }, + "baseline": { + "type": "string", + "description": "The baseline size for comparison." + }, + "maximumWarning": { + "type": "string", + "description": "The maximum threshold for warning relative to the baseline." + }, + "maximumError": { + "type": "string", + "description": "The maximum threshold for error relative to the baseline." + }, + "minimumWarning": { + "type": "string", + "description": "The minimum threshold for warning relative to the baseline." + }, + "minimumError": { + "type": "string", + "description": "The minimum threshold for error relative to the baseline." + }, + "warning": { + "type": "string", + "description": "The threshold for warning relative to the baseline (min & max)." + }, + "error": { + "type": "string", + "description": "The threshold for error relative to the baseline (min & max)." + } + }, + "additionalProperties": false, + "required": ["type"] + } + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/allow-js_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/allow-js_spec.ts new file mode 100644 index 000000000000..049b95ee8e42 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/allow-js_spec.ts @@ -0,0 +1,122 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; +import { join, normalize, relative, virtualFs } from '@angular-devkit/core'; +import { Observable } from 'rxjs'; +import { take, tap } from 'rxjs/operators'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder allow js', () => { + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + host.writeMultipleFiles({ + 'src/my-js-file.js': `console.log(1); export const a = 2;`, + 'src/main.ts': `import { a } from './my-js-file'; console.log(a);`, + }); + + host.replaceInFile( + 'tsconfig.json', + '"target": "es2022"', + '"target": "es2022", "allowJs": true', + ); + + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + + const content = virtualFs.fileBufferToString( + await host.read(join(normalize(output.outputPath), 'main.js')).toPromise(), + ); + + expect(content).toContain('const a = 2'); + + await run.stop(); + }); + + it('works with aot', async () => { + host.writeMultipleFiles({ + 'src/my-js-file.js': `console.log(1); export const a = 2;`, + 'src/main.ts': `import { a } from './my-js-file'; console.log(a);`, + }); + + host.replaceInFile( + 'tsconfig.json', + '"target": "es2022"', + '"target": "es2022", "allowJs": true', + ); + + const overrides = { aot: true }; + + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + + const content = virtualFs.fileBufferToString( + await host.read(join(normalize(output.outputPath), 'main.js')).toPromise(), + ); + + expect(content).toContain('const a = 2'); + + await run.stop(); + }); + + it('works with watch', async () => { + host.writeMultipleFiles({ + 'src/my-js-file.js': `console.log(1); export const a = 2;`, + 'src/main.ts': `import { a } from './my-js-file'; console.log(a);`, + }); + + host.replaceInFile( + 'tsconfig.json', + '"target": "es2022"', + '"target": "es2022", "allowJs": true', + ); + + const overrides = { watch: true }; + + let buildCount = 1; + const run = await architect.scheduleTarget(targetSpec, overrides); + + await (run.output as Observable) + .pipe( + tap((output) => { + const path = relative(host.root(), join(normalize(output.outputPath), 'main.js')); + const content = virtualFs.fileBufferToString(host.scopedSync().read(path)); + + switch (buildCount) { + case 1: + expect(content).toContain('const a = 2'); + host.writeMultipleFiles({ + 'src/my-js-file.js': `console.log(1); export const a = 1;`, + }); + break; + case 2: + expect(content).toContain('const a = 1'); + break; + } + + buildCount++; + }), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + take(2), + ) + .toPromise(); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/aot_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/aot_spec.ts similarity index 78% rename from packages/angular_devkit/build_angular/src/browser/specs/aot_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/aot_spec.ts index 39f955a82507..ae6b620aa37c 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/aot_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/aot_spec.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -9,7 +9,7 @@ import { Architect } from '@angular-devkit/architect'; import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; import { join, logging, normalize, virtualFs } from '@angular-devkit/core'; -import { createArchitect, host } from '../../test-utils'; +import { createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder AOT', () => { const targetSpec = { project: 'app', target: 'build' }; @@ -27,11 +27,11 @@ describe('Browser Builder AOT', () => { const run = await architect.scheduleTarget(targetSpec, overrides); const output = (await run.result) as BrowserBuilderOutput; - expect(output.success).toBe(true); + expect(output.success).toBeTrue(); - const fileName = join(normalize(output.outputPath), 'main.js'); + const fileName = join(normalize(output.outputs[0].path), 'main.js'); const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); - expect(content).toContain('AppComponent.ɵcmp'); + expect(content).toContain('AppComponent_Factory'); await run.stop(); }); @@ -41,11 +41,7 @@ describe('Browser Builder AOT', () => { aot: true, }; - host.replaceInFile( - 'src/app/app.component.ts', - 'app.component.css', - 'app.component.scss', - ); + host.replaceInFile('src/app/app.component.ts', 'app.component.css', 'app.component.scss'); host.writeMultipleFiles({ 'src/app/app.component.scss': ` @@ -55,7 +51,7 @@ describe('Browser Builder AOT', () => { const logger = new logging.Logger(''); const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); + logger.subscribe((e) => logs.push(e.message)); const run = await architect.scheduleTarget(targetSpec, overrides, { logger }); const output = await run.result; diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/base-href_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/base-href_spec.ts new file mode 100644 index 000000000000..536ea0c5ae16 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/base-href_spec.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; +import { join, normalize, tags, virtualFs } from '@angular-devkit/core'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder base href', () => { + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + host.writeMultipleFiles({ + 'src/my-js-file.js': `console.log(1); export const a = 2;`, + 'src/main.ts': `import { a } from './my-js-file'; console.log(a);`, + }); + + const overrides = { baseHref: '/myUrl' }; + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(fileName).toPromise()); + expect(content).toMatch(//); + + await run.stop(); + }); + + it('should not override base href in HTML when option is not set', async () => { + host.writeMultipleFiles({ + 'src/index.html': ` + + + + + `, + }); + + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as BrowserBuilderOutput; + + expect(output.success).toBeTrue(); + const fileName = join(normalize(output.outputs[0].path), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(fileName).toPromise()); + expect(content).toContain(``); + + await run.stop(); + }); + + it('should insert base href in the the correct position', async () => { + host.writeMultipleFiles({ + 'src/index.html': tags.oneLine` + + + `, + }); + + const overrides = { baseHref: '/myUrl' }; + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toContain(''); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/build-optimizer_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/build-optimizer_spec.ts new file mode 100644 index 000000000000..4c5147f53b5c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/build-optimizer_spec.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; +import { join, normalize } from '@angular-devkit/core'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder build optimizer', () => { + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + const overrides = { aot: true, buildOptimizer: true }; + const { files } = await browserBuild(architect, host, targetSpec, overrides); + expect(await files['main.js']).not.toMatch(/\.decorators =/); + }); + + it('fails if AOT is disabled', async () => { + const overrides = { aot: false, buildOptimizer: true }; + const run = await architect.scheduleTarget(targetSpec, overrides); + await expectAsync(run.result).toBeRejectedWithError(); + await run.stop(); + }); + + it('reduces bundle size', async () => { + const noBoOverrides = { aot: true, optimization: true, vendorChunk: false }; + const boOverrides = { ...noBoOverrides, buildOptimizer: true }; + + const run = await architect.scheduleTarget(targetSpec, noBoOverrides); + const output = (await run.result) as BrowserBuilderOutput; + + expect(output.success).toBe(true); + + const noBoStats = await host.stat(join(normalize(output.outputPath), 'main.js')).toPromise(); + if (!noBoStats) { + throw new Error('Main file has no stats'); + } + const noBoSize = noBoStats.size; + await run.stop(); + + const boRun = await architect.scheduleTarget(targetSpec, boOverrides); + const boOutput = (await run.result) as BrowserBuilderOutput; + expect(boOutput.success).toBe(true); + + const boStats = await host.stat(join(normalize(output.outputPath), 'main.js')).toPromise(); + if (!boStats) { + throw new Error('Main file has no stats'); + } + const boSize = boStats.size; + await boRun.stop(); + + const sizeDiff = Math.round(((boSize - noBoSize) / noBoSize) * 10000) / 100; + if (sizeDiff > -1 && sizeDiff < 0) { + throw new Error( + 'Total size difference is too small, ' + + 'build optimizer does not seem to have made any optimizations.', + ); + } + + if (sizeDiff > 1) { + throw new Error( + 'Total size difference is positive, ' + 'build optimizer made the bundle bigger.', + ); + } + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/cross-origin_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/cross-origin_spec.ts new file mode 100644 index 000000000000..5fcdd4e2fece --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/cross-origin_spec.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { BrowserBuilderOutput, CrossOrigin } from '@angular-devkit/build-angular'; +import { join, normalize, virtualFs } from '@angular-devkit/core'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder crossOrigin', () => { + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + + host.writeMultipleFiles({ + 'src/index.html': Buffer.from( + '\ufeff', + 'utf8', + ), + }); + }); + + afterEach(async () => host.restore().toPromise()); + + it('works with use-credentials', async () => { + const overrides = { crossOrigin: CrossOrigin.UseCredentials }; + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `` + + `` + + `` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('works with anonymous', async () => { + const overrides = { crossOrigin: CrossOrigin.Anonymous }; + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `` + + `` + + `` + + `` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('works with none', async () => { + const overrides = { crossOrigin: CrossOrigin.None }; + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `` + + `` + + `` + + `` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('works for lazy chunks', async () => { + host.writeMultipleFiles({ + 'src/lazy-module.ts': 'export const value = 100;', + 'src/main.ts': `import('./lazy-module');`, + }); + + const overrides = { crossOrigin: CrossOrigin.UseCredentials }; + const run = await architect.scheduleTarget(targetSpec, overrides); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + + const fileName = join(normalize(output.outputPath), 'runtime.js'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toContain('script.crossOrigin = "use-credentials"'); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/deploy-url_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/deploy-url_spec.ts similarity index 86% rename from packages/angular_devkit/build_angular/src/browser/specs/deploy-url_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/deploy-url_spec.ts index 550dd8d3d8e1..c40874adb534 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/deploy-url_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/deploy-url_spec.ts @@ -1,15 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect } from '@angular-devkit/architect'; import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; import { join, normalize, virtualFs } from '@angular-devkit/core'; -import { createArchitect, host } from '../../test-utils'; - +import { createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder deploy url', () => { const targetSpec = { project: 'app', target: 'build' }; @@ -33,7 +33,7 @@ describe('Browser Builder deploy url', () => { }); const run = await architect.scheduleTarget(targetSpec, overrides); - const output = await run.result as BrowserBuilderOutput; + const output = (await run.result) as BrowserBuilderOutput; expect(output.success).toBe(true); expect(output.outputPath).not.toBeUndefined(); const outputPath = normalize(output.outputPath); @@ -48,8 +48,8 @@ describe('Browser Builder deploy url', () => { expect(runtimeContent).toContain('deployUrl/'); const run2 = await architect.scheduleTarget(targetSpec, overrides2); - const output2 = await run2.result as BrowserBuilderOutput; - expect(output2.outputPath).toEqual(outputPath); // These should be the same. + const output2 = (await run2.result) as BrowserBuilderOutput; + expect(output2.outputPath).toEqual(outputPath); // These should be the same. const content2 = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); expect(content2).toContain('http://example.com/some/path/main.js'); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/errors_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/errors_spec.ts new file mode 100644 index 000000000000..7a627392311f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/errors_spec.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { logging } from '@angular-devkit/core'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder errors', () => { + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('shows error when files are not part of the compilation', async () => { + host.replaceInFile('src/tsconfig.app.json', /,\r?\n?\s*"polyfills\.ts"/, ''); + host.replaceInFile('src/tsconfig.app.json', '"**/*.ts"', '"**/*.d.ts"'); + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, {}, { logger }); + const output = await run.result; + expect(output.success).toBe(false); + expect(logs.join()).toContain('polyfills.ts is missing from the TypeScript'); + await run.stop(); + }); + + it('shows TS syntax errors', async () => { + host.appendToFile('src/app/app.component.ts', ']]]'); + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, {}, { logger }); + const output = await run.result; + expect(output.success).toBe(false); + expect(logs.join()).toContain('Declaration or statement expected'); + await run.stop(); + }); + + it('shows static analysis errors', async () => { + host.replaceInFile('src/app/app.component.ts', `'app-root'`, `(() => 'app-root')()`); + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, { aot: true }, { logger }); + const output = await run.result; + expect(output.success).toBe(false); + + // Wait for the builder to complete + await run.stop(); + + expect(logs.join()).toContain('selector must be a string'); + }); + + it('shows missing export errors', async () => { + host.writeMultipleFiles({ + 'src/not-main.js': ` + import { missingExport } from 'rxjs'; + console.log(missingExport); + `, + }); + const overrides = { main: 'src/not-main.js' }; + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, overrides, { logger }); + const output = await run.result; + expect(output.success).toBe(false); + + // Wait for the builder to complete + await run.stop(); + + expect(logs.join()).toContain( + `export 'missingExport' (imported as 'missingExport') was not found in 'rxjs'`, + ); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/font-optimization_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/font-optimization_spec.ts similarity index 79% rename from packages/angular_devkit/build_angular/src/browser/specs/font-optimization_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/font-optimization_spec.ts index 8075059db024..2c9a32f62a93 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/font-optimization_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/font-optimization_spec.ts @@ -1,12 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder font optimization', () => { const target = { project: 'app', target: 'build' }; @@ -39,24 +40,13 @@ describe('Browser Builder font optimization', () => { expect(html).toContain(`font-family: 'Roboto'`); }); - it('should not add woff when IE support is not needed', async () => { + it('should not add woff', async () => { const { files } = await browserBuild(architect, host, target, overrides); const html = await files['index.html']; expect(html).toContain(`format('woff2');`); expect(html).not.toContain(`format('woff');`); }); - it('should add woff when IE support is needed', async () => { - host.writeMultipleFiles({ - '.browserslistrc': 'IE 11', - }); - - const { files } = await browserBuild(architect, host, target, overrides); - const html = await files['index.html']; - expect(html).toContain(`format('woff2');`); - expect(html).toContain(`format('woff');`); - }); - it('should remove comments and line breaks when styles optimization is true', async () => { const { files } = await browserBuild(architect, host, target, { optimization: { diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/index_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/index_spec.ts new file mode 100644 index 000000000000..eee3427740cf --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/index_spec.ts @@ -0,0 +1,255 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; +import { join, normalize, tags, virtualFs, workspaces } from '@angular-devkit/core'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder index HTML processing', () => { + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works with UTF-8 BOM', async () => { + host.writeMultipleFiles({ + 'src/index.html': Buffer.from( + '\ufeff', + 'utf8', + ), + }); + + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + // todo: enable when utf16 is supported + xit('works with UTF16 LE BOM', async () => { + host.writeMultipleFiles({ + 'src/index.html': Buffer.from( + '\ufeff', + 'utf16le', + ), + }); + + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `` + + `` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('keeps escaped charaters', async () => { + host.writeMultipleFiles({ + 'src/index.html': tags.oneLine` + í + + `, + }); + + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `í ` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('keeps custom template charaters', async () => { + host.writeMultipleFiles({ + 'src/index.html': tags.oneLine` + <%= csrf_meta_tags %> + + `, + }); + + const run = await architect.scheduleTarget(targetSpec); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + const fileName = join(normalize(output.outputPath), 'index.html'); + const content = virtualFs.fileBufferToString(await host.read(normalize(fileName)).toPromise()); + expect(content).toBe( + `<%= csrf_meta_tags %> ` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('uses the input value from the index option longform', async () => { + const { workspace } = await workspaces.readWorkspace( + host.root(), + workspaces.createWorkspaceHost(host), + ); + const app = workspace.projects.get('app'); + if (!app) { + fail('Test application "app" not found.'); + + return; + } + const target = app.targets.get('build'); + if (!target) { + fail('Test application "app" target "build" not found.'); + + return; + } + if (!target.options) { + target.options = {}; + } + target.options.index = { input: 'src/index-2.html' }; + await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); + + host.writeMultipleFiles({ + 'src/index-2.html': tags.oneLine` + <%= csrf_meta_tags %> + + `, + }); + await host.delete(join(host.root(), normalize('src/index.html'))).toPromise(); + + // Recreate architect to use update angular.json + architect = (await createArchitect(host.root())).architect; + + const run = await architect.scheduleTarget(targetSpec); + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + const outputIndexPath = join(host.root(), 'dist', 'index.html'); + const content = await host.read(normalize(outputIndexPath)).toPromise(); + expect(virtualFs.fileBufferToString(content)).toBe( + `<%= csrf_meta_tags %> ` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('uses the output value from the index option longform', async () => { + const { workspace } = await workspaces.readWorkspace( + host.root(), + workspaces.createWorkspaceHost(host), + ); + const app = workspace.projects.get('app'); + if (!app) { + fail('Test application "app" not found.'); + + return; + } + const target = app.targets.get('build'); + if (!target) { + fail('Test application "app" target "build" not found.'); + + return; + } + if (!target.options) { + target.options = {}; + } + target.options.index = { input: 'src/index.html', output: 'main.html' }; + await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); + + host.writeMultipleFiles({ + 'src/index.html': tags.oneLine` + + + `, + }); + + // Recreate architect to use update angular.json + architect = (await createArchitect(host.root())).architect; + + const run = await architect.scheduleTarget(targetSpec); + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + const outputIndexPath = join(host.root(), 'dist', 'main.html'); + const content = await host.read(normalize(outputIndexPath)).toPromise(); + expect(virtualFs.fileBufferToString(content)).toBe( + ` ` + + `` + + `` + + ``, + ); + await run.stop(); + }); + + it('creates subdirectories for output value from the index option longform', async () => { + const { workspace } = await workspaces.readWorkspace( + host.root(), + workspaces.createWorkspaceHost(host), + ); + const app = workspace.projects.get('app'); + if (!app) { + fail('Test application "app" not found.'); + + return; + } + const target = app.targets.get('build'); + if (!target) { + fail('Test application "app" target "build" not found.'); + + return; + } + if (!target.options) { + target.options = {}; + } + target.options.index = { input: 'src/index.html', output: 'extra/main.html' }; + await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); + + host.writeMultipleFiles({ + 'src/index.html': tags.oneLine` + + + `, + }); + + // Recreate architect to use update angular.json + architect = (await createArchitect(host.root())).architect; + + const run = await architect.scheduleTarget(targetSpec); + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + const outputIndexPath = join(host.root(), 'dist', 'extra', 'main.html'); + const content = await host.read(normalize(outputIndexPath)).toPromise(); + expect(virtualFs.fileBufferToString(content)).toBe( + ` ` + + `` + + `` + + ``, + ); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/lazy-module_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/lazy-module_spec.ts similarity index 75% rename from packages/angular_devkit/build_angular/src/browser/specs/lazy-module_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/lazy-module_spec.ts index 9a30f156e516..fa0f7c381b1a 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/lazy-module_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/lazy-module_spec.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -16,9 +16,8 @@ import { host, lazyModuleFiles, lazyModuleFnImport, -} from '../../test-utils'; +} from '../../../testing/test-utils'; -// tslint:disable-next-line:no-big-function describe('Browser Builder lazy modules', () => { const target = { project: 'app', target: 'build' }; let architect: Architect; @@ -29,27 +28,25 @@ describe('Browser Builder lazy modules', () => { }); afterEach(async () => host.restore().toPromise()); - function addLazyLoadedModulesInTsConfig(host: TestProjectHost, lazyModuleFiles: Record) { - const files = [ - ...Object.keys(lazyModuleFiles), - 'main.ts', - ] - .map(f => '"' + f.replace('src/', '') + '"') - .join(', '); - - host.replaceInFile( - 'src/tsconfig.app.json', - '"main.ts"', - `${files}`, - ); + function addLazyLoadedModulesInTsConfig( + host: TestProjectHost, + lazyModuleFiles: Record, + ) { + const files = [...Object.keys(lazyModuleFiles), 'main.ts'] + .map((f) => '"' + f.replace('src/', '') + '"') + .join(', '); + + host.replaceInFile('src/tsconfig.app.json', '"main.ts"', `${files}`); } function hasMissingModuleError(logs: string): boolean { // TS type error when using import(). - return logs.includes('Cannot find module') || + return ( + logs.includes('Cannot find module') || // Webpack error when using import() on a rebuild. // There is no TS error because the type checker is forked on rebuilds. - logs.includes('Module not found'); + logs.includes('Module not found') + ); } describe(`Load children syntax`, () => { @@ -68,8 +65,7 @@ describe('Browser Builder lazy modules', () => { const { files } = await browserBuild(architect, host, target, { aot: true }); const data = await files['src_app_lazy_lazy_module_ts.js']; - expect(data).not.toBeUndefined('Lazy module output bundle does not exist'); - expect(data).toContain('LazyModule.ɵmod'); + expect(data).toContain('this.ɵmod'); }); }); @@ -83,12 +79,13 @@ describe('Browser Builder lazy modules', () => { const logger = new logging.Logger(''); const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); + logger.subscribe((e) => logs.push(e.message)); const run = await architect.scheduleTarget(target, {}, { logger }); const output = await run.result; expect(output.success).toBe(false); - expect(hasMissingModuleError(logs.join())).toBe(true, 'Should show missing module error'); + expect(hasMissingModuleError(logs.join())).toBeTrue(); + await run.stop(); }); it('should show error when lazy route is invalid on watch mode AOT', async () => { @@ -101,8 +98,8 @@ describe('Browser Builder lazy modules', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( - debounceTime(3000), - tap(buildEvent => { + debounceTime(1500), + tap((buildEvent) => { buildNumber++; switch (buildNumber) { case 1: @@ -128,7 +125,7 @@ describe('Browser Builder lazy modules', () => { }); const { files } = await browserBuild(architect, host, target); - expect(files['src_lazy-module_ts.js']).not.toBeUndefined(); + expect(files['src_lazy-module_ts.js']).toBeDefined(); }); it(`supports lazy bundle for dynamic import() calls`, async () => { @@ -139,14 +136,10 @@ describe('Browser Builder lazy modules', () => { import(/*webpackChunkName: '[request]'*/'./lazy-' + lazyFileName); `, }); - host.replaceInFile( - 'src/tsconfig.app.json', - '"main.ts"', - `"main.ts","lazy-module.ts"`, - ); + host.replaceInFile('src/tsconfig.app.json', '"main.ts"', `"main.ts","lazy-module.ts"`); const { files } = await browserBuild(architect, host, target); - expect(files['lazy-module.js']).not.toBeUndefined(); + expect(files['lazy-module.js']).toBeDefined(); }); it(`supports making a common bundle for shared lazy modules`, async () => { @@ -157,9 +150,9 @@ describe('Browser Builder lazy modules', () => { }); const { files } = await browserBuild(architect, host, target); - expect(files['src_one_ts.js']).not.toBeUndefined(); - expect(files['src_two_ts.js']).not.toBeUndefined(); - expect(files['default-node_modules_angular_common___ivy_ngcc___fesm2015_http_js.js']).toBeDefined(); + expect(files['src_one_ts.js']).toBeDefined(); + expect(files['src_two_ts.js']).toBeDefined(); + expect(files['default-node_modules_angular_common_fesm2020_http_mjs.js']).toBeDefined(); }); it(`supports disabling the common bundle`, async () => { @@ -170,8 +163,8 @@ describe('Browser Builder lazy modules', () => { }); const { files } = await browserBuild(architect, host, target, { commonChunk: false }); - expect(files['src_one_ts.js']).not.toBeUndefined(); - expect(files['src_two_ts.js']).not.toBeUndefined(); - expect(files['default-node_modules_angular_common___ivy_ngcc___fesm2015_http_js.js']).toBeUndefined(); + expect(files['src_one_ts.js']).toBeDefined(); + expect(files['src_two_ts.js']).toBeDefined(); + expect(files['default-node_modules_angular_common_fesm2020_http_mjs.js']).toBeUndefined(); }); }); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/no-entry-module_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/no-entry-module_spec.ts similarity index 87% rename from packages/angular_devkit/build_angular/src/browser/specs/no-entry-module_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/no-entry-module_spec.ts index 50120ecdbd1a..331f18296e8a 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/no-entry-module_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/no-entry-module_spec.ts @@ -1,13 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder no entry module', () => { const target = { project: 'app', target: 'build' }; diff --git a/packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/optimization-level_spec.ts similarity index 83% rename from packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/optimization-level_spec.ts index 8936edee2329..f5450529db01 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/optimization-level_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/optimization-level_spec.ts @@ -1,13 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; +import { Architect } from '@angular-devkit/architect'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder optimization level', () => { const target = { project: 'app', target: 'build' }; @@ -26,14 +26,6 @@ describe('Browser Builder optimization level', () => { expect(await files['main.js']).not.toContain('AppComponent'); }); - it('tsconfig target changes optimizations to use es2017', async () => { - host.replaceInFile('tsconfig.json', '"target": "es5"', '"target": "es2017"'); - - const overrides = { optimization: true }; - const { files } = await browserBuild(architect, host, target, overrides); - expect(await files['vendor.js']).toMatch(/class \w{constructor\(\){/); - }); - it('supports styles only optimizations', async () => { const overrides = { optimization: { @@ -41,7 +33,6 @@ describe('Browser Builder optimization level', () => { scripts: false, }, aot: true, - extractCss: true, styles: ['src/styles.css'], }; @@ -64,7 +55,6 @@ describe('Browser Builder optimization level', () => { scripts: true, }, aot: true, - extractCss: true, styles: ['src/styles.css'], }; diff --git a/packages/angular_devkit/build_angular/src/browser/specs/output-path_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/output-path_spec.ts similarity index 91% rename from packages/angular_devkit/build_angular/src/browser/specs/output-path_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/output-path_spec.ts index beb600d77adc..6686d0d2b874 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/output-path_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/output-path_spec.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -9,8 +9,7 @@ import { Architect } from '@angular-devkit/architect'; import { getSystemPath, join, virtualFs } from '@angular-devkit/core'; import * as fs from 'fs'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder output path', () => { const target = { project: 'app', target: 'build' }; @@ -24,10 +23,9 @@ describe('Browser Builder output path', () => { it('deletes output path', async () => { // Write a file to the output path to later verify it was deleted. - await host.write( - join(host.root(), 'dist/file.txt'), - virtualFs.stringToFileBuffer('file'), - ).toPromise(); + await host + .write(join(host.root(), 'dist/file.txt'), virtualFs.stringToFileBuffer('file')) + .toPromise(); // Delete an app file to force a failed compilation. // Failed compilations still delete files, but don't output any. @@ -67,6 +65,7 @@ describe('Browser Builder output path', () => { expect(await host.exists(join(host.root(), 'dist')).toPromise()).toBe(false); expect(await host.exists(join(host.root(), 'src-link')).toPromise()).toBe(true); + await run.stop(); }); it('does not allow output path to be project root', async () => { @@ -75,7 +74,7 @@ describe('Browser Builder output path', () => { try { await run.result; expect('THE ABOVE LINE SHOULD THROW').toBe(''); - } catch { } + } catch {} await run.stop(); }); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/poll_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/poll_spec.ts new file mode 100644 index 000000000000..74ed6da46d43 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/poll_spec.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { debounceTime, take, tap, timeout } from 'rxjs/operators'; +import { createArchitect, host } from '../../../testing/test-utils'; +import { BUILD_TIMEOUT } from '../index'; + +describe('Browser Builder poll', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + const overrides = { watch: true, poll: 4000 }; + const intervals: number[] = []; + let startTime: number | undefined; + + const run = await architect.scheduleTarget(target, overrides); + await run.output + .pipe( + timeout(BUILD_TIMEOUT), + // Debounce 1s, otherwise changes are too close together and polling doesn't work. + debounceTime(1000), + tap((buildEvent) => { + expect(buildEvent.success).toBe(true); + if (startTime != undefined) { + intervals.push(Date.now() - startTime - 1000); + } + startTime = Date.now(); + host.appendToFile('src/main.ts', 'console.log(1);'); + }), + take(4), + ) + .toPromise(); + + intervals.sort(); + const median = intervals[Math.trunc(intervals.length / 2)]; + expect(median).toBeGreaterThan(2950); + expect(median).toBeLessThan(12000); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/rebuild_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/rebuild_spec.ts similarity index 85% rename from packages/angular_devkit/build_angular/src/browser/specs/rebuild_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/rebuild_spec.ts index e597807133c7..3fd1698e08d2 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/rebuild_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/rebuild_spec.ts @@ -1,21 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-big-function + import { Architect } from '@angular-devkit/architect'; import { join, logging, normalize, virtualFs } from '@angular-devkit/core'; -import { debounceTime, take, takeWhile, tap } from 'rxjs/operators'; +import { debounceTime, take, takeWhile, tap, timeout } from 'rxjs/operators'; import { createArchitect, host, lazyModuleFiles, lazyModuleFnImport, outputPath, -} from '../../test-utils'; +} from '../../../testing/test-utils'; +import { BUILD_TIMEOUT } from '../index'; describe('Browser Builder rebuilds', () => { const target = { project: 'app', target: 'build' }; @@ -54,15 +55,8 @@ describe('Browser Builder rebuilds', () => { export let X = '$$_E2E_GOLDEN_VALUE_2'; `, 'src/main.ts': ` - import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - import { AppModule } from './app/app.module'; - import { environment } from './environments/environment'; - - if (environment.production) { - enableProdMode(); - } platformBrowserDynamic().bootstrapModule(AppModule); @@ -78,10 +72,13 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(result => { - expect(result.success).toBe(true, 'build should succeed'); - const hasLazyChunk = host.scopedSync().exists(normalize('dist/src_app_lazy_lazy_module_ts.js')); + tap((result) => { + expect(result.success).toBeTrue(); + const hasLazyChunk = host + .scopedSync() + .exists(normalize('dist/src_app_lazy_lazy_module_ts.js')); switch (phase) { case 1: // No lazy chunk should exist. @@ -129,8 +126,9 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => expect(buildEvent.success).toBe(true)), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), tap(() => host.appendToFile('src/app/app.component.css', ':host { color: blue; }')), take(2), ) @@ -151,10 +149,10 @@ describe('Browser Builder rebuilds', () => { `, ); - const overrides = { watch: true, forkTypeChecker: false }; + const overrides = { watch: true }; const logger = new logging.Logger(''); let logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); + logger.subscribe((e) => logs.push(e.message)); const typeError = `is not assignable to parameter of type 'number'`; let buildNumber = 0; @@ -162,8 +160,9 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides, { logger }); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => { + tap((buildEvent) => { buildNumber += 1; switch (buildNumber) { case 1: @@ -215,12 +214,14 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => expect(buildEvent.success).toBe(true)), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), tap(() => host.writeMultipleFiles({ 'src/type.ts': `export type MyType = string;` })), take(2), ) .toPromise(); + await run.stop(); }); it('rebuilds on transitive type-only file changes', async () => { @@ -235,18 +236,22 @@ describe('Browser Builder rebuilds', () => { `, 'src/interface3.ts': `export interface Interface3 { nbr: number; }`, }); - host.appendToFile('src/main.ts', ` + host.appendToFile( + 'src/main.ts', + ` import { Interface1 } from './interface1'; const something: Interface1 = { nbr: 43 }; - `); + `, + ); const overrides = { watch: true }; const run = await architect.scheduleTarget(target, overrides); let buildNumber = 0; await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => expect(buildEvent.success).toBe(true)), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), tap(() => { // NOTE: this only works for transitive type deps after the first build, and only if the // typedep file was there on the previous build. @@ -261,6 +266,8 @@ describe('Browser Builder rebuilds', () => { take(5), ) .toPromise(); + + await run.stop(); }); it('rebuilds on transitive non node package DTS file changes', async () => { @@ -275,18 +282,22 @@ describe('Browser Builder rebuilds', () => { `, 'src/interface3.d.ts': `export interface Interface3 { nbr: number; }`, }); - host.appendToFile('src/main.ts', ` + host.appendToFile( + 'src/main.ts', + ` import { Interface1 } from './interface1'; const something: Interface1 = { nbr: 43 }; - `); + `, + ); const overrides = { watch: true }; const run = await architect.scheduleTarget(target, overrides); let buildNumber = 0; await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => expect(buildEvent.success).toBe(true)), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), tap(() => { buildNumber++; if (buildNumber === 1) { @@ -296,6 +307,7 @@ describe('Browser Builder rebuilds', () => { take(2), ) .toPromise(); + await run.stop(); }); it('rebuilds after errors in JIT', async () => { @@ -310,15 +322,16 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => { + tap((buildEvent) => { buildNumber++; switch (buildNumber) { case 1: // The first build should fail. expect(buildEvent.success).toBe(false); // Fix the error. - host.writeMultipleFiles({'src/app/app.component.ts': origContent}); + host.writeMultipleFiles({ 'src/app/app.component.ts': origContent }); break; case 2: @@ -344,10 +357,10 @@ describe('Browser Builder rebuilds', () => { // `selector must be a string` errors on VE are part of the emit result, but on Ivy they only // show up in getNgSemanticDiagnostics. Since getNgSemanticDiagnostics is only called on the // type checker, we must disable it to get a failing fourth build with Ivy. - const overrides = { watch: true, aot: true, forkTypeChecker: false }; + const overrides = { watch: true, aot: true }; const logger = new logging.Logger(''); let logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); + logger.subscribe((e) => logs.push(e.message)); const staticAnalysisError = 'selector must be a string'; const syntaxError = 'Declaration or statement expected.'; let buildNumber = 0; @@ -355,15 +368,18 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides, { logger }); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => { + tap((buildEvent) => { buildNumber += 1; switch (buildNumber) { case 1: // The first build should error out with a static analysis error. expect(buildEvent.success).toBe(false, 'First build should not succeed.'); - expect(logs.join().includes(staticAnalysisError)).toBe(true, - 'First build should have static analysis error.'); + expect(logs.join().includes(staticAnalysisError)).toBe( + true, + 'First build should have static analysis error.', + ); logs = []; // Fix the static analysis error. host.writeMultipleFiles({ 'src/app/app.component.ts': origContent }); @@ -371,8 +387,10 @@ describe('Browser Builder rebuilds', () => { case 2: expect(buildEvent.success).toBe(true, 'Second build should succeed.'); - expect(logs.join().includes(staticAnalysisError)).toBe(false, - 'Second build should not have static analysis error.'); + expect(logs.join().includes(staticAnalysisError)).toBe( + false, + 'Second build should not have static analysis error.', + ); logs = []; // Add an syntax error to a non-main file. host.appendToFile('src/app/app.component.ts', `]]]`); @@ -381,10 +399,14 @@ describe('Browser Builder rebuilds', () => { case 3: // The third build should have TS syntax error. expect(buildEvent.success).toBe(false, 'Third build should not succeed.'); - expect(logs.join().includes(syntaxError)).toBe(true, - 'Third build should have syntax analysis error.'); - expect(logs.join().includes(staticAnalysisError)).toBe(false, - 'Third build should not have static analysis error.'); + expect(logs.join().includes(syntaxError)).toBe( + true, + 'Third build should have syntax analysis error.', + ); + expect(logs.join().includes(staticAnalysisError)).toBe( + false, + 'Third build should not have static analysis error.', + ); logs = []; // Fix the syntax error, but add the static analysis error again. host.writeMultipleFiles({ @@ -397,10 +419,14 @@ describe('Browser Builder rebuilds', () => { case 4: expect(buildEvent.success).toBe(false, 'Fourth build should not succeed.'); - expect(logs.join().includes(syntaxError)).toBe(false, - 'Fourth build should not have syntax analysis error.'); - expect(logs.join().includes(staticAnalysisError)).toBe(true, - 'Fourth build should have static analysis error.'); + expect(logs.join().includes(syntaxError)).toBe( + false, + 'Fourth build should not have syntax analysis error.', + ); + expect(logs.join().includes(staticAnalysisError)).toBe( + true, + 'Fourth build should have static analysis error.', + ); logs = []; // Restore the file to a error-less state. host.writeMultipleFiles({ 'src/app/app.component.ts': origContent }); @@ -409,10 +435,14 @@ describe('Browser Builder rebuilds', () => { case 5: // The fifth build should have everything fixed.. expect(buildEvent.success).toBe(true, 'Fifth build should succeed.'); - expect(logs.join().includes(syntaxError)).toBe(false, - 'Fifth build should not have syntax analysis error.'); - expect(logs.join().includes(staticAnalysisError)).toBe(false, - 'Fifth build should not have static analysis error.'); + expect(logs.join().includes(syntaxError)).toBe( + false, + 'Fifth build should not have syntax analysis error.', + ); + expect(logs.join().includes(staticAnalysisError)).toBe( + false, + 'Fifth build should not have static analysis error.', + ); break; } }), @@ -437,8 +467,9 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => { + tap((buildEvent) => { buildNumber += 1; const fileName = './dist/main.js'; let content; @@ -527,8 +558,9 @@ describe('Browser Builder rebuilds', () => { const run = await architect.scheduleTarget(target, overrides); await run.output .pipe( + timeout(BUILD_TIMEOUT), debounceTime(rebuildDebounceTime), - tap(buildEvent => { + tap((buildEvent) => { buildNumber += 1; switch (buildNumber) { case 1: @@ -557,28 +589,30 @@ describe('Browser Builder rebuilds', () => { let buildCount = 1; const run = await architect.scheduleTarget(target, overrides); - await run.output.pipe( - debounceTime(rebuildDebounceTime), - tap(() => { - const content = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'main.js')), - ); - - switch (buildCount) { - case 1: + await run.output + .pipe( + debounceTime(rebuildDebounceTime), + tap(() => { + const content = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + + switch (buildCount) { + case 1: expect(content).not.toContain('color: green'); host.appendToFile('src/app/app.component.css', 'h1 { color: green; }'); break; - case 2: + case 2: expect(content).toContain('color: green'); break; - } + } - buildCount++; - }), - tap((buildEvent) => expect(buildEvent.success).toBe(true)), - take(2), - ).toPromise(); + buildCount++; + }), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + take(2), + ) + .toPromise(); await run.stop(); }); @@ -587,28 +621,31 @@ describe('Browser Builder rebuilds', () => { let buildCount = 1; const run = await architect.scheduleTarget(target, overrides); - await run.output.pipe( - debounceTime(rebuildDebounceTime), - tap(() => { - const content = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'main.js')), - ); - - switch (buildCount) { - case 1: + await run.output + .pipe( + timeout(BUILD_TIMEOUT), + debounceTime(rebuildDebounceTime), + tap(() => { + const content = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + + switch (buildCount) { + case 1: expect(content).not.toContain('New Updated Content'); host.appendToFile('src/app/app.component.html', 'New Updated Content'); break; - case 2: + case 2: expect(content).toContain('New Updated Content'); break; - } + } - buildCount++; - }), - tap((buildEvent) => expect(buildEvent.success).toBe(true)), - take(2), - ).toPromise(); + buildCount++; + }), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + take(2), + ) + .toPromise(); await run.stop(); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/replacements_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/replacements_spec.ts new file mode 100644 index 000000000000..39841f9aeb1f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/replacements_spec.ts @@ -0,0 +1,215 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { logging, normalize, virtualFs } from '@angular-devkit/core'; +import { of, race } from 'rxjs'; +import { delay, filter, map, take, takeUntil, takeWhile, tap, timeout } from 'rxjs/operators'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder file replacements', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + beforeEach(() => + host.writeMultipleFiles({ + 'src/meaning-too.ts': 'export var meaning = 42;', + 'src/meaning.ts': `export var meaning = 10;`, + + 'src/main.ts': ` + import { meaning } from './meaning'; + + console.log(meaning); + `, + }), + ); + + it('allows file replacements', async () => { + const overrides = { + fileReplacements: [ + { + replace: normalize('/src/meaning.ts'), + with: normalize('/src/meaning-too.ts'), + }, + ], + }; + + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['main.js']).toMatch(/meaning\s*=\s*42/); + expect(await files['main.js']).not.toMatch(/meaning\s*=\s*10/); + }); + + it(`allows file replacements with deprecated format`, async () => { + const overrides = { + fileReplacements: [ + { + src: normalize('/src/meaning.ts'), + replaceWith: normalize('/src/meaning-too.ts'), + }, + ], + }; + + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['main.js']).toMatch(/meaning\s*=\s*42/); + expect(await files['main.js']).not.toMatch(/meaning\s*=\s*10/); + }); + + it(`fails compilation with missing 'replace' file`, async () => { + const overrides = { + fileReplacements: [ + { + replace: normalize('/src/meaning.ts'), + with: normalize('/src/meaning-three.ts'), + }, + ], + }; + + const run = await architect.scheduleTarget(target, overrides); + await expectAsync(run.result).toBeRejectedWithError(); + await run.stop(); + }); + + it(`fails compilation with missing 'with' file`, async () => { + const overrides = { + fileReplacements: [ + { + replace: normalize('/src/meaning-three.ts'), + with: normalize('/src/meaning-too.ts'), + }, + ], + }; + + const run = await architect.scheduleTarget(target, overrides); + await expectAsync(run.result).toBeRejectedWithError(); + await run.stop(); + }); + + it('file replacements work with watch mode', async () => { + const overrides = { + fileReplacements: [ + { + replace: normalize('/src/meaning.ts'), + with: normalize('/src/meaning-too.ts'), + }, + ], + watch: true, + }; + + let buildCount = 0; + let phase = 1; + + const run = await architect.scheduleTarget(target, overrides); + await run.output + .pipe( + timeout(30000), + tap((result) => { + expect(result.success).toBe(true, 'build should succeed'); + + const fileName = normalize('dist/main.js'); + const content = virtualFs.fileBufferToString(host.scopedSync().read(fileName)); + const has42 = /meaning\s*=\s*42/.test(content); + buildCount++; + switch (phase) { + case 1: + const has10 = /meaning\s*=\s*10/.test(content); + + if (has42 && !has10) { + phase = 2; + host.writeMultipleFiles({ + 'src/meaning-too.ts': 'export var meaning = 84;', + }); + } + break; + + case 2: + const has84 = /meaning\s*=\s*84/.test(content); + + if (has84 && !has42) { + phase = 3; + } else { + // try triggering a rebuild again + host.writeMultipleFiles({ + 'src/meaning-too.ts': 'export var meaning = 84;', + }); + } + break; + } + }), + takeWhile(() => phase < 3), + ) + .toPromise() + .catch(() => { + throw new Error(`stuck at phase ${phase} [builds: ${buildCount}]`); + }); + + await run.stop(); + }); + + it('file replacements work with forked type checker on watch mode', async () => { + host.writeMultipleFiles({ + 'src/file-replaced.ts': 'export var obj = { one: 1, two: 2 };', + 'src/file.ts': `export var obj = { one: 1 };`, + 'src/main.ts': ` + import { obj } from './file'; + console.log(obj.two); + `, + }); + + const overrides = { + fileReplacements: [ + { + replace: normalize('/src/file.ts'), + with: normalize('/src/file-replaced.ts'), + }, + ], + watch: true, + }; + + const unexpectedError = `Property 'two' does not exist on type '{ one: number; }'`; + const expectedError = `Property 'prop' does not exist on type '{}'`; + + const logger = new logging.Logger(''); + + // Race between a timeout and the expected log entry. + const stop$ = race( + of(null).pipe(delay((45000 * 2) / 3)), + logger.pipe( + filter((entry) => entry.message.includes(expectedError)), + map((entry) => entry.message), + take(1), + ), + ); + + let errorAdded = false; + const run = await architect.scheduleTarget(target, overrides, { logger }); + run.output + .pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true, 'build should succeed')), + tap(() => { + // Introduce a known type error to detect in the logger filter. + if (!errorAdded) { + host.appendToFile('src/main.ts', 'console.log({}.prop);'); + errorAdded = true; + } + }), + takeUntil(stop$), + ) + .subscribe(); + + const res = await stop$.toPromise(); + expect(res).toBeDefined(); + expect(res).not.toContain(unexpectedError); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/resolve-json-module_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/resolve-json-module_spec.ts new file mode 100644 index 000000000000..c47fd03462e7 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/resolve-json-module_spec.ts @@ -0,0 +1,68 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { join, virtualFs } from '@angular-devkit/core'; +import { take, tap } from 'rxjs/operators'; +import { createArchitect, host, outputPath } from '../../../testing/test-utils'; + +describe('Browser Builder resolve json module', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works with watch', async () => { + host.writeMultipleFiles({ + 'src/my-json-file.json': `{"foo": "1"}`, + 'src/main.ts': `import * as a from './my-json-file.json'; console.log(a);`, + }); + + host.replaceInFile( + 'tsconfig.json', + '"target": "es2022"', + '"target": "es2022", "resolveJsonModule": true', + ); + + const overrides = { watch: true }; + + let buildCount = 1; + const run = await architect.scheduleTarget(target, overrides); + await run.output + .pipe( + tap(() => { + const content = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + + switch (buildCount) { + case 1: + expect(content).toContain('"foo":"1"'); + host.writeMultipleFiles({ + 'src/my-json-file.json': `{"foo": "2"}`, + }); + break; + case 2: + expect(content).toContain('"foo":"2"'); + break; + } + + buildCount++; + }), + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + take(2), + ) + .toPromise(); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/resources-output-path_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/resources-output-path_spec.ts new file mode 100644 index 000000000000..57a3cf93c8f7 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/resources-output-path_spec.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { normalize } from '@angular-devkit/core'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder styles resources output path', () => { + const imgSvg = ` + + + + `; + + function writeFiles() { + // Use a large image for the relative ref so it cannot be inlined. + host.copyFile('src/spectrum.png', './src/assets/global-img-relative.png'); + host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png'); + host.writeMultipleFiles({ + 'src/styles.css': ` + h1 { background: url('https://github.com/assets/global-img-absolute.svg'); } + h2 { background: url('https://github.com/assets/global-img-relative.png'); } + `, + 'src/app/app.component.css': ` + h3 { background: url('https://github.com/assets/component-img-absolute.svg'); } + h4 { background: url('https://github.com/assets/component-img-relative.png'); } + `, + 'src/assets/global-img-absolute.svg': imgSvg, + 'src/assets/component-img-absolute.svg': imgSvg, + }); + } + + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it(`supports resourcesOutputPath in resource urls`, async () => { + writeFiles(); + // Check base paths are correctly generated. + const overrides = { + aot: true, + resourcesOutputPath: 'out-assets', + }; + + const { files } = await browserBuild(architect, host, target, overrides); + const styles = await files['styles.css']; + const main = await files['main.js']; + + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(styles).toContain(`url('https://github.com/out-assets/global-img-relative.png')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/out-assets/component-img-relative.png')`); + + expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/out-assets/global-img-relative.png'))).toBe( + true, + ); + expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))).toBe( + true, + ); + expect(host.scopedSync().exists(normalize('dist/out-assets/component-img-relative.png'))).toBe( + true, + ); + }); + + it(`supports blank resourcesOutputPath`, async () => { + writeFiles(); + + // Check base paths are correctly generated. + const overrides = { aot: true }; + const { files } = await browserBuild(architect, host, target, overrides); + const styles = await files['styles.css']; + const main = await files['main.js']; + + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(styles).toContain(`url('https://github.com/global-img-relative.png')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/component-img-relative.png')`); + expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))).toBe( + true, + ); + expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))).toBe(true); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/scripts-array_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/scripts-array_spec.ts new file mode 100644 index 000000000000..ee55a57b2fc4 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/scripts-array_spec.ts @@ -0,0 +1,148 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { logging } from '@angular-devkit/core'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder scripts array', () => { + const scripts: { [path: string]: string } = { + 'src/input-script.js': "console.log('input-script'); var number = 1+1;", + 'src/zinput-script.js': "console.log('zinput-script');", + 'src/finput-script.js': "console.log('finput-script');", + 'src/uinput-script.js': "console.log('uinput-script');", + 'src/binput-script.js': "console.log('binput-script');", + 'src/ainput-script.js': "console.log('ainput-script');", + 'src/cinput-script.js': "console.log('cinput-script');", + 'src/lazy-script.js': "console.log('lazy-script');", + 'src/pre-rename-script.js': "console.log('pre-rename-script');", + 'src/pre-rename-lazy-script.js': "console.log('pre-rename-lazy-script');", + }; + const getScriptsOption = () => [ + 'src/input-script.js', + 'src/zinput-script.js', + 'src/finput-script.js', + 'src/uinput-script.js', + 'src/binput-script.js', + 'src/ainput-script.js', + 'src/cinput-script.js', + { input: 'src/lazy-script.js', bundleName: 'lazy-script', inject: false }, + { input: 'src/pre-rename-script.js', bundleName: 'renamed-script' }, + { input: 'src/pre-rename-lazy-script.js', bundleName: 'renamed-lazy-script', inject: false }, + ]; + + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + const matches: Record = { + 'scripts.js': 'input-script', + 'lazy-script.js': 'lazy-script', + 'renamed-script.js': 'pre-rename-script', + 'renamed-lazy-script.js': 'pre-rename-lazy-script', + 'main.js': 'input-script', + 'index.html': + '' + + '' + + '' + + '' + + '' + + '', + }; + + host.writeMultipleFiles(scripts); + host.appendToFile('src/main.ts', "\nimport './input-script.js';"); + + // Remove styles so we don't have to account for them in the index.html order check. + const { files } = await browserBuild(architect, host, target, { + styles: [], + scripts: getScriptsOption(), + } as {}); + + for (const [fileName, content] of Object.entries(matches)) { + expect(await files[fileName]).toMatch(content); + } + }); + + it('uglifies, uses sourcemaps, and adds hashes', async () => { + host.writeMultipleFiles(scripts); + + const { files } = await browserBuild(architect, host, target, { + optimization: true, + sourceMap: true, + outputHashing: 'all', + scripts: getScriptsOption(), + } as {}); + + const fileNames = Object.keys(files); + const scriptsBundle = fileNames.find((n) => /scripts\.[0-9a-f]{16}\.js/.test(n)); + expect(scriptsBundle).toBeTruthy(); + expect(await files[scriptsBundle || '']).toMatch('var number=2;'); + + expect(fileNames.some((n) => /scripts\.[0-9a-f]{16}\.js\.map/.test(n))).toBeTruthy(); + expect(fileNames.some((n) => /renamed-script\.[0-9a-f]{16}\.js/.test(n))).toBeTruthy(); + expect(fileNames.some((n) => /renamed-script\.[0-9a-f]{16}\.js\.map/.test(n))).toBeTruthy(); + expect(fileNames.some((n) => /script\.[0-9a-f]{16}\.js/.test(n))).toBeTruthy(); + expect(await files['lazy-script.js']).not.toBeUndefined(); + expect(await files['lazy-script.js.map']).not.toBeUndefined(); + expect(await files['renamed-lazy-script.js']).not.toBeUndefined(); + expect(await files['renamed-lazy-script.js.map']).not.toBeUndefined(); + }); + + it('preserves script order', async () => { + host.writeMultipleFiles(scripts); + + const { files } = await browserBuild(architect, host, target, { + scripts: getScriptsOption(), + } as {}); + + expect(await files['scripts.js']).toMatch( + new RegExp( + /.*['"]input-script['"](.|\n|\r)*/.source + + /['"]zinput-script['"](.|\n|\r)*/.source + + /['"]finput-script['"](.|\n|\r)*/.source + + /['"]uinput-script['"](.|\n|\r)*/.source + + /['"]binput-script['"](.|\n|\r)*/.source + + /['"]ainput-script['"](.|\n|\r)*/.source + + /['"]cinput-script['"]/.source, + ), + ); + }); + + it('chunk in entry', async () => { + host.writeMultipleFiles(scripts); + + const logger = new logging.Logger('build-script-chunk-entry'); + const logs: string[] = []; + logger.subscribe(({ message }) => { + logs.push(message); + }); + + await browserBuild( + architect, + host, + target, + { + scripts: getScriptsOption(), + } as {}, + { logger }, + ); + + const joinedLogs = logs.join('\n'); + expect(joinedLogs).toMatch(/lazy-script.+\d+ bytes/); + expect(joinedLogs).toMatch(/renamed-script.+\d+ bytes/); + expect(joinedLogs).toMatch(/renamed-lazy-script.+\d+ bytes/); + expect(joinedLogs).not.toContain('Lazy Chunks'); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/service-worker_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/service-worker_spec.ts new file mode 100644 index 000000000000..185b0f172b54 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/service-worker_spec.ts @@ -0,0 +1,250 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { normalize, virtualFs } from '@angular-devkit/core'; +import { debounceTime, take, tap } from 'rxjs/operators'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder service worker', () => { + const manifest = { + index: '/index.html', + assetGroups: [ + { + name: 'app', + installMode: 'prefetch', + resources: { + files: ['/favicon.ico', '/index.html', '/*.bundle.css', '/*.bundle.js', '/*.chunk.js'], + }, + }, + { + name: 'assets', + installMode: 'lazy', + updateMode: 'prefetch', + resources: { + files: ['/assets/**', '/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)'], + }, + }, + ], + }; + + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('errors if no ngsw-config.json is present', async () => { + const overrides = { serviceWorker: true }; + + const run = await architect.scheduleTarget(target, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); + + await run.stop(); + }); + + it('supports specifying a custom service worker configuration file', async () => { + host.writeMultipleFiles({ + 'src/configs/ngsw.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, + }); + + const overrides = { serviceWorker: true, ngswConfigPath: 'src/configs/ngsw.json' }; + + const run = await architect.scheduleTarget(target, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(normalize('dist/ngsw.json'))).toBeTrue(); + }); + + it('works with service worker', async () => { + host.writeMultipleFiles({ + 'src/ngsw-config.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, + }); + + const overrides = { serviceWorker: true }; + const run = await architect.scheduleTarget(target, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + expect(host.scopedSync().exists(normalize('dist/ngsw.json'))).toBeTrue(); + const ngswJson = JSON.parse( + virtualFs.fileBufferToString(host.scopedSync().read(normalize('dist/ngsw.json'))), + ); + // Verify index and assets are there. + expect(ngswJson).toEqual( + jasmine.objectContaining({ + configVersion: 1, + index: '/index.html', + navigationUrls: [ + { positive: true, regex: '^\\/.*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*\\.[^/]*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$' }, + ], + assetGroups: [ + { + name: 'app', + installMode: 'prefetch', + updateMode: 'prefetch', + urls: ['/favicon.ico', '/index.html'], + cacheQueryOptions: { + ignoreVary: true, + }, + patterns: [], + }, + { + name: 'assets', + installMode: 'lazy', + updateMode: 'prefetch', + urls: ['/assets/folder-asset.txt', '/spectrum.png'], + cacheQueryOptions: { + ignoreVary: true, + }, + patterns: [], + }, + ], + dataGroups: [], + hashTable: { + '/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01', + '/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4', + '/index.html': '8964a35a8b850942f8d18ba919f248762ff3154d', + '/spectrum.png': '8d048ece46c0f3af4b598a95fd8e4709b631c3c0', + }, + }), + ); + + await run.stop(); + }); + + it('works in watch mode', async () => { + host.writeMultipleFiles({ + 'src/ngsw-config.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, + }); + + const overrides = { serviceWorker: true, watch: true }; + const run = await architect.scheduleTarget(target, overrides); + let buildNumber = 0; + + await run.output + .pipe( + debounceTime(3000), + tap((buildEvent) => { + expect(buildEvent.success).toBeTrue(); + + const ngswJsonPath = normalize('dist/ngsw.json'); + expect(host.scopedSync().exists(ngswJsonPath)).toBeTrue(); + const ngswJson = JSON.parse( + virtualFs.fileBufferToString(host.scopedSync().read(ngswJsonPath)), + ); + + const hashTableEntries = Object.keys(ngswJson.hashTable); + + buildNumber += 1; + switch (buildNumber) { + case 1: + expect(hashTableEntries).toEqual([ + '/assets/folder-asset.txt', + '/favicon.ico', + '/index.html', + '/spectrum.png', + ]); + + host.copyFile('src/assets/folder-asset.txt', 'src/assets/folder-new-asset.txt'); + break; + + case 2: + expect(hashTableEntries).toEqual([ + '/assets/folder-asset.txt', + '/assets/folder-new-asset.txt', + '/favicon.ico', + '/index.html', + '/spectrum.png', + ]); + break; + } + }), + take(2), + ) + .toPromise(); + + await run.stop(); + }); + + it('works with service worker and baseHref', async () => { + host.writeMultipleFiles({ + 'src/ngsw-config.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + }); + + const overrides = { serviceWorker: true, baseHref: '/foo/bar' }; + const run = await architect.scheduleTarget(target, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + expect(host.scopedSync().exists(normalize('dist/ngsw.json'))).toBeTrue(); + const ngswJson = JSON.parse( + virtualFs.fileBufferToString(host.scopedSync().read(normalize('dist/ngsw.json'))), + ); + // Verify index and assets include the base href. + expect(ngswJson).toEqual( + jasmine.objectContaining({ + configVersion: 1, + index: '/foo/bar/index.html', + navigationUrls: [ + { positive: true, regex: '^\\/.*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*\\.[^/]*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$' }, + ], + assetGroups: [ + { + name: 'app', + installMode: 'prefetch', + updateMode: 'prefetch', + urls: ['/foo/bar/favicon.ico', '/foo/bar/index.html'], + patterns: [], + cacheQueryOptions: { + ignoreVary: true, + }, + }, + { + name: 'assets', + installMode: 'lazy', + updateMode: 'prefetch', + urls: ['/foo/bar/assets/folder-asset.txt'], + patterns: [], + cacheQueryOptions: { + ignoreVary: true, + }, + }, + ], + dataGroups: [], + hashTable: { + '/foo/bar/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01', + '/foo/bar/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4', + '/foo/bar/index.html': '5c99755c1e7cfd1c8aba34ad1155afc72a288fec', + }, + }), + ); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/source-map_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/source-map_spec.ts new file mode 100644 index 000000000000..62349bb149be --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/source-map_spec.ts @@ -0,0 +1,171 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { OutputHashing } from '@angular-devkit/build-angular'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder source map', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + const overrides = { + sourceMap: true, + styles: ['src/styles.css'], + }; + + host.writeMultipleFiles({ + 'src/styles.css': `div { flex: 1 }`, + }); + + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['main.js.map']).not.toBeUndefined(); + expect(await files['styles.css.map']).not.toBeUndefined(); + }); + + it(`sourcemaps sources should not start with '/'`, async () => { + // If sourcemaps sources start with a '/' it will break VS code breakpoints + // Unless 'sourceMapPathOverrides' are provided + const overrides = { + sourceMap: true, + }; + + const { files } = await browserBuild(architect, host, target, overrides); + const mainJSMap = await files['main.js.map']; + expect(mainJSMap).not.toBeUndefined(); + + const sources: string[] = JSON.parse(mainJSMap).sources; + for (const source of sources) { + expect(source.startsWith('/')).toBe(false, `${source} started with an '/'.`); + } + }); + + it('works with outputHashing', async () => { + const { files } = await browserBuild(architect, host, target, { + sourceMap: true, + outputHashing: OutputHashing.All, + }); + + expect(Object.keys(files).some((name) => /main\.[0-9a-f]{16}\.js.map/.test(name))).toBeTruthy(); + }); + + it('does not output source map when disabled', async () => { + const { files } = await browserBuild(architect, host, target, { + sourceMap: false, + }); + + expect(files['main.js.map']).toBeUndefined(); + }); + + it('supports hidden sourcemaps', async () => { + const overrides = { + sourceMap: { + hidden: true, + styles: true, + scripts: true, + }, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + }); + + const { files } = await browserBuild(architect, host, target, overrides); + expect('main.js.map' in files).toBe(true); + expect('styles.css.map' in files).toBe(true); + expect(await files['main.js']).not.toContain('sourceMappingURL=main.js.map'); + expect(await files['styles.css']).not.toContain('sourceMappingURL=styles.css.map'); + }); + + it('supports styles only sourcemaps', async () => { + const overrides = { + sourceMap: { + styles: true, + scripts: false, + }, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + }); + + const { files } = await browserBuild(architect, host, target, overrides); + expect('main.js.map' in files).toBe(false); + expect('styles.css.map' in files).toBe(true); + expect(await files['main.js']).not.toContain('sourceMappingURL=main.js.map'); + expect(await files['styles.css']).toContain('sourceMappingURL=styles.css.map'); + }); + + it('supports scripts only sourcemaps', async () => { + const overrides = { + sourceMap: { + styles: false, + scripts: true, + }, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + }); + + const { files } = await browserBuild(architect, host, target, overrides); + expect('main.js.map' in files).toBe(true); + expect('styles.css.map' in files).toBe(false); + expect(await files['main.js']).toContain('sourceMappingURL=main.js.map'); + expect(await files['styles.css']).not.toContain('sourceMappingURL=styles.css.map'); + }); + + it('should not inline component styles sourcemaps when hidden', async () => { + const overrides = { + sourceMap: { + hidden: true, + styles: true, + scripts: true, + }, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `div { flex: 1 }`, + 'src/app/app.component.css': `p { color: red; }`, + }); + + const { files } = await browserBuild(architect, host, target, overrides); + expect('main.js.map' in files).toBe(true); + expect('styles.css.map' in files).toBe(true); + expect(await files['main.js']).not.toContain('sourceMappingURL=main.js.map'); + expect(await files['main.js']).not.toContain('sourceMappingURL=data:application/json'); + expect(await files['styles.css']).not.toContain('sourceMappingURL=styles.css.map'); + expect(await files['styles.css']).not.toContain('sourceMappingURL=data:application/json'); + }); + + it('should resolve sources to partial SCSS files', async () => { + const overrides = { + sourceMap: true, + styles: ['src/styles.scss'], + }; + + host.writeMultipleFiles({ + 'src/styles.scss': `@import 'https://github.com/partial';`, + 'src/_partial.scss': `p { color: red; }`, + }); + + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['styles.css.map']).toContain('_partial.scss'); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/stats-json_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/stats-json_spec.ts new file mode 100644 index 000000000000..a46f974c8381 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/stats-json_spec.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder stats json', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + const { files } = await browserBuild(architect, host, target, { statsJson: true }); + expect('stats.json' in files).toBe(true); + }); + + it('works with profile flag', async () => { + const { files } = await browserBuild(architect, host, target, { statsJson: true }); + expect('stats.json' in files).toBe(true); + const stats = JSON.parse(await files['stats.json']); + expect(stats.chunks[0].modules[0].profile.building).toBeDefined(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/styles_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/styles_spec.ts new file mode 100644 index 000000000000..964d5deac850 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/styles_spec.ts @@ -0,0 +1,701 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { TestProjectHost } from '@angular-devkit/architect/testing'; +import { normalize, tags } from '@angular-devkit/core'; +import { dirname } from 'path'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder styles', () => { + const extensionsWithImportSupport = ['css', 'scss', 'less']; + const extensionsWithVariableSupport = ['scss', 'less']; + const imgSvg = ` + + + + `; + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('supports global styles', async () => { + const styles = [ + 'src/input-style.css', + { input: 'src/lazy-style.css', bundleName: 'lazy-style', inject: false }, + { input: 'src/pre-rename-style.css', bundleName: 'renamed-style' }, + { input: 'src/pre-rename-lazy-style.css', bundleName: 'renamed-lazy-style', inject: false }, + ] as {}; + const cssMatches: { [path: string]: string } = { + 'styles.css': '.input-style', + 'lazy-style.css': '.lazy-style', + 'renamed-style.css': '.pre-rename-style', + 'renamed-lazy-style.css': '.pre-rename-lazy-style', + }; + const cssIndexMatches: { [path: string]: string } = { + 'index.html': + '' + + '', + }; + host.writeMultipleFiles({ + 'src/string-style.css': '.string-style { color: red }', + 'src/input-style.css': '.input-style { color: red }', + 'src/lazy-style.css': '.lazy-style { color: red }', + 'src/pre-rename-style.css': '.pre-rename-style { color: red }', + 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', + }); + + const { files } = await browserBuild(architect, host, target, { styles }); + // Check css files were created. + for (const cssFileName of Object.keys(cssMatches)) { + expect(await files[cssFileName]).toMatch(cssMatches[cssFileName]); + } + + // Check check index has styles in the right order. + for (const cssIndexFileName of Object.keys(cssIndexMatches)) { + expect(await files[cssIndexFileName]).toMatch(cssIndexMatches[cssIndexFileName]); + } + }); + + it('supports empty styleUrls in components', async () => { + host.writeMultipleFiles({ + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: [] + }) + export class AppComponent { + title = 'app'; + } + `, + }); + + await browserBuild(architect, host, target); + }); + + it('supports autoprefixer with inline component styles in JIT mode', async () => { + host.writeMultipleFiles({ + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styles: ['div { mask-composite: add; }'], + }) + export class AppComponent { + title = 'app'; + } + `, + '.browserslistrc': ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + }); + + const { files } = await browserBuild(architect, host, target, { aot: false }); + + expect(await files['main.js']).toContain('-webkit-mask-composite'); + }); + + it('supports autoprefixer with inline component styles in AOT mode', async () => { + host.writeMultipleFiles({ + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styles: ['div { mask-composite: add; }'], + }) + export class AppComponent { + title = 'app'; + } + `, + '.browserslistrc': ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + }); + + const { files } = await browserBuild(architect, host, target, { aot: true }); + + expect(await files['main.js']).toContain('-webkit-mask-composite'); + }); + + extensionsWithImportSupport.forEach((ext) => { + it(`supports imports in ${ext} files`, async () => { + host.writeMultipleFiles({ + [`src/styles.${ext}`]: ` + @import 'https://github.com/imported-styles.$%7Bext%7D'; + body { background-color: #00f; } + `, + [`src/imported-styles.${ext}`]: 'p { background-color: #f00; }', + [`src/app/app.component.${ext}`]: ` + @import 'https://github.com/imported-component-styles.$%7Bext%7D'; + .outer { + .inner { + background: #fff; + } + } + `, + [`src/app/imported-component-styles.${ext}`]: 'h1 { background: #000; }', + }); + + const matches: { [path: string]: RegExp } = { + 'styles.css': new RegExp( + // The global style should be there + /p\s*{\s*background-color: #f00;\s*}(.|\n|\r)*/.source + + // The global style via import should be there + /body\s*{\s*background-color: #00f;\s*}/.source, + ), + 'styles.css.map': /"mappings":".+"/, + 'main.js': new RegExp( + // The component style should be there + /h1(.|\n|\r)*background:\s*#000(.|\n|\r)*/.source + + // The component style via import should be there + /.outer(.|\n|\r)*.inner(.|\n|\r)*background:\s*#[fF]+/.source, + ), + }; + + const overrides = { + aot: true, + sourceMap: true, + styles: [`src/styles.${ext}`], + }; + + host.replaceInFile( + 'src/app/app.component.ts', + './app.component.css', + `./app.component.${ext}`, + ); + + const { files } = await browserBuild(architect, host, target, overrides); + for (const fileName of Object.keys(matches)) { + expect(await files[fileName]).toMatch(matches[fileName]); + } + }); + }); + + extensionsWithImportSupport.forEach((ext) => { + it(`supports material imports in ${ext} files`, async () => { + host.writeMultipleFiles({ + [`src/styles.${ext}`]: ` + @import "https://github.com/@angular/material/prebuilt-themes/indigo-pink.css"; + `, + [`src/app/app.component.${ext}`]: ` + @import "https://github.com/@angular/material/prebuilt-themes/indigo-pink.css"; + `, + }); + host.replaceInFile( + 'src/app/app.component.ts', + './app.component.css', + `./app.component.${ext}`, + ); + + const overrides = { + styles: [{ input: `src/styles.${ext}` }], + }; + await browserBuild(architect, host, target, overrides); + }); + }); + + extensionsWithVariableSupport.forEach((ext) => { + it(`supports ${ext} includePaths`, async () => { + let variableAssignment = ''; + let variablereference = ''; + if (ext === 'scss') { + variableAssignment = '$primary-color:'; + variablereference = '$primary-color'; + } else if (ext === 'less') { + variableAssignment = '@primary-color:'; + variablereference = '@primary-color'; + } + + host.writeMultipleFiles({ + [`src/style-paths/variables.${ext}`]: `${variableAssignment} #f00;`, + [`src/styles.${ext}`]: ` + @import 'https://github.com/variables'; + h1 { color: ${variablereference}; } + `, + [`src/app/app.component.${ext}`]: ` + @import 'https://github.com/variables'; + h2 { color: ${variablereference}; } + `, + }); + + const matches: { [path: string]: RegExp } = { + 'styles.css': /h1\s*{\s*color: #f00;\s*}/, + 'main.js': /h2.*{.*color: #f00;.*}/, + }; + + host.replaceInFile( + 'src/app/app.component.ts', + './app.component.css', + `./app.component.${ext}`, + ); + + const overrides = { + styles: [`src/styles.${ext}`], + stylePreprocessorOptions: { + includePaths: ['src/style-paths'], + }, + }; + + const { files } = await browserBuild(architect, host, target, overrides); + for (const fileName of Object.keys(matches)) { + expect(await files[fileName]).toMatch(matches[fileName]); + } + }); + }); + + it(`supports font-awesome imports`, async () => { + // font-awesome mock to avoid having an extra dependency. + host.writeMultipleFiles({ + 'node_modules/font-awesome/scss/font-awesome.scss': ` + * { color: red } + `, + }); + + host.writeMultipleFiles({ + 'src/styles.scss': ` + @import "https://github.com/font-awesome/scss/font-awesome"; + `, + }); + + const overrides = { styles: [`src/styles.scss`] }; + await browserBuild(architect, host, target, overrides); + }); + + it(`uses autoprefixer`, async () => { + host.writeMultipleFiles({ + 'src/styles.css': tags.stripIndents` + @import url(https://github.com/imported-styles.css); + /* normal-comment */ + /*! important-comment */ + div { mask-composite: add; }`, + 'src/imported-styles.css': tags.stripIndents` + /* normal-comment */ + /*! important-comment */ + section { mask-composite: add; }`, + '.browserslistrc': ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + }); + + const overrides = { optimization: false }; + const { files } = await browserBuild(architect, host, target, overrides); + const content = await files['styles.css']; + expect(content).toContain(tags.stripIndents` + /* normal-comment */ + /*! important-comment */ + section { -webkit-mask-composite: source-over; mask-composite: add; }`); + // Check separately because Webpack may add source file comments inbetween the rules + expect(content).toContain(tags.stripIndents` + /* normal-comment */ + /*! important-comment */ + div { -webkit-mask-composite: source-over; mask-composite: add; }`); + }); + + it(`minimizes css`, async () => { + host.writeMultipleFiles({ + 'src/styles.css': tags.stripIndents` + /* normal-comment */ + /*! important-comment */ + div { flex: 1 }`, + }); + + const overrides = { optimization: true }; + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['styles.css']).toContain('/*! important-comment */'); + }); + + // TODO: consider making this a unit test in the url processing plugins. + it( + `supports baseHref/deployUrl in resource urls`, + async () => { + // Use a large image for the relative ref so it cannot be inlined. + host.copyFile('src/spectrum.png', './src/assets/global-img-relative.png'); + host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png'); + host.writeMultipleFiles({ + 'src/styles.css': ` + h1 { background: url('https://github.com/assets/global-img-absolute.svg'); } + h2 { background: url('https://github.com/assets/global-img-relative.png'); } + `, + 'src/app/app.component.css': ` + h3 { background: url('https://github.com/assets/component-img-absolute.svg'); } + h4 { background: url('https://github.com/assets/component-img-relative.png'); } + `, + 'src/assets/global-img-absolute.svg': imgSvg, + 'src/assets/component-img-absolute.svg': imgSvg, + }); + + let { files } = await browserBuild(architect, host, target, { aot: true }); + + // Check base paths are correctly generated. + let styles = await files['styles.css']; + let main = await files['main.js']; + + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(styles).toContain(`url('https://github.com/global-img-relative.png')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/component-img-relative.png')`); + + expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/global-img-relative.png'))).toBe(true); + expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg'))).toBe( + true, + ); + expect(host.scopedSync().exists(normalize('dist/component-img-relative.png'))).toBe(true); + + // Check urls with deploy-url scheme are used as is. + files = ( + await browserBuild(architect, host, target, { + baseHref: '/base/', + deployUrl: 'http://deploy.url/', + }) + ).files; + + styles = await files['styles.css']; + main = await files['main.js']; + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + + // Check urls with base-href scheme are used as is (with deploy-url). + files = ( + await browserBuild(architect, host, target, { + baseHref: 'http://base.url/', + deployUrl: 'deploy/', + }) + ).files; + styles = await files['styles.css']; + main = await files['main.js']; + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + + // Check urls with deploy-url and base-href scheme only use deploy-url. + files = ( + await browserBuild(architect, host, target, { + baseHref: 'http://base.url/', + deployUrl: 'http://deploy.url/', + }) + ).files; + styles = await files['styles.css']; + main = await files['main.js']; + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + + // Check with schemeless base-href and deploy-url flags. + files = ( + await browserBuild(architect, host, target, { + baseHref: '/base/', + deployUrl: 'deploy/', + }) + ).files; + styles = await files['styles.css']; + main = await files['main.js']; + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + + // Check with identical base-href and deploy-url flags. + files = ( + await browserBuild(architect, host, target, { + baseHref: '/base/', + deployUrl: '/base/', + }) + ).files; + + styles = await files['styles.css']; + main = await files['main.js']; + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + + // Check with only base-href flag. + files = ( + await browserBuild(architect, host, target, { + baseHref: '/base/', + }) + ).files; + + styles = await files['styles.css']; + main = await files['main.js']; + expect(styles).toContain(`url('https://github.com/assets/global-img-absolute.svg')`); + expect(main).toContain(`url('https://github.com/assets/component-img-absolute.svg')`); + // NOTE: Timeout for large amount of builds in test. Test should be split up when refactored. + }, + 4 * 60 * 1000, + ); + + it(`supports bootstrap@4 with full path`, async () => { + const bootstrapPath = dirname(require.resolve('bootstrap/package.json')); + + const overrides = { + styles: [bootstrapPath + '/dist/css/bootstrap.css'], + scripts: [bootstrapPath + '/dist/js/bootstrap.js'], + }; + + await browserBuild(architect, host, target, overrides); + }); + + it(`supports bootstrap@4 with package reference`, async () => { + const overrides = { + styles: ['bootstrap/dist/css/bootstrap.css'], + scripts: ['bootstrap/dist/js/bootstrap.js'], + }; + + await browserBuild(architect, host, target, overrides); + }); + + it(`supports inline javascript in less`, async () => { + const overrides = { styles: [`src/styles.less`] }; + host.writeMultipleFiles({ + 'src/styles.less': ` + .myFunction() { + @functions: ~\`(function () { + return ''; + })()\`; + } + .myFunction(); + `, + }); + + await browserBuild(architect, host, target, overrides); + }); + + it('causes equal failure for tilde and tilde-slash url()', async () => { + host.writeMultipleFiles({ + 'src/styles.css': ` + body { + background-image: url('https://github.com/~/does-not-exist.jpg'); + } + `, + }); + + const overrides = { optimization: true }; + const run = await architect.scheduleTarget(target, overrides); + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); + + host.writeMultipleFiles({ + 'src/styles.css': ` + body { + background-image: url('https://github.com/~does-not-exist.jpg'); + } + `, + }); + + const run2 = await architect.scheduleTarget(target, overrides); + await expectAsync(run2.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); + await run2.stop(); + await run.stop(); + }); + + it('supports Protocol-relative Url', async () => { + host.writeMultipleFiles({ + 'src/styles.css': ` + body { + background-image: url('https://cdn.com/classic-bg.jpg'); + } + `, + }); + + const overrides = { optimization: true }; + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['styles.css']).toContain('background-image:url(https://cdn.com/classic-bg.jpg)'); + }); + + it('supports fonts with space in filename', async () => { + host.writeMultipleFiles({ + 'src/styles.css': ` + @font-face { + font-family: "Font Awesome"; + src: url("https://github.com/assets/fa%20solid-900.woff2") format("woff2"); + } + + body { + font-family: "Font Awesome"; + } + `, + 'src/assets/fa solid-900.woff2': '', + }); + + const { output } = await browserBuild(architect, host, target); + expect(output.success).toBe(true); + }); + + extensionsWithImportSupport.forEach((ext) => { + it(`retains declarations order in ${ext} files when using @import`, async () => { + host.writeMultipleFiles({ + [`src/styles-one.${ext}`]: tags.stripIndents` + .one { + color: #fff; + } + `, + [`src/styles-two.${ext}`]: tags.stripIndents` + .two { + color: #fff; + } + `, + // LESS doesn't support css imports by default. + // See: https://github.com/less/less.js/issues/3188#issuecomment-374690630 + [`src/styles-three.${ext}`]: tags.stripIndents` + @import ${ + ext === 'less' ? ' (css) ' : '' + }url("https://fonts.googleapis.com/css?family=Roboto:400"); + .three { + color: #fff; + } + `, + }); + + const overrides = { + styles: [`src/styles-one.${ext}`, `src/styles-two.${ext}`, `src/styles-three.${ext}`], + }; + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['styles.css']).toMatch(/\.one(.|\n|\r)*\.two(.|\n|\r)*\.three/); + }); + }); + + extensionsWithImportSupport.forEach((ext) => { + it(`adjusts relative resource URLs when using @import in ${ext} (global)`, async () => { + host.copyFile('src/spectrum.png', './src/more-styles/images/global-img-relative.png'); + host.writeMultipleFiles({ + [`src/styles-one.${ext}`]: tags.stripIndents` + @import "https://github.com/more-styles/styles-two.$%7Bext%7D"; + `, + [`src/more-styles/styles-two.${ext}`]: tags.stripIndents` + .two { + background-image: url(https://github.com/images/global-img-relative.png); + } + `, + }); + + const overrides = { + sourceMap: false, + styles: [`src/styles-one.${ext}`], + }; + const { files } = await browserBuild(architect, host, target, overrides); + expect(await files['styles.css']).toContain("'global-img-relative.png'"); + }); + + it(`adjusts relative resource URLs when using @import in ${ext} (component)`, async () => { + host.copyFile('src/spectrum.png', './src/app/images/component-img-relative.png'); + host.writeMultipleFiles({ + [`src/app/styles/component-styles.${ext}`]: ` + div { background-image: url(https://github.com/images/component-img-relative.png); } + `, + [`src/app/app.component.${ext}`]: ` + @import "https://github.com/styles/component-styles.$%7Bext%7D"; + `, + }); + + host.replaceInFile( + 'src/app/app.component.ts', + './app.component.css', + `./app.component.${ext}`, + ); + + const overrides = { + sourceMap: false, + }; + await browserBuild(architect, host, target, overrides); + }); + }); + + it('should minify colors based on browser support', async () => { + host.writeMultipleFiles({ + 'src/styles.css': ` + div { box-shadow: 0 3px 10px, rgba(0, 0, 0, 0.15); } + `, + }); + + let result = await browserBuild(architect, host, target, { optimization: true }); + expect(await result.files['styles.css']).toContain('#00000026'); + + await host.restore().toPromise(); + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + + // Edge 17 doesn't support #rrggbbaa colors + // https://github.com/angular/angular-cli/issues/21594#:~:text=%23rrggbbaa%20hex%20color%20notation + // While this browser is un-supported, this is used as a base case to test that the underlying + // logic to pass the list of supported browsers to the css optimizer works. + host.writeMultipleFiles({ + 'src/styles.css': ` + div { box-shadow: 0 3px 10px, rgba(0, 0, 0, 0.15); } + `, + '.browserslistrc': `edge 18`, + }); + + result = await browserBuild(architect, host, target, { optimization: true }); + + expect(await result.files['styles.css']).toContain('rgba(0,0,0,.15)'); + }); + + it('works when using the same css file in `styles` and `stylesUrl`', async () => { + host.writeMultipleFiles({ + 'src/styles.css': ` + div { color: red } + `, + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['../styles.css'] + }) + export class AppComponent { + title = 'app'; + } + `, + }); + + await browserBuild(architect, host, target, { styles: ['src/styles.css'] }); + }); + + it('works when Data URI has escaped quote', async () => { + const svgData = `"data:image/svg+xml;charset=utf-8,"`; + + host.writeMultipleFiles({ + 'src/styles.css': ` + div { background: url(https://github.com/$%7BsvgData%7D) } + `, + }); + + const result = await browserBuild(architect, host, target, { styles: ['src/styles.css'] }); + expect(await result.files['styles.css']).toContain(svgData); + }); + + it('works when Data URI has parenthesis', async () => { + const svgData = + '"data:image/svg+xml;charset=utf-8,' + + `` + + '"'; + + host.writeMultipleFiles({ + 'src/styles.css': ` + div { background: url(https://github.com/$%7BsvgData%7D) } + `, + }); + + const result = await browserBuild(architect, host, target, { styles: ['src/styles.css'] }); + expect(await result.files['styles.css']).toContain(svgData); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/svg_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/svg_spec.ts similarity index 93% rename from packages/angular_devkit/build_angular/src/browser/specs/svg_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/svg_spec.ts index 82cc1cb8ac7c..1a040678c6c6 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/svg_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/svg_spec.ts @@ -1,13 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect } from '@angular-devkit/architect'; import { join, normalize, virtualFs } from '@angular-devkit/core'; -import { createArchitect, host, outputPath } from '../../test-utils'; +import { createArchitect, host, outputPath } from '../../../testing/test-utils'; describe('Browser Builder allow svg', () => { const target = { project: 'app', target: 'build' }; diff --git a/packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/tsconfig-paths_spec.ts similarity index 82% rename from packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/tsconfig-paths_spec.ts index 8e31ff766798..eafc266fc12f 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/tsconfig-paths_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/tsconfig-paths_spec.ts @@ -1,14 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; - +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder tsconfig paths', () => { const target = { project: 'app', target: 'build' }; @@ -22,14 +21,18 @@ describe('Browser Builder tsconfig paths', () => { it('works', async () => { host.replaceInFile('src/app/app.module.ts', './app.component', '@root/app/app.component'); - host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, ` + host.replaceInFile( + 'tsconfig.json', + /"baseUrl": ".\/",/, + ` "baseUrl": "./", "paths": { "@root/*": [ "./src/*" ] }, - `); + `, + ); await browserBuild(architect, host, target); }); @@ -40,7 +43,10 @@ describe('Browser Builder tsconfig paths', () => { 'src/app/shared/meaning.ts': 'export var meaning = 42;', 'src/app/shared/index.ts': `export * from './meaning'`, }); - host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, ` + host.replaceInFile( + 'tsconfig.json', + /"baseUrl": ".\/",/, + ` "baseUrl": "./", "paths": { "@shared": [ @@ -54,8 +60,11 @@ describe('Browser Builder tsconfig paths', () => { "src/app/shared/*" ] }, - `); - host.appendToFile('src/app/app.component.ts', ` + `, + ); + host.appendToFile( + 'src/app/app.component.ts', + ` import { meaning } from 'src/app/shared/meaning'; import { meaning as meaning2 } from '@shared'; import { meaning as meaning3 } from '@shared/meaning'; @@ -69,7 +78,8 @@ describe('Browser Builder tsconfig paths', () => { console.log(meaning3) console.log(meaning4) console.log(meaning5) - `); + `, + ); await browserBuild(architect, host, target); }); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts new file mode 100644 index 000000000000..5ff6302007f5 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts @@ -0,0 +1,256 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { BrowserBuilderOutput } from '@angular-devkit/build-angular'; +import { logging } from '@angular-devkit/core'; +import { debounceTime, take, tap } from 'rxjs/operators'; +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Browser Builder unused files warnings', () => { + const warningMessageSuffix = `is part of the TypeScript compilation but it's unused`; + const targetSpec = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('should not show warning when all files are used', async () => { + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + expect(logs.join().includes(warningMessageSuffix)).toBe(false); + + await run.stop(); + }); + + it('should show warning when some files are unused', async () => { + host.writeMultipleFiles({ + 'src/unused-file.ts': `export const unused = '1';`, + }); + + host.replaceInFile('src/tsconfig.app.json', '"main.ts"', '"main.ts", "unused-file.ts"'); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + expect(logs.join().includes(`unused-file.ts ${warningMessageSuffix}`)).toBe(true); + + await run.stop(); + }); + + it('should not show warning when excluded files are unused', async () => { + const ignoredFiles = { + 'src/file.d.ts': 'export type MyType = number;', + }; + + host.writeMultipleFiles(ignoredFiles); + + host.replaceInFile( + 'src/tsconfig.app.json', + '"main.ts"', + `"main.ts", ${Object.keys(ignoredFiles) + .map((f) => `"${f.replace('src/', '')}"`) + .join(',')}`, + ); + + host.replaceInFile( + 'src/tsconfig.app.json', + '"compilerOptions":', + '"angularCompilerOptions": { "strictTemplates": true }, "compilerOptions":', + ); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, { aot: true }, { logger }); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + expect(logs.join().includes(warningMessageSuffix)).toBe(false); + + await run.stop(); + }); + + it('should not show warning when type files are used', async () => { + host.writeMultipleFiles({ + 'src/app/type.ts': 'export type MyType = number;', + }); + + host.replaceInFile( + 'src/app/app.component.ts', + `'@angular/core';`, + `'@angular/core';\nimport { MyType } from './type';\n`, + ); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + expect(logs.join().includes(warningMessageSuffix)).toBe(false); + + await run.stop(); + }); + + it('should not show warning when type files are used transitively', async () => { + host.writeMultipleFiles({ + 'src/app/type.ts': `import {Myinterface} from './interface'; export type MyType = Myinterface;`, + 'src/app/interface.ts': 'export interface Myinterface {nbr: number;}', + }); + + host.replaceInFile( + 'src/app/app.component.ts', + `'@angular/core';`, + `'@angular/core';\nimport { MyType } from './type';\n`, + ); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(targetSpec, undefined, { logger }); + const output = (await run.result) as BrowserBuilderOutput; + expect(output.success).toBe(true); + expect(logs.join().includes(warningMessageSuffix)).toBe(false); + + await run.stop(); + }); + + it('works for rebuilds', async () => { + host.replaceInFile('src/tsconfig.app.json', '"**/*.d.ts"', '"**/*.d.ts", "testing/**/*.ts"'); + + const logger = new logging.Logger(''); + let logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + let buildNumber = 0; + const run = await architect.scheduleTarget(targetSpec, { watch: true }, { logger }); + + await run.output + .pipe( + debounceTime(1000), + tap((buildEvent) => { + expect(buildEvent.success).toBe(true); + + buildNumber++; + switch (buildNumber) { + case 1: + // The first should not have unused files + expect(logs.join().includes(warningMessageSuffix)).toBe( + false, + `Case ${buildNumber} failed.`, + ); + + // Write a used file + host.writeMultipleFiles({ + 'src/testing/type.ts': 'export type MyType = number;', + }); + + // touch file to trigger build + host.replaceInFile( + 'src/app/app.component.ts', + `'@angular/core';`, + `'@angular/core';\n`, + ); + break; + + case 2: + // The second should have type.ts as unused + expect(logs.join().includes(`type.ts ${warningMessageSuffix}`)).toBe( + true, + `Case ${buildNumber} failed.`, + ); + + host.replaceInFile( + 'src/app/app.component.ts', + `'@angular/core';`, + `'@angular/core';\nimport { MyType } from '../testing/type';`, + ); + break; + + case 3: + // The third should not have any unused files + expect(logs.join().includes(warningMessageSuffix)).toBe( + false, + `Case ${buildNumber} failed.`, + ); + break; + } + + logs = []; + }), + take(3), + ) + .toPromise(); + await run.stop(); + }); + + it('should only show warning once per file', async () => { + host.replaceInFile('src/tsconfig.app.json', '"**/*.d.ts"', '"**/*.d.ts", "testing/**/*.ts"'); + + // Write a used file + host.writeMultipleFiles({ + 'src/testing/type.ts': 'export type MyType = number;', + }); + + const logger = new logging.Logger(''); + let logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + let buildNumber = 0; + const run = await architect.scheduleTarget(targetSpec, { watch: true }, { logger }); + + await run.output + .pipe( + debounceTime(1000), + tap((buildEvent) => { + expect(buildEvent.success).toBe(true); + + buildNumber++; + switch (buildNumber) { + case 1: + // The first should have type.ts as unused. + expect(logs.join().includes(`type.ts ${warningMessageSuffix}`)).toBe( + true, + `Case ${buildNumber} failed.`, + ); + + // touch a file to trigger a rebuild + host.appendToFile('src/main.ts', ''); + break; + case 2: + // The second should should have type.ts as unused but shouldn't warn. + expect(logs.join().includes(warningMessageSuffix)).toBe( + false, + `Case ${buildNumber} failed.`, + ); + break; + } + + logs = []; + }), + take(2), + ) + .toPromise(); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/specs/vendor-chunk_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/vendor-chunk_spec.ts similarity index 84% rename from packages/angular_devkit/build_angular/src/browser/specs/vendor-chunk_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/specs/vendor-chunk_spec.ts index e78237b25186..51a942631684 100644 --- a/packages/angular_devkit/build_angular/src/browser/specs/vendor-chunk_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/vendor-chunk_spec.ts @@ -1,13 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Architect } from '@angular-devkit/architect'; -import { browserBuild, createArchitect, host } from '../../test-utils'; +import { Architect } from '@angular-devkit/architect'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; describe('Browser Builder vendor chunk', () => { const target = { project: 'app', target: 'build' }; diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/vendor-source-map_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/vendor-source-map_spec.ts new file mode 100644 index 000000000000..024003369ac8 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/vendor-source-map_spec.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import * as path from 'path'; +import { browserBuild, createArchitect, host } from '../../../testing/test-utils'; + +// Following the naming conventions from +// https://sourcemaps.info/spec.html#h.ghqpj1ytqjbm +const IGNORE_LIST = 'x_google_ignoreList'; + +describe('Browser Builder external source map', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('works', async () => { + const overrides = { + sourceMap: { + scripts: true, + styles: true, + vendor: true, + }, + }; + + const { files } = await browserBuild(architect, host, target, overrides); + const sourcePaths: string[] = JSON.parse(await files['vendor.js.map']).sources; + const hasTsSourcePaths = sourcePaths.some((p) => path.extname(p) == '.ts'); + expect(hasTsSourcePaths).toBe(true, `vendor.js.map should have '.ts' extentions`); + }); + + it('does not map sourcemaps from external library when disabled', async () => { + const overrides = { + sourceMap: { + scripts: true, + styles: true, + vendor: false, + }, + }; + + const { files } = await browserBuild(architect, host, target, overrides); + const sourcePaths: string[] = JSON.parse(await files['vendor.js.map']).sources; + const hasTsSourcePaths = sourcePaths.some((p) => path.extname(p) == '.ts'); + expect(hasTsSourcePaths).toBe(false, `vendor.js.map not should have '.ts' extentions`); + }); +}); + +describe('Identifying third-party code in source maps', () => { + interface SourceMap { + sources: string[]; + [IGNORE_LIST]: number[]; + } + + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + it('specifies which sources are third party when vendor processing is disabled', async () => { + const overrides = { + sourceMap: { + scripts: true, + vendor: false, + }, + }; + + const { files } = await browserBuild(architect, host, target, overrides); + const mainMap: SourceMap = JSON.parse(await files['main.js.map']); + const polyfillsMap: SourceMap = JSON.parse(await files['polyfills.js.map']); + const runtimeMap: SourceMap = JSON.parse(await files['runtime.js.map']); + const vendorMap: SourceMap = JSON.parse(await files['vendor.js.map']); + + expect(mainMap[IGNORE_LIST]).not.toBeUndefined(); + expect(polyfillsMap[IGNORE_LIST]).not.toBeUndefined(); + expect(runtimeMap[IGNORE_LIST]).not.toBeUndefined(); + expect(vendorMap[IGNORE_LIST]).not.toBeUndefined(); + + expect(mainMap[IGNORE_LIST].length).toEqual(0); + expect(polyfillsMap[IGNORE_LIST].length).not.toEqual(0); + expect(runtimeMap[IGNORE_LIST].length).not.toEqual(0); + expect(vendorMap[IGNORE_LIST].length).not.toEqual(0); + + const thirdPartyInMain = mainMap.sources.some((s) => s.includes('node_modules')); + const thirdPartyInPolyfills = polyfillsMap.sources.some((s) => s.includes('node_modules')); + const thirdPartyInRuntime = runtimeMap.sources.some((s) => s.includes('webpack')); + const thirdPartyInVendor = vendorMap.sources.some((s) => s.includes('node_modules')); + expect(thirdPartyInMain).toBe(false, `main.js.map should not include any node modules`); + expect(thirdPartyInPolyfills).toBe(true, `polyfills.js.map should include some node modules`); + expect(thirdPartyInRuntime).toBe(true, `runtime.js.map should include some webpack code`); + expect(thirdPartyInVendor).toBe(true, `vendor.js.map should include some node modules`); + + // All sources in the main map are first-party. + expect(mainMap.sources.filter((_, i) => !mainMap[IGNORE_LIST].includes(i))).toEqual([ + './src/app/app.component.ts', + './src/app/app.module.ts', + './src/main.ts', + './src/app/app.component.css', + ]); + + // Only some sources in the polyfills map are first-party. + expect(polyfillsMap.sources.filter((_, i) => !polyfillsMap[IGNORE_LIST].includes(i))).toEqual([ + './src/polyfills.ts', + ]); + + // None of the sources in the runtime and vendor maps are first-party. + expect(runtimeMap.sources.filter((_, i) => !runtimeMap[IGNORE_LIST].includes(i))).toEqual([]); + expect(vendorMap.sources.filter((_, i) => !vendorMap[IGNORE_LIST].includes(i))).toEqual([]); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/web-worker_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/web-worker_spec.ts new file mode 100644 index 000000000000..489e1a392aee --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/web-worker_spec.ts @@ -0,0 +1,183 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { join, logging, virtualFs } from '@angular-devkit/core'; +import { timer } from 'rxjs'; +import { debounceTime, map, switchMap, takeWhile, tap } from 'rxjs/operators'; +import { browserBuild, createArchitect, host, outputPath } from '../../../testing/test-utils'; + +describe('Browser Builder Web Worker support', () => { + const target = { project: 'app', target: 'build' }; + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + afterEach(async () => host.restore().toPromise()); + + const workerFiles: { [k: string]: string } = { + 'src/app/dep.ts': `export const foo = 'bar';`, + 'src/app/app.worker.ts': ` + /// + + import { foo } from './dep'; + console.log('hello from worker'); + addEventListener('message', ({ data }) => { + console.log('worker got message:', data); + if (data === 'hello') { + postMessage(foo); + } + }); + `, + 'src/main.ts': ` + import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + import { AppModule } from './app/app.module'; + platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)); + + const worker = new Worker(new URL('./app/app.worker', import.meta.url), { type: 'module' }); + worker.onmessage = ({ data }) => { + console.log('page got message:', data); + }; + worker.postMessage('hello'); + `, + // Make a new tsconfig for the *.worker.ts files. + // The final place for this tsconfig must take into consideration editor tooling, unit + // tests, and integration with other build targets. + './src/tsconfig.worker.json': ` + { + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/worker", + "lib": [ + "es2018", + "webworker" + ], + "types": [] + }, + "include": [ + "**/*.worker.ts", + ] + }`, + // Alter the app tsconfig to not include *.worker.ts files. + './src/tsconfig.app.json': ` + { + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/worker", + "types": [] + }, + "files": [ + "main.ts", + "polyfills.ts" + ] + }`, + }; + + it('bundles TS worker', async () => { + host.writeMultipleFiles(workerFiles); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const overrides = { webWorkerTsConfig: 'src/tsconfig.worker.json' }; + await browserBuild(architect, host, target, overrides, { logger }); + + // Worker bundle contains worker code. + const workerContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'src_app_app_worker_ts.js')), + ); + expect(workerContent).toContain('hello from worker'); + expect(workerContent).toContain('bar'); + + // Main bundle references worker. + const mainContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, 'main.js')), + ); + expect(mainContent).toContain('src_app_app_worker_ts'); + expect(logs.join().includes('Warning')).toBe(false, 'Should show no warnings.'); + }); + + it('minimizes and hashes worker', async () => { + host.writeMultipleFiles(workerFiles); + const overrides = { + webWorkerTsConfig: 'src/tsconfig.worker.json', + outputHashing: 'all', + optimization: true, + }; + await browserBuild(architect, host, target, overrides); + + // Worker bundle should have hash and minified code. + const workerBundle = host.fileMatchExists( + outputPath, + /src_app_app_worker_ts\.[0-9a-f]{16}\.js/, + ) as string; + expect(workerBundle).toBeTruthy('workerBundle should exist'); + const workerContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, workerBundle)), + ); + expect(workerContent).toContain('hello from worker'); + expect(workerContent).toContain('bar'); + expect(workerContent).toContain('"hello"===e&&postMessage'); + + // Main bundle should reference hashed worker bundle. + const mainBundle = host.fileMatchExists(outputPath, /main\.[0-9a-f]{16}\.js/) as string; + expect(mainBundle).toBeTruthy('mainBundle should exist'); + const mainContent = virtualFs.fileBufferToString( + host.scopedSync().read(join(outputPath, mainBundle)), + ); + expect(mainContent).toContain('src_app_app_worker_ts'); + }); + + it('rebuilds TS worker', async () => { + host.writeMultipleFiles(workerFiles); + const overrides = { + webWorkerTsConfig: 'src/tsconfig.worker.json', + watch: true, + }; + + let phase = 1; + const workerPath = join(outputPath, 'src_app_app_worker_ts.js'); + let workerContent = ''; + + // The current linux-based CI environments may not fully settled in regards to filesystem + // changes from previous tests which reuse the same directory and fileset. + // The initial delay helps mitigate false positive rebuild triggers in such scenarios. + const { run } = await timer(1000) + .pipe( + switchMap(() => architect.scheduleTarget(target, overrides)), + switchMap((run) => run.output.pipe(map((output) => ({ run, output })))), + debounceTime(1000), + tap(({ output }) => expect(output.success).toBe(true, 'build should succeed')), + tap(() => { + switch (phase) { + case 1: + // Original worker content should be there. + workerContent = virtualFs.fileBufferToString(host.scopedSync().read(workerPath)); + expect(workerContent).toContain('bar'); + // Change content of worker dependency. + host.writeMultipleFiles({ 'src/app/dep.ts': `export const foo = 'baz';` }); + phase = 2; + break; + + case 2: + workerContent = virtualFs.fileBufferToString(host.scopedSync().read(workerPath)); + // Worker content should have changed. + expect(workerContent).toContain('baz'); + phase = 3; + break; + } + }), + takeWhile(() => phase < 3), + ) + .toPromise(); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/works_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/works_spec.ts new file mode 100644 index 000000000000..fae71618c916 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/works_spec.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { describeBuilder } from '../../../testing'; +import { buildWebpackBrowser } from '../index'; + +const BROWSER_BUILDER_INFO = { + name: '@angular-devkit/build-angular:browser', + schemaPath: __dirname + '/../schema.json', +}; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('basic test', () => { + it('works', async () => { + // Provide a target and options for builder execution + harness.useTarget('build', { + outputPath: 'dist', + index: 'src/index.html', + main: 'src/main.ts', + polyfills: 'src/polyfills.ts', + tsConfig: 'src/tsconfig.app.json', + progress: false, + vendorChunk: true, + assets: ['src/favicon.ico', 'src/assets'], + styles: ['src/styles.css'], + scripts: [], + }); + + // Execute builder with above provided project, target, and options + await harness.executeOnce(); + + // Default files should be in outputPath. + expect(harness.hasFile('dist/runtime.js')).toBeTrue(); + expect(harness.hasFile('dist/main.js')).toBeTrue(); + expect(harness.hasFile('dist/polyfills.js')).toBeTrue(); + expect(harness.hasFile('dist/vendor.js')).toBeTrue(); + expect(harness.hasFile('dist/favicon.ico')).toBeTrue(); + expect(harness.hasFile('dist/styles.css')).toBeTrue(); + expect(harness.hasFile('dist/index.html')).toBeTrue(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts new file mode 100644 index 000000000000..7e488ddcbc2b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Behavior: "Browser support"', () => { + it('creates correct sourcemaps when downleveling async functions', async () => { + // Add a JavaScript file with async code + await harness.writeFile( + 'src/async-test.js', + 'async function testJs() { console.log("from-async-js-function"); }', + ); + + // Add an async function to the project as well as JavaScript file + // The type `Void123` is used as a unique identifier for the final sourcemap + // If sourcemaps are not properly propagated then it will not be in the final sourcemap + await harness.modifyFile( + 'src/main.ts', + (content) => + 'import "./async-test";\n' + + content + + '\ntype Void123 = void;' + + `\nasync function testApp(): Promise { console.log("from-async-app-function"); }`, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + vendorChunk: true, + sourceMap: { + scripts: true, + }, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); + harness.expectFile('dist/main.js.map').content.toContain('Promise'); + }); + + it('downlevels async functions ', async () => { + // Add an async function to the project + await harness.writeFile( + 'src/main.ts', + 'async function test(): Promise { console.log("from-async-function"); }', + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + vendorChunk: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); + harness.expectFile('dist/main.js').content.toContain('"from-async-function"'); + }); + + it('warns when IE is present in browserslist', async () => { + await harness.appendToFile( + '.browserslistrc', + ` + IE 9 + IE 11 + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: + `One or more browsers which are configured in the project's Browserslist ` + + 'configuration will be ignored as ES5 output is not supported by the Angular CLI.\n' + + 'Ignored browsers: ie 11, ie 9', + }), + ); + }); + + it('downlevels "for await...of"', async () => { + // Add an async function to the project + await harness.writeFile( + 'src/main.ts', + ` + (async () => { + for await (const o of [1, 2, 3]) { + console.log("for await...of"); + } + })(); + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + vendorChunk: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.not.toMatch(/\sawait\s/); + harness.expectFile('dist/main.js').content.toContain('"for await...of"'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/index_watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/index_watch_spec.ts new file mode 100644 index 000000000000..b023f6f3833f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/index_watch_spec.ts @@ -0,0 +1,53 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { BUILD_TIMEOUT, buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Behavior: "index is updated during watch mode"', () => { + it('index is watched in watch mode', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: { + harness.expectFile('dist/index.html').content.toContain('HelloWorldApp'); + harness.expectFile('dist/index.html').content.not.toContain('UpdatedPageTitle'); + + // Trigger rebuild + await harness.modifyFile('src/index.html', (s) => + s.replace('HelloWorldApp', 'UpdatedPageTitle'), + ); + break; + } + case 1: { + harness.expectFile('dist/index.html').content.toContain('UpdatedPageTitle'); + break; + } + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/localize_watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/localize_watch_spec.ts new file mode 100644 index 000000000000..6b1e00abab3b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/localize_watch_spec.ts @@ -0,0 +1,95 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { BUILD_TIMEOUT, buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Behavior: "localize works in watch mode"', () => { + beforeEach(() => { + harness.useProject('test', { + root: '.', + sourceRoot: 'src', + cli: { + cache: { + enabled: false, + }, + }, + i18n: { + locales: { + 'fr': 'src/locales/messages.fr.xlf', + }, + }, + }); + }); + + it('localize works in watch mode', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + localize: true, + }); + + await harness.writeFile( + 'src/app/app.component.html', + ` +

Hello {{ title }}!

+ `, + ); + + await harness.writeFile('src/locales/messages.fr.xlf', TRANSLATION_FILE_CONTENT); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: { + harness.expectFile('dist/fr/main.js').content.toContain('Bonjour'); + + // Trigger rebuild + await harness.appendToFile('src/app/app.component.html', '\n\n'); + break; + } + case 1: { + harness.expectFile('dist/fr/main.js').content.toContain('Bonjour'); + break; + } + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); + +const TRANSLATION_FILE_CONTENT = ` + + + + + + Bonjour ! + + src/app/app.component.html + 2,3 + + An introduction header for this sample + + + + +`; diff --git a/packages/angular_devkit/build_angular/src/browser/tests/behavior/rebuild-errors_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/rebuild-errors_spec.ts similarity index 91% rename from packages/angular_devkit/build_angular/src/browser/tests/behavior/rebuild-errors_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/rebuild-errors_spec.ts index 06d766cd23fa..cb553c366c80 100644 --- a/packages/angular_devkit/build_angular/src/browser/tests/behavior/rebuild-errors_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/rebuild-errors_spec.ts @@ -1,19 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable: no-big-function + import { logging } from '@angular-devkit/core'; import { concatMap, count, take, timeout } from 'rxjs/operators'; -import { buildWebpackBrowser } from '../../index'; +import { BUILD_TIMEOUT, buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { describe('Behavior: "Rebuild Error"', () => { - it('detects template errors with no AOT codegen or TS emit differences', async () => { harness.useTarget('build', { ...BASE_OPTIONS, @@ -33,7 +32,9 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { // Create a directive and add to application await harness.writeFile('src/app/dir.ts', goodDirectiveContents); - await harness.writeFile('src/app/app.module.ts', ` + await harness.writeFile( + 'src/app/app.module.ts', + ` import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @@ -50,22 +51,26 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { bootstrap: [AppComponent] }) export class AppModule { } - `); + `, + ); // Create app component that uses the directive - await harness.writeFile('src/app/app.component.ts', ` + await harness.writeFile( + 'src/app/app.component.ts', + ` import { Component } from '@angular/core' @Component({ selector: 'app-root', template: '', }) export class AppComponent { } - `); + `, + ); const buildCount = await harness .execute({ outputLogsOnFailure: false }) .pipe( - timeout(60000), + timeout(BUILD_TIMEOUT), concatMap(async ({ result, logs }, index) => { switch (index) { case 0: @@ -73,13 +78,16 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { // Update directive to use a different input type for 'foo' (number -> string) // Should cause a template error - await harness.writeFile('src/app/dir.ts', ` + await harness.writeFile( + 'src/app/dir.ts', + ` import { Directive, Input } from '@angular/core'; @Directive({ selector: 'dir' }) export class Dir { @Input() foo: string; } - `); + `, + ); break; case 1: @@ -150,13 +158,16 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { const typeErrorText = `Type 'number' is not assignable to type 'string'.`; // Create two directives and add to application - await harness.writeFile('src/app/dir.ts', ` + await harness.writeFile( + 'src/app/dir.ts', + ` import { Directive, Input } from '@angular/core'; @Directive({ selector: 'dir' }) export class Dir { @Input() foo: number; } - `); + `, + ); // Same selector with a different type on the `foo` property but initially no `@Input` const goodDirectiveContents = ` @@ -168,7 +179,9 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { `; await harness.writeFile('src/app/dir2.ts', goodDirectiveContents); - await harness.writeFile('src/app/app.module.ts', ` + await harness.writeFile( + 'src/app/app.module.ts', + ` import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @@ -187,22 +200,26 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { bootstrap: [AppComponent] }) export class AppModule { } - `); + `, + ); // Create app component that uses the directive - await harness.writeFile('src/app/app.component.ts', ` + await harness.writeFile( + 'src/app/app.component.ts', + ` import { Component } from '@angular/core' @Component({ selector: 'app-root', template: '', }) export class AppComponent { } - `); + `, + ); const buildCount = await harness .execute({ outputLogsOnFailure: false }) .pipe( - timeout(60000), + timeout(BUILD_TIMEOUT), concatMap(async ({ result, logs }, index) => { switch (index) { case 0: @@ -210,13 +227,16 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { // Update second directive to use string property `foo` as an Input // Should cause a template error - await harness.writeFile('src/app/dir2.ts', ` + await harness.writeFile( + 'src/app/dir2.ts', + ` import { Directive, Input } from '@angular/core'; @Directive({ selector: 'dir' }) export class Dir2 { @Input() foo: string; } - `); + `, + ); break; case 1: @@ -281,12 +301,13 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.useTarget('build', { ...BASE_OPTIONS, watch: true, + aot: false, }); const buildCount = await harness .execute({ outputLogsOnFailure: false }) .pipe( - timeout(30000), + timeout(BUILD_TIMEOUT), concatMap(async ({ result, logs }, index) => { switch (index) { case 0: diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/styles_unsupported_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/styles_unsupported_spec.ts new file mode 100644 index 000000000000..7ea328461f95 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/styles_unsupported_spec.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { logging } from '@angular-devkit/core'; +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Behavior: "Style Unsupported"', () => { + it('errors when importing a css file as an ECMA module (Webpack specific behaviour)', async () => { + await harness.writeFiles({ + 'src/test-style.css': '.test-a {color: red}', + 'src/main.ts': `import './test-style.css'`, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [], + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('./src/test-style.css:1:0 - Error'), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/allowed-common-js-dependencies_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/allowed-common-js-dependencies_spec.ts new file mode 100644 index 000000000000..720af1436b05 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/allowed-common-js-dependencies_spec.ts @@ -0,0 +1,130 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { logging } from '@angular-devkit/core'; +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "allowedCommonJsDependencies"', () => { + describe('given option is not set', () => { + for (const aot of [true, false]) { + it(`should show warning when depending on a Common JS bundle in ${ + aot ? 'AOT' : 'JIT' + } Mode`, async () => { + // Add a Common JS dependency + await harness.appendToFile('src/app/app.component.ts', `import 'bootstrap';`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: [], + aot, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching( + /Warning: .+app\.component\.ts depends on 'bootstrap'\. CommonJS or AMD dependencies/, + ), + }), + ); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('jquery'), + }), + 'Should not warn on transitive CommonJS packages which parent is also CommonJS.', + ); + }); + } + }); + + it('should not show warning when depending on a Common JS bundle which is allowed', async () => { + // Add a Common JS dependency + await harness.appendToFile( + 'src/app/app.component.ts', + ` + import 'bootstrap'; + import 'zone.js/dist/zone-error'; + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: ['bootstrap', 'zone.js'], + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(/CommonJS or AMD dependencies/), + }), + ); + }); + + it(`should not show warning when importing non global local data '@angular/common/locale/fr'`, async () => { + await harness.appendToFile( + 'src/app/app.component.ts', + `import '@angular/common/locales/fr';`, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: [], + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(/CommonJS or AMD dependencies/), + }), + ); + }); + + it('should not show warning in JIT for templateUrl and styleUrl when using paths', async () => { + await harness.modifyFile('tsconfig.json', (content) => { + return content.replace( + /"baseUrl": ".\/",/, + ` + "baseUrl": "./", + "paths": { + "@app/*": [ + "src/app/*" + ] + }, + `, + ); + }); + + await harness.modifyFile('src/app/app.module.ts', (content) => + content.replace('./app.component', '@app/app.component'), + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: [], + aot: false, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(/CommonJS or AMD dependencies/), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/assets_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/assets_spec.ts new file mode 100644 index 000000000000..0029b6b1e8ca --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/assets_spec.ts @@ -0,0 +1,390 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "assets"', () => { + beforeEach(async () => { + // Application code is not needed for asset tests + await harness.writeFile('src/main.ts', ''); + }); + + it('supports an empty array value', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + + it('supports mixing shorthand and longhand syntax', async () => { + await harness.writeFile('src/files/test.svg', ''); + await harness.writeFile('src/files/another.file', 'asset file'); + await harness.writeFile('src/extra.file', 'extra file'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: ['src/extra.file', { glob: '*', input: 'src/files', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/extra.file').content.toBe('extra file'); + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').content.toBe('asset file'); + }); + + describe('shorthand syntax', () => { + it('copies a single asset', async () => { + await harness.writeFile('src/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: ['src/test.svg'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + }); + + it('copies multiple assets', async () => { + await harness.writeFile('src/test.svg', ''); + await harness.writeFile('src/another.file', 'asset file'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: ['src/test.svg', 'src/another.file'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').content.toBe('asset file'); + }); + + it('copies an asset with directory and maintains directory in output', async () => { + await harness.writeFile('src/subdirectory/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: ['src/subdirectory/test.svg'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/subdirectory/test.svg').content.toBe(''); + }); + + it('does not fail if asset does not exist', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: ['src/test.svg'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').toNotExist(); + }); + + it('throws exception if asset path is not within project source root', async () => { + await harness.writeFile('test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: ['test.svg'], + }); + + const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + + expect(result).toBeUndefined(); + expect(error).toEqual( + jasmine.objectContaining({ + message: jasmine.stringMatching('path must start with the project source root'), + }), + ); + + harness.expectFile('dist/test.svg').toNotExist(); + }); + }); + + describe('longhand syntax', () => { + it('copies a single asset', async () => { + await harness.writeFile('src/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + }); + + it('copies multiple assets as separate entries', async () => { + await harness.writeFile('src/test.svg', ''); + await harness.writeFile('src/another.file', 'asset file'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [ + { glob: 'test.svg', input: 'src', output: '.' }, + { glob: 'another.file', input: 'src', output: '.' }, + ], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').content.toBe('asset file'); + }); + + it('copies multiple assets with a single entry glob pattern', async () => { + await harness.writeFile('src/test.svg', ''); + await harness.writeFile('src/another.file', 'asset file'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: '{test.svg,another.file}', input: 'src', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').content.toBe('asset file'); + }); + + it('copies multiple assets with a wildcard glob pattern', async () => { + await harness.writeFile('src/files/test.svg', ''); + await harness.writeFile('src/files/another.file', 'asset file'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: '*', input: 'src/files', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').content.toBe('asset file'); + }); + + it('copies multiple assets with a recursive wildcard glob pattern', async () => { + await harness.writeFiles({ + 'src/files/test.svg': '', + 'src/files/another.file': 'asset file', + 'src/files/nested/extra.file': 'extra file', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: '**/*', input: 'src/files', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').content.toBe('asset file'); + harness.expectFile('dist/nested/extra.file').content.toBe('extra file'); + }); + + it('automatically ignores "." prefixed files when using wildcard glob pattern', async () => { + await harness.writeFile('src/files/.gitkeep', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: '*', input: 'src/files', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/.gitkeep').toNotExist(); + }); + + it('supports ignoring a specific file when using a glob pattern', async () => { + await harness.writeFiles({ + 'src/files/test.svg': '', + 'src/files/another.file': 'asset file', + 'src/files/nested/extra.file': 'extra file', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: '**/*', input: 'src/files', output: '.', ignore: ['another.file'] }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').toNotExist(); + harness.expectFile('dist/nested/extra.file').content.toBe('extra file'); + }); + + it('supports ignoring with a glob pattern when using a glob pattern', async () => { + await harness.writeFiles({ + 'src/files/test.svg': '', + 'src/files/another.file': 'asset file', + 'src/files/nested/extra.file': 'extra file', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: '**/*', input: 'src/files', output: '.', ignore: ['**/*.file'] }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + harness.expectFile('dist/another.file').toNotExist(); + harness.expectFile('dist/nested/extra.file').toNotExist(); + }); + + it('copies an asset with directory and maintains directory in output', async () => { + await harness.writeFile('src/subdirectory/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'subdirectory/test.svg', input: 'src', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/subdirectory/test.svg').content.toBe(''); + }); + + it('does not fail if asset does not exist', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').toNotExist(); + }); + + it('uses project output path when output option is empty string', async () => { + await harness.writeFile('src/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: '' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + }); + + it('uses project output path when output option is "."', async () => { + await harness.writeFile('src/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: '.' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + }); + + it('uses project output path when output option is "/"', async () => { + await harness.writeFile('src/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: '/' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').content.toBe(''); + }); + + it('creates a project output sub-path when output option path does not exist', async () => { + await harness.writeFile('src/test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: 'subdirectory' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/subdirectory/test.svg').content.toBe(''); + }); + + it('throws exception if output option is not within project output path', async () => { + await harness.writeFile('test.svg', ''); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [{ glob: 'test.svg', input: 'src', output: '..' }], + }); + + const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + + expect(result).toBeUndefined(); + expect(error).toEqual( + jasmine.objectContaining({ + message: jasmine.stringMatching( + 'An asset cannot be written to a location outside of the output path', + ), + }), + ); + + harness.expectFile('dist/test.svg').toNotExist(); + }); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/bundle-budgets_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/bundle-budgets_spec.ts new file mode 100644 index 000000000000..a0ced423407c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/bundle-budgets_spec.ts @@ -0,0 +1,206 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { logging } from '@angular-devkit/core'; +import { lazyModuleFiles, lazyModuleFnImport } from '../../../../testing/test-utils'; +import { buildWebpackBrowser } from '../../index'; +import { Type } from '../../schema'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + const CSS_EXTENSIONS = ['css', 'scss', 'less']; + const BUDGET_NOT_MET_REGEXP = /Budget .+ was not met by/; + + describe('Option: "bundleBudgets"', () => { + it(`should not warn when size is below threshold`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + budgets: [{ type: Type.All, maximumWarning: '100mb' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + + it(`should error when size is above 'maximumError' threshold`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + budgets: [{ type: Type.All, maximumError: '100b' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(false); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + + it(`should warn when size is above 'maximumWarning' threshold`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + budgets: [{ type: Type.All, maximumWarning: '100b' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + + it(`should warn when lazy bundle is above 'maximumWarning' threshold`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + budgets: [{ type: Type.Bundle, name: 'lazy-lazy-module', maximumWarning: '100b' }], + }); + + await harness.writeFiles(lazyModuleFiles); + await harness.writeFiles(lazyModuleFnImport); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringMatching('lazy-lazy-module exceeded maximum budget'), + }), + ); + }); + + CSS_EXTENSIONS.forEach((ext) => { + it(`shows warnings for large component ${ext} when using 'anyComponentStyle' when AOT`, async () => { + const cssContent = ` + .foo { color: white; padding: 1px; } + .buz { color: white; padding: 2px; } + .bar { color: white; padding: 3px; } + `; + + await harness.writeFiles({ + [`src/app/app.component.${ext}`]: cssContent, + [`src/assets/foo.${ext}`]: cssContent, + [`src/styles.${ext}`]: cssContent, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('app.component.css', `app.component.${ext}`), + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + aot: true, + styles: [`src/styles.${ext}`], + budgets: [{ type: Type.AnyComponentStyle, maximumWarning: '1b' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringMatching(new RegExp(`Warning.+app.component.${ext}`)), + }), + ); + }); + }); + + describe(`should ignore '.map' files`, () => { + it(`when 'bundle' budget`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: true, + optimization: true, + extractLicenses: true, + budgets: [{ type: Type.Bundle, name: 'main', maximumError: '1mb' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + + it(`when 'intial' budget`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: true, + optimization: true, + extractLicenses: true, + budgets: [{ type: Type.Initial, name: 'main', maximumError: '1mb' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + + it(`when 'all' budget`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: true, + optimization: true, + extractLicenses: true, + budgets: [{ type: Type.All, maximumError: '1mb' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + + it(`when 'any' budget`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: true, + optimization: true, + extractLicenses: true, + budgets: [{ type: Type.Any, maximumError: '1mb' }], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(BUDGET_NOT_MET_REGEXP), + }), + ); + }); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/extract-licenses_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/extract-licenses_spec.ts new file mode 100644 index 000000000000..d8d4aca94a68 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/extract-licenses_spec.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "extractLicenses"', () => { + it(`should generate '3rdpartylicenses.txt' when 'extractLicenses' is true`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + extractLicenses: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/3rdpartylicenses.txt').content.toContain('MIT'); + }); + + it(`should not generate '3rdpartylicenses.txt' when 'extractLicenses' is false`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + extractLicenses: false, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/3rdpartylicenses.txt').toNotExist(); + }); + + it(`should generate '3rdpartylicenses.txt' when 'extractLicenses' is not set`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/3rdpartylicenses.txt').content.toContain('MIT'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/inline-critical_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/inline-critical_spec.ts new file mode 100644 index 000000000000..a8c86d84c9a8 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/inline-critical_spec.ts @@ -0,0 +1,163 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "inlineCritical"', () => { + beforeEach(async () => { + await harness.writeFile('src/styles.css', 'body { color: #000 }'); + }); + + it(`should extract critical css when 'inlineCritical' is true`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: { + scripts: false, + styles: { + minify: true, + inlineCritical: true, + }, + fonts: false, + }, + styles: ['src/styles.css'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness + .expectFile('dist/index.html') + .content.toContain( + ``, + ); + harness.expectFile('dist/index.html').content.toContain(`body{color:#000}`); + }); + + it(`should extract critical css when 'optimization' is unset`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + optimization: undefined, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness + .expectFile('dist/index.html') + .content.toContain( + ``, + ); + harness.expectFile('dist/index.html').content.toContain(`body{color:#000}`); + }); + + it(`should extract critical css when 'optimization' is true`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + optimization: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness + .expectFile('dist/index.html') + .content.toContain( + ``, + ); + harness.expectFile('dist/index.html').content.toContain(`body{color:#000}`); + }); + + it(`should not extract critical css when 'optimization' is false`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + optimization: false, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/index.html').content.not.toContain(` { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + optimization: { + scripts: false, + styles: { + minify: false, + inlineCritical: false, + }, + fonts: false, + }, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/index.html').content.not.toContain(` { + harness.useTarget('build', { + ...BASE_OPTIONS, + deployUrl: 'http://cdn.com/', + optimization: { + scripts: false, + styles: { + minify: true, + inlineCritical: true, + }, + fonts: false, + }, + styles: ['src/styles.css'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness + .expectFile('dist/index.html') + .content.toContain( + ``, + ); + harness.expectFile('dist/index.html').content.toContain(`body{color:#000}`); + }); + + it(`should extract critical css when using '@media all {}' and 'minify' is set to true`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + optimization: { + scripts: false, + styles: { + minify: true, + inlineCritical: true, + }, + fonts: false, + }, + }); + + await harness.writeFile('src/styles.css', '@media all { body { color: #000 } }'); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness + .expectFile('dist/index.html') + .content.toContain( + ``, + ); + harness.expectFile('dist/index.html').content.toContain(`body{color:#000}`); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/inline-style-language_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/inline-style-language_spec.ts new file mode 100644 index 000000000000..7bd712dd422b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/inline-style-language_spec.ts @@ -0,0 +1,138 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { buildWebpackBrowser } from '../../index'; +import { InlineStyleLanguage } from '../../schema'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "inlineStyleLanguage"', () => { + beforeEach(async () => { + // Setup application component with inline style property + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content + .replace('styleUrls', 'styles') + .replace('./app.component.css', '__STYLE_MARKER__'); + }); + }); + + for (const aot of [true, false]) { + describe(`[${aot ? 'AOT' : 'JIT'}]`, () => { + it('supports SCSS inline component styles when set to "scss"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + inlineStyleLanguage: InlineStyleLanguage.Scss, + aot, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('__STYLE_MARKER__', '$primary: indianred;\\nh1 { color: $primary; }'), + ); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.toContain('color: indianred'); + }); + + it('supports Sass inline component styles when set to "sass"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + inlineStyleLanguage: InlineStyleLanguage.Sass, + aot, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('__STYLE_MARKER__', '$primary: indianred\\nh1\\n\\tcolor: $primary'), + ); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.toContain('color: indianred'); + }); + + it('supports Less inline component styles when set to "less"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + inlineStyleLanguage: InlineStyleLanguage.Less, + aot, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('__STYLE_MARKER__', '@primary: indianred;\\nh1 { color: @primary; }'), + ); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.toContain('color: indianred'); + }); + + it('updates produced stylesheet in watch mode', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + main: 'src/main.ts', + inlineStyleLanguage: InlineStyleLanguage.Scss, + aot, + watch: true, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('__STYLE_MARKER__', '$primary: indianred;\\nh1 { color: $primary; }'), + ); + + const buildCount = await harness + .execute() + .pipe( + timeout(30000), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: + harness.expectFile('dist/main.js').content.toContain('color: indianred'); + harness.expectFile('dist/main.js').content.not.toContain('color: aqua'); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace( + '$primary: indianred;\\nh1 { color: $primary; }', + '$primary: aqua;\\nh1 { color: $primary; }', + ), + ); + break; + case 1: + harness.expectFile('dist/main.js').content.not.toContain('color: indianred'); + harness.expectFile('dist/main.js').content.toContain('color: aqua'); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace( + '$primary: aqua;\\nh1 { color: $primary; }', + '$primary: blue;\\nh1 { color: $primary; }', + ), + ); + break; + case 2: + harness.expectFile('dist/main.js').content.not.toContain('color: indianred'); + harness.expectFile('dist/main.js').content.not.toContain('color: aqua'); + harness.expectFile('dist/main.js').content.toContain('color: blue'); + break; + } + }), + take(3), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(3); + }); + }); + } + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/main_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/main_spec.ts similarity index 90% rename from packages/angular_devkit/build_angular/src/browser/tests/options/main_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/tests/options/main_spec.ts index 021039529a92..967bedded489 100644 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/main_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/main_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; @@ -22,7 +23,6 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.expectFile('dist/runtime.js').toExist(); harness.expectFile('dist/main.js').toExist(); - harness.expectFile('dist/vendor.js').toExist(); harness.expectFile('dist/index.html').toExist(); }); @@ -40,7 +40,6 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.expectFile('dist/runtime.js').toExist(); harness.expectFile('dist/main.js').toExist(); - harness.expectFile('dist/vendor.js').toNotExist(); harness.expectFile('dist/index.html').toExist(); harness.expectFile('dist/main.js').content.toContain(`console.log('main')`); @@ -61,7 +60,6 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.expectFile('dist/runtime.js').toNotExist(); harness.expectFile('dist/main.js').toNotExist(); - harness.expectFile('dist/vendor.js').toNotExist(); harness.expectFile('dist/index.html').toNotExist(); }); }); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/named-chunks_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/named-chunks_spec.ts similarity index 90% rename from packages/angular_devkit/build_angular/src/browser/tests/options/named-chunks_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/tests/options/named-chunks_spec.ts index 85fcb4796240..87d67ac17461 100644 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/named-chunks_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/named-chunks_spec.ts @@ -1,16 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; const MAIN_OUTPUT = 'dist/main.js'; const NAMED_LAZY_OUTPUT = 'dist/src_lazy-module_ts.js'; -const UNNAMED_LAZY_OUTPUT = 'dist/339.js'; +const UNNAMED_LAZY_OUTPUT = 'dist/459.js'; describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { describe('Option: "namedChunks"', () => { @@ -62,8 +63,8 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { expect(result?.success).toBe(true); harness.expectFile(MAIN_OUTPUT).toExist(); - harness.expectFile(NAMED_LAZY_OUTPUT).toExist(); - harness.expectFile(UNNAMED_LAZY_OUTPUT).toNotExist(); + harness.expectFile(NAMED_LAZY_OUTPUT).toNotExist(); + harness.expectFile(UNNAMED_LAZY_OUTPUT).toExist(); }); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/output-hashing_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/output-hashing_spec.ts new file mode 100644 index 000000000000..29d451fffd62 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/output-hashing_spec.ts @@ -0,0 +1,169 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { OutputHashing } from '../../schema'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "outputHashing"', () => { + beforeEach(async () => { + // Application code is not needed for asset tests + await harness.writeFile('src/main.ts', ''); + await harness.writeFile('src/polyfills.ts', ''); + }); + + it('hashes all filenames when set to "all"', async () => { + await harness.writeFile('src/styles.css', `h1 { background: url('https://github.com/spectrum.png')}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + polyfills: 'src/polyfills.ts', + outputHashing: OutputHashing.All, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{16}\.js$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{16}\.js$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{16}\.js$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.css$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{16}\.png$/)).toBeTrue(); + }); + + it(`doesn't hash any filenames when not set`, async () => { + await harness.writeFile('src/styles.css', `h1 { background: url('https://github.com/spectrum.png')}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: 'src/polyfills.ts', + styles: ['src/styles.css'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.css$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{16}\.png$/)).toBeFalse(); + }); + + it(`doesn't hash any filenames when set to "none"`, async () => { + await harness.writeFile('src/styles.css', `h1 { background: url('https://github.com/spectrum.png')}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + polyfills: 'src/polyfills.ts', + outputHashing: OutputHashing.None, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.css$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{16}\.png$/)).toBeFalse(); + }); + + it(`hashes CSS resources filenames only when set to "media"`, async () => { + await harness.writeFile('src/styles.css', `h1 { background: url('https://github.com/spectrum.png')}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + polyfills: 'src/polyfills.ts', + outputHashing: OutputHashing.Media, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.css$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{16}\.png$/)).toBeTrue(); + }); + + it(`hashes bundles filenames only when set to "bundles"`, async () => { + await harness.writeFile('src/styles.css', `h1 { background: url('https://github.com/spectrum.png')}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + polyfills: 'src/polyfills.ts', + outputHashing: OutputHashing.Bundles, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + expect(harness.hasFileMatch('dist', /runtime\.[0-9a-f]{16}\.js$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /main\.[0-9a-f]{16}\.js$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /polyfills\.[0-9a-f]{16}\.js$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.css$/)).toBeTrue(); + expect(harness.hasFileMatch('dist', /spectrum\.[0-9a-f]{16}\.png$/)).toBeFalse(); + }); + + it('does not hash non injected styles', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.All, + sourceMap: true, + styles: [ + { + input: 'src/styles.css', + inject: false, + }, + ], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.js$/)).toBeFalse(); + expect(harness.hasFileMatch('dist', /styles\.[0-9a-f]{16}\.js.map$/)).toBeFalse(); + harness.expectFile('dist/styles.css').toExist(); + harness.expectFile('dist/styles.css.map').toExist(); + }); + + it('does not override different files which has the same filenames when hashing is "none"', async () => { + await harness.writeFiles({ + 'src/styles.css': ` + h1 { background: url('https://github.com/test.svg')} + h2 { background: url('https://github.com/small/test.svg')} + `, + './src/test.svg': ` + Hello World + `, + './src/small/test.svg': ` + Hello World + `, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + outputHashing: OutputHashing.None, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness.expectFile('dist/test.svg').toExist(); + harness.expectFile('dist/small-test.svg').toExist(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/polyfills_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts similarity index 81% rename from packages/angular_devkit/build_angular/src/browser/tests/options/polyfills_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts index 0f3d3893ec72..fbf3e5ad850a 100644 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/polyfills_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; @@ -53,5 +54,16 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.expectFile('dist/polyfills.js').toNotExist(); }); + + it('resolves module specifiers in array', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: ['zone.js', 'zone.js/testing'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('dist/polyfills.js').toExist(); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/scripts_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/scripts_spec.ts similarity index 97% rename from packages/angular_devkit/build_angular/src/browser/tests/options/scripts_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/tests/options/scripts_spec.ts index c3ad0d0a0702..610c72263056 100644 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/scripts_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/scripts_spec.ts @@ -1,11 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-big-function + import { buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; @@ -93,18 +93,19 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { ); }); - it('throws an exception if script does not exist', async () => { + it('fails and shows an error if script does not exist', async () => { harness.useTarget('build', { ...BASE_OPTIONS, scripts: ['src/test-script-a.js'], }); - const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + const { result, logs } = await harness.executeOnce(); - expect(result).toBeUndefined(); - expect(error).toEqual( + expect(result?.success).toBeFalse(); + expect(logs).toContain( jasmine.objectContaining({ - message: jasmine.stringMatching(`Script file src/test-script-a.js does not exist.`), + level: 'error', + message: jasmine.stringMatching(`Can't resolve 'src/test-script-a.js'`), }), ); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/stats-json_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/stats-json_spec.ts new file mode 100644 index 000000000000..b085f31f96d1 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/stats-json_spec.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "statsJson"', () => { + beforeEach(async () => { + // Application code is not needed for stat JSON tests + await harness.writeFile('src/main.ts', ''); + }); + + it('generates a Webpack Stats file in output when true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + statsJson: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + if (harness.expectFile('dist/stats.json').toExist()) { + const content = harness.readFile('dist/stats.json'); + expect(() => JSON.parse(content)) + .withContext('Expected Webpack Stats file to be valid JSON.') + .not.toThrow(); + } + }); + + it('includes Webpack profiling information', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + statsJson: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + if (harness.expectFile('dist/stats.json').toExist()) { + const stats = JSON.parse(harness.readFile('dist/stats.json')); + expect(stats?.chunks?.[0]?.modules?.[0]?.profile?.building).toBeDefined(); + } + }); + + it('does not generate a Webpack Stats file in output when false', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + statsJson: false, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/stats.json').toNotExist(); + }); + + it('does not generate a Webpack Stats file in output when not present', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/stats.json').toNotExist(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/styles_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/styles_spec.ts new file mode 100644 index 000000000000..7a7ba7f52392 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/styles_spec.ts @@ -0,0 +1,435 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "styles"', () => { + beforeEach(async () => { + // Application code is not needed for styles tests + await harness.writeFile('src/main.ts', ''); + }); + + it('supports an empty array value', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').toNotExist(); + }); + + it('does not create an output styles file when option is not present', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').toNotExist(); + }); + + describe('shorthand syntax', () => { + it('processes a single style into a single output', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/test-style-a.css'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('processes multiple styles into a single output', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/test-style-a.css', 'src/test-style-b.css'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); + harness.expectFile('dist/styles.css').content.toContain('.test-b {color: green}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('preserves order of multiple styles in single output', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + 'src/test-style-c.css': '.test-c {color: blue}', + 'src/test-style-d.css': '.test-d {color: yellow}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [ + 'src/test-style-c.css', + 'src/test-style-d.css', + 'src/test-style-b.css', + 'src/test-style-a.css', + ], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness + .expectFile('dist/styles.css') + .content.toMatch( + /\.test-c {color: blue}[\s|\S]+\.test-d {color: yellow}[\s|\S]+\.test-b {color: green}[\s|\S]+\.test-a {color: red}/m, + ); + }); + + it('fails and shows an error if style does not exist', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/test-style-a.css'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(`Can't resolve 'src/test-style-a.css'`), + }), + ); + + harness.expectFile('dist/styles.css').toNotExist(); + }); + + it('shows the output style as a chunk entry in the logging output', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/test-style-a.css'], + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + expect(logs).toContain( + jasmine.objectContaining({ message: jasmine.stringMatching(/styles\.css.+\d+ bytes/) }), + ); + }); + }); + + describe('longhand syntax', () => { + it('processes a single style into a single output', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('processes a single style into a single output named with bundleName', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css', bundleName: 'extra' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('uses default bundleName when bundleName is empty string', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css', bundleName: '' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('processes multiple styles with no bundleName into a single output', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css' }, { input: 'src/test-style-b.css' }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); + harness.expectFile('dist/styles.css').content.toContain('.test-b {color: green}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('processes multiple styles with same bundleName into a single output', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [ + { input: 'src/test-style-a.css', bundleName: 'extra' }, + { input: 'src/test-style-b.css', bundleName: 'extra' }, + ], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); + harness.expectFile('dist/extra.css').content.toContain('.test-b {color: green}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('processes multiple styles with different bundleNames into separate outputs', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [ + { input: 'src/test-style-a.css', bundleName: 'extra' }, + { input: 'src/test-style-b.css', bundleName: 'other' }, + ], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); + harness.expectFile('dist/other.css').content.toContain('.test-b {color: green}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('preserves order of multiple styles in single output', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + 'src/test-style-c.css': '.test-c {color: blue}', + 'src/test-style-d.css': '.test-d {color: yellow}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [ + { input: 'src/test-style-c.css' }, + { input: 'src/test-style-d.css' }, + { input: 'src/test-style-b.css' }, + { input: 'src/test-style-a.css' }, + ], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness + .expectFile('dist/styles.css') + .content.toMatch( + /\.test-c {color: blue}[\s|\S]+\.test-d {color: yellow}[\s|\S]+\.test-b {color: green}[\s|\S]+\.test-a {color: red}/, + ); + }); + + it('preserves order of multiple styles with different bundleNames', async () => { + await harness.writeFiles({ + 'src/test-style-a.css': '.test-a {color: red}', + 'src/test-style-b.css': '.test-b {color: green}', + 'src/test-style-c.css': '.test-c {color: blue}', + 'src/test-style-d.css': '.test-d {color: yellow}', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [ + { input: 'src/test-style-c.css', bundleName: 'other' }, + { input: 'src/test-style-d.css', bundleName: 'extra' }, + { input: 'src/test-style-b.css', bundleName: 'extra' }, + { input: 'src/test-style-a.css', bundleName: 'other' }, + ], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness + .expectFile('dist/other.css') + .content.toMatch(/\.test-c {color: blue}[\s|\S]+\.test-a {color: red}/); + harness + .expectFile('dist/extra.css') + .content.toMatch(/\.test-d {color: yellow}[\s|\S]+\.test-b {color: green}/); + harness + .expectFile('dist/index.html') + .content.toMatch( + /\s*/, + ); + }); + + it('adds link element to index when inject is true', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css', inject: true }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/styles.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.toContain(''); + }); + + it('does not add link element to index when inject is false', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css', inject: false }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + // `inject: false` causes the bundleName to be the input file name + harness.expectFile('dist/test-style-a.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.not.toContain(''); + }); + + it('does not add link element to index with bundleName when inject is false', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css', bundleName: 'extra', inject: false }], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/extra.css').content.toContain('.test-a {color: red}'); + harness + .expectFile('dist/index.html') + .content.not.toContain(''); + }); + + it('shows the output style as a chunk entry in the logging output', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css' }], + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + expect(logs).toContain( + jasmine.objectContaining({ message: jasmine.stringMatching(/styles\.css.+\d+ bytes/) }), + ); + }); + + it('shows the output style as a chunk entry with bundleName in the logging output', async () => { + await harness.writeFile('src/test-style-a.css', '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [{ input: 'src/test-style-a.css', bundleName: 'extra' }], + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + expect(logs).toContain( + jasmine.objectContaining({ message: jasmine.stringMatching(/extra\.css.+\d+ bytes/) }), + ); + }); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/browser/tests/options/subresource-integrity_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/subresource-integrity_spec.ts similarity index 93% rename from packages/angular_devkit/build_angular/src/browser/tests/options/subresource-integrity_spec.ts rename to packages/angular_devkit/build_angular/src/builders/browser/tests/options/subresource-integrity_spec.ts index d7e31047b2b8..a6fa69578328 100644 --- a/packages/angular_devkit/build_angular/src/browser/tests/options/subresource-integrity_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/subresource-integrity_spec.ts @@ -1,11 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-big-function + import { logging } from '@angular-devkit/core'; import { buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; @@ -44,7 +44,7 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { const { result } = await harness.executeOnce(); expect(result?.success).toBe(true); - harness.expectFile('dist/index.html').content.toMatch(/integrity="\w+-[A-Za-z0-9\/\+=]+"/); + harness.expectFile('dist/index.html').content.toMatch(/integrity="\w+-[A-Za-z0-9/+=]+"/); }); it(`does not issue a warning when 'true' and 'scripts' is set.`, async () => { @@ -59,7 +59,7 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { const { result, logs } = await harness.executeOnce(); expect(result?.success).toBe(true); - harness.expectFile('dist/index.html').content.toMatch(/integrity="\w+-[A-Za-z0-9\/\+=]+"/); + harness.expectFile('dist/index.html').content.toMatch(/integrity="\w+-[A-Za-z0-9/+=]+"/); expect(logs).not.toContain( jasmine.objectContaining({ message: jasmine.stringMatching(/subresource-integrity/), diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/tsconfig_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/tsconfig_spec.ts new file mode 100644 index 000000000000..77bc192af4b9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/tsconfig_spec.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "tsConfig"', () => { + it('throws an exception when TypeScript Configuration file does not exist', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + tsConfig: 'src/missing.json', + }); + + const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + + expect(result).toBeUndefined(); + expect(error).toEqual( + jasmine.objectContaining({ + message: jasmine.stringMatching('no such file or directory'), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/verbose_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/verbose_spec.ts new file mode 100644 index 000000000000..598a2b44a21e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/verbose_spec.ts @@ -0,0 +1,160 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { logging } from '@angular-devkit/core'; +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { BUILD_TIMEOUT, buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +// The below plugin is only enabled when verbose option is set to true. +const VERBOSE_LOG_TEXT = 'LOG from webpack.'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "verbose"', () => { + beforeEach(async () => { + // Application code is not needed for verbose output + await harness.writeFile('src/main.ts', ''); + }); + + it('should include verbose logs when true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + verbose: true, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(VERBOSE_LOG_TEXT), + }), + ); + }); + + it('should not include verbose logs when undefined', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + verbose: undefined, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(VERBOSE_LOG_TEXT), + }), + ); + }); + + it('should not include verbose logs when false', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + verbose: false, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(VERBOSE_LOG_TEXT), + }), + ); + }); + + it('should list modified files when verbose is set to true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + verbose: true, + watch: true, + }); + + await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result, logs }, index) => { + expect(result?.success).toBeTrue(); + + switch (index) { + case 0: + // Amend file + await harness.appendToFile('/src/main.ts', ' '); + break; + case 1: + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching( + /angular\.watch-files-logs-plugin\n\s+Modified files:\n.+main\.ts/, + ), + }), + ); + + break; + } + }), + take(2), + count(), + ) + .toPromise(); + }); + + it('should not include error stacktraces when false', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + verbose: false, + styles: ['./src/styles.scss'], + }); + + // Create a compilatation error. + await harness.writeFile('./src/styles.scss', `@import 'https://github.com/invalid-module';`); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(`SassError: Can't find stylesheet to import.`), + }), + ); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('styles.scss.webpack'), + }), + ); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('at Object.loader'), + }), + ); + }); + + it('should include error stacktraces when true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + verbose: true, + styles: ['./src/styles.scss'], + }); + + // Create a compilatation error. + await harness.writeFile('./src/styles.scss', `@import 'https://github.com/invalid-module';`); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('styles.scss.webpack'), + }), + ); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('at Object.loader'), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/watch_spec.ts new file mode 100644 index 000000000000..d9cb3f57399a --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/watch_spec.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Option: "watch"', () => { + beforeEach(async () => { + // Application code is not needed for these tests + await harness.writeFile('src/main.ts', ''); + }); + + it('does not wait for file changes when false', (done) => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: false, + }); + + // If the build waits then it will timeout with the custom timeout. + // A single build should not take more than 15 seconds. + let count = 0; + harness + .execute() + .pipe(timeout(15000)) + .subscribe({ + complete() { + expect(count).toBe(1); + done(); + }, + next({ result }) { + count++; + expect(result?.success).toBe(true); + }, + error(error) { + done.fail(error); + }, + }); + }); + + it('does not wait for file changes when not present', (done) => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // If the build waits then it will timeout with the custom timeout. + // A single build should not take more than 15 seconds. + let count = 0; + harness + .execute() + .pipe(timeout(15000)) + .subscribe({ + complete() { + expect(count).toBe(1); + done(); + }, + next({ result }) { + count++; + expect(result?.success).toBe(true); + }, + error(error) { + done.fail(error); + }, + }); + }); + + it('watches for file changes when true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + main: 'src/main.ts', + watch: true, + }); + + const buildCount = await harness + .execute() + .pipe( + timeout(30000), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: + harness.expectFile('dist/main.js').content.not.toContain('abcd1234'); + + await harness.modifyFile( + 'src/main.ts', + (content) => content + 'console.log("abcd1234");', + ); + break; + case 1: + harness.expectFile('dist/main.js').content.toContain('abcd1234'); + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/setup.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/setup.ts new file mode 100644 index 000000000000..d9660f06b913 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/setup.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Schema } from '../schema'; + +export { describeBuilder } from '../../../testing'; + +export const BROWSER_BUILDER_INFO = Object.freeze({ + name: '@angular-devkit/build-angular:browser', + schemaPath: __dirname + '/../schema.json', +}); + +/** + * Contains all required browser builder fields. + * Also disables progress reporting to minimize logging output. + */ +export const BASE_OPTIONS = Object.freeze({ + index: 'src/index.html', + main: 'src/main.ts', + outputPath: 'dist', + tsConfig: 'src/tsconfig.app.json', + progress: false, + + // Disable optimizations + optimization: false, + buildOptimizer: false, +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts new file mode 100644 index 000000000000..637f0f75229e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts @@ -0,0 +1,431 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect'; +import { + DevServerBuildOutput, + WebpackLoggingCallback, + runWebpackDevServer, +} from '@angular-devkit/build-webpack'; +import { json, tags } from '@angular-devkit/core'; +import * as path from 'path'; +import { Observable, from } from 'rxjs'; +import { concatMap, switchMap } from 'rxjs/operators'; +import * as url from 'url'; +import webpack from 'webpack'; +import webpackDevServer from 'webpack-dev-server'; +import { ExecutionTransformer } from '../../transforms'; +import { normalizeOptimization } from '../../utils'; +import { checkPort } from '../../utils/check-port'; +import { colors } from '../../utils/color'; +import { I18nOptions, loadTranslations } from '../../utils/i18n-options'; +import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator'; +import { createTranslationLoader } from '../../utils/load-translations'; +import { NormalizedCachedOptions, normalizeCacheOptions } from '../../utils/normalize-cache'; +import { generateEntryPoints } from '../../utils/package-chunk-sort'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { assertCompatibleAngularVersion } from '../../utils/version'; +import { + generateI18nBrowserWebpackConfigFromContext, + getIndexInputFile, + getIndexOutputFile, +} from '../../utils/webpack-browser-config'; +import { addError, addWarning } from '../../utils/webpack-diagnostics'; +import { getCommonConfig, getDevServerConfig, getStylesConfig } from '../../webpack/configs'; +import { IndexHtmlWebpackPlugin } from '../../webpack/plugins/index-html-webpack-plugin'; +import { ServiceWorkerPlugin } from '../../webpack/plugins/service-worker-plugin'; +import { + BuildEventStats, + createWebpackLoggingCallback, + generateBuildEventStats, +} from '../../webpack/utils/stats'; +import { Schema as BrowserBuilderSchema, OutputHashing } from '../browser/schema'; +import { Schema } from './schema'; + +export type DevServerBuilderOptions = Schema; + +/** + * @experimental Direct usage of this type is considered experimental. + */ +export type DevServerBuilderOutput = DevServerBuildOutput & { + baseUrl: string; + stats: BuildEventStats; +}; + +/** + * Reusable implementation of the Angular Webpack development server builder. + * @param options Dev Server options. + * @param context The build context. + * @param transforms A map of transforms that can be used to hook into some logic (such as + * transforming webpack configuration before passing it to webpack). + * + * @experimental Direct usage of this function is considered experimental. + */ +// eslint-disable-next-line max-lines-per-function +export function serveWebpackBrowser( + options: DevServerBuilderOptions, + context: BuilderContext, + transforms: { + webpackConfiguration?: ExecutionTransformer; + logging?: WebpackLoggingCallback; + indexHtml?: IndexHtmlTransform; + } = {}, +): Observable { + // Check Angular version. + const { logger, workspaceRoot } = context; + assertCompatibleAngularVersion(workspaceRoot); + + const browserTarget = targetFromTargetString(options.browserTarget); + + async function setup(): Promise<{ + browserOptions: BrowserBuilderSchema; + webpackConfig: webpack.Configuration; + projectRoot: string; + }> { + const projectName = context.target?.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + options.port = await checkPort(options.port ?? 4200, options.host || 'localhost'); + + if (options.hmr) { + logger.warn(tags.stripIndents`NOTICE: Hot Module Replacement (HMR) is enabled for the dev server. + See https://webpack.js.org/guides/hot-module-replacement for information on working with HMR for Webpack.`); + } + + if ( + !options.disableHostCheck && + options.host && + !/^127\.\d+\.\d+\.\d+/g.test(options.host) && + options.host !== 'localhost' + ) { + logger.warn(tags.stripIndent` + Warning: This is a simple server for use in testing or debugging Angular applications + locally. It hasn't been reviewed for security issues. + + Binding this server to an open connection can result in compromising your application or + computer. Using a different host than the one passed to the "--host" flag might result in + websocket connection issues. You might need to use "--disable-host-check" if that's the + case. + `); + } + + if (options.disableHostCheck) { + logger.warn(tags.oneLine` + Warning: Running a server with --disable-host-check is a security risk. + See https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a + for more information. + `); + } + // Get the browser configuration from the target name. + const rawBrowserOptions = (await context.getTargetOptions(browserTarget)) as json.JsonObject & + BrowserBuilderSchema; + + if (rawBrowserOptions.outputHashing && rawBrowserOptions.outputHashing !== OutputHashing.None) { + // Disable output hashing for dev build as this can cause memory leaks + // See: https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405 + rawBrowserOptions.outputHashing = OutputHashing.None; + logger.warn(`Warning: 'outputHashing' option is disabled when using the dev-server.`); + } + + const metadata = await context.getProjectMetadata(projectName); + const cacheOptions = normalizeCacheOptions(metadata, context.workspaceRoot); + + const browserName = await context.getBuilderNameForTarget(browserTarget); + + // Issue a warning that the dev-server does not currently support the experimental esbuild- + // based builder and will use Webpack. + if (browserName === '@angular-devkit/build-angular:browser-esbuild') { + logger.warn( + 'WARNING: The experimental esbuild-based builder is not currently supported ' + + 'by the dev-server. The stable Webpack-based builder will be used instead.', + ); + } + + const browserOptions = (await context.validateOptions( + { + ...rawBrowserOptions, + watch: options.watch, + verbose: options.verbose, + // In dev server we should not have budgets because of extra libs such as socks-js + budgets: undefined, + } as json.JsonObject & BrowserBuilderSchema, + browserName, + )) as json.JsonObject & BrowserBuilderSchema; + + const { styles, scripts } = normalizeOptimization(browserOptions.optimization); + if (scripts || styles.minify) { + logger.error(tags.stripIndents` + **************************************************************************************** + This is a simple server for use in testing or debugging Angular applications locally. + It hasn't been reviewed for security issues. + + DON'T USE IT FOR PRODUCTION! + **************************************************************************************** + `); + } + + const { config, projectRoot, i18n } = await generateI18nBrowserWebpackConfigFromContext( + browserOptions, + context, + (wco) => [getDevServerConfig(wco), getCommonConfig(wco), getStylesConfig(wco)], + options, + ); + + if (!config.devServer) { + throw new Error('Webpack Dev Server configuration was not set.'); + } + + let locale: string | undefined; + if (i18n.shouldInline) { + // Dev-server only supports one locale + locale = [...i18n.inlineLocales][0]; + } else if (i18n.hasDefinedSourceLocale) { + // use source locale if not localizing + locale = i18n.sourceLocale; + } + + let webpackConfig = config; + + // If a locale is defined, setup localization + if (locale) { + if (i18n.inlineLocales.size > 1) { + throw new Error( + 'The development server only supports localizing a single locale per build.', + ); + } + + await setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions, context); + } + + if (transforms.webpackConfiguration) { + webpackConfig = await transforms.webpackConfiguration(webpackConfig); + } + + webpackConfig.plugins ??= []; + + if (browserOptions.index) { + const { scripts = [], styles = [], baseHref } = browserOptions; + const entrypoints = generateEntryPoints({ + scripts, + styles, + // The below is needed as otherwise HMR for CSS will break. + // styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null. + // https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39 + isHMREnabled: !!webpackConfig.devServer?.hot, + }); + + webpackConfig.plugins.push( + new IndexHtmlWebpackPlugin({ + indexPath: path.resolve(workspaceRoot, getIndexInputFile(browserOptions.index)), + outputPath: getIndexOutputFile(browserOptions.index), + baseHref, + entrypoints, + deployUrl: browserOptions.deployUrl, + sri: browserOptions.subresourceIntegrity, + cache: cacheOptions, + postTransform: transforms.indexHtml, + optimization: normalizeOptimization(browserOptions.optimization), + crossOrigin: browserOptions.crossOrigin, + lang: locale, + }), + ); + } + + if (browserOptions.serviceWorker) { + webpackConfig.plugins.push( + new ServiceWorkerPlugin({ + baseHref: browserOptions.baseHref, + root: context.workspaceRoot, + projectRoot, + ngswConfigPath: browserOptions.ngswConfigPath, + }), + ); + } + + return { + browserOptions, + webpackConfig, + projectRoot, + }; + } + + return from(setup()).pipe( + switchMap(({ browserOptions, webpackConfig }) => { + return runWebpackDevServer(webpackConfig, context, { + logging: transforms.logging || createWebpackLoggingCallback(browserOptions, logger), + webpackFactory: require('webpack') as typeof webpack, + webpackDevServerFactory: require('webpack-dev-server') as typeof webpackDevServer, + }).pipe( + concatMap(async (buildEvent, index) => { + const webpackRawStats = buildEvent.webpackStats; + if (!webpackRawStats) { + throw new Error('Webpack stats build result is required.'); + } + + // Resolve serve address. + const publicPath = webpackConfig.devServer?.devMiddleware?.publicPath; + + const serverAddress = url.format({ + protocol: options.ssl ? 'https' : 'http', + hostname: options.host === '0.0.0.0' ? 'localhost' : options.host, + port: buildEvent.port, + pathname: typeof publicPath === 'string' ? publicPath : undefined, + }); + + if (index === 0) { + logger.info( + '\n' + + tags.oneLine` + ** + Angular Live Development Server is listening on ${options.host}:${buildEvent.port}, + open your browser on ${serverAddress} + ** + ` + + '\n', + ); + + if (options.open) { + const open = (await import('open')).default; + await open(serverAddress); + } + } + + if (buildEvent.success) { + logger.info(`\n${colors.greenBright(colors.symbols.check)} Compiled successfully.`); + } else { + logger.info(`\n${colors.redBright(colors.symbols.cross)} Failed to compile.`); + } + + return { + ...buildEvent, + baseUrl: serverAddress, + stats: generateBuildEventStats(webpackRawStats, browserOptions), + } as DevServerBuilderOutput; + }), + ); + }), + ); +} + +async function setupLocalize( + locale: string, + i18n: I18nOptions, + browserOptions: BrowserBuilderSchema, + webpackConfig: webpack.Configuration, + cacheOptions: NormalizedCachedOptions, + context: BuilderContext, +) { + const localeDescription = i18n.locales[locale]; + + // Modify main entrypoint to include locale data + if ( + localeDescription?.dataPath && + typeof webpackConfig.entry === 'object' && + !Array.isArray(webpackConfig.entry) && + webpackConfig.entry['main'] + ) { + if (Array.isArray(webpackConfig.entry['main'])) { + webpackConfig.entry['main'].unshift(localeDescription.dataPath); + } else { + webpackConfig.entry['main'] = [ + localeDescription.dataPath, + webpackConfig.entry['main'] as string, + ]; + } + } + + let missingTranslationBehavior = browserOptions.i18nMissingTranslation || 'ignore'; + let translation = localeDescription?.translation || {}; + + if (locale === i18n.sourceLocale) { + missingTranslationBehavior = 'ignore'; + translation = {}; + } + + const i18nLoaderOptions = { + locale, + missingTranslationBehavior, + translation: i18n.shouldInline ? translation : undefined, + translationFiles: localeDescription?.files.map((file) => + path.resolve(context.workspaceRoot, file.path), + ), + }; + + const i18nRule: webpack.RuleSetRule = { + test: /\.[cm]?[tj]sx?$/, + enforce: 'post', + use: [ + { + loader: require.resolve('../../babel/webpack-loader'), + options: { + cacheDirectory: + (cacheOptions.enabled && path.join(cacheOptions.path, 'babel-dev-server-i18n')) || + false, + cacheIdentifier: JSON.stringify({ + locale, + translationIntegrity: localeDescription?.files.map((file) => file.integrity), + }), + i18n: i18nLoaderOptions, + }, + }, + ], + }; + + // Get the rules and ensure the Webpack configuration is setup properly + const rules = webpackConfig.module?.rules || []; + if (!webpackConfig.module) { + webpackConfig.module = { rules }; + } else if (!webpackConfig.module.rules) { + webpackConfig.module.rules = rules; + } + + rules.push(i18nRule); + + // Add a plugin to reload translation files on rebuilds + const loader = await createTranslationLoader(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + webpackConfig.plugins!.push({ + apply: (compiler: webpack.Compiler) => { + compiler.hooks.thisCompilation.tap('build-angular', (compilation) => { + if (i18n.shouldInline && i18nLoaderOptions.translation === undefined) { + // Reload translations + loadTranslations( + locale, + localeDescription, + context.workspaceRoot, + loader, + { + warn(message) { + addWarning(compilation, message); + }, + error(message) { + addError(compilation, message); + }, + }, + undefined, + browserOptions.i18nDuplicateTranslation, + ); + + i18nLoaderOptions.translation = localeDescription.translation ?? {}; + } + + compilation.hooks.finishModules.tap('build-angular', () => { + // After loaders are finished, clear out the now unneeded translations + i18nLoaderOptions.translation = undefined; + }); + }); + }, + }); +} + +export default createBuilder(serveWebpackBrowser); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json b/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json new file mode 100644 index 000000000000..58bc9f68948f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Dev Server Target", + "description": "Dev Server target options for Build Facade.", + "type": "object", + "properties": { + "browserTarget": { + "type": "string", + "description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "port": { + "type": "number", + "description": "Port to listen on.", + "default": 4200 + }, + "host": { + "type": "string", + "description": "Host to listen on.", + "default": "localhost" + }, + "proxyConfig": { + "type": "string", + "description": "Proxy configuration file. For more information, see https://angular.io/guide/build#proxying-to-a-backend-server." + }, + "ssl": { + "type": "boolean", + "description": "Serve using HTTPS.", + "default": false + }, + "sslKey": { + "type": "string", + "description": "SSL key to use for serving HTTPS." + }, + "sslCert": { + "type": "string", + "description": "SSL certificate to use for serving HTTPS." + }, + "headers": { + "type": "object", + "description": "Custom HTTP headers to be added to all responses.", + "propertyNames": { + "pattern": "^[-_A-Za-z0-9]+$" + }, + "additionalProperties": { + "type": "string" + } + }, + "open": { + "type": "boolean", + "description": "Opens the url in default browser.", + "default": false, + "alias": "o" + }, + "verbose": { + "type": "boolean", + "description": "Adds more details to output logging." + }, + "liveReload": { + "type": "boolean", + "description": "Whether to reload the page on change, using live-reload.", + "default": true + }, + "publicHost": { + "type": "string", + "description": "The URL that the browser client (or live-reload client, if enabled) should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies." + }, + "allowedHosts": { + "type": "array", + "description": "List of hosts that are allowed to access the dev server.", + "default": [], + "items": { + "type": "string" + } + }, + "servePath": { + "type": "string", + "description": "The pathname where the application will be served." + }, + "disableHostCheck": { + "type": "boolean", + "description": "Don't verify connected clients are part of allowed hosts.", + "default": false + }, + "hmr": { + "type": "boolean", + "description": "Enable hot module replacement.", + "default": false + }, + "watch": { + "type": "boolean", + "description": "Rebuild on change.", + "default": true + }, + "poll": { + "type": "number", + "description": "Enable and define the file watching poll time period in milliseconds." + } + }, + "additionalProperties": false, + "required": ["browserTarget"] +} diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/specs/hmr_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/hmr_spec.ts new file mode 100644 index 000000000000..41a493ae8a36 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/hmr_spec.ts @@ -0,0 +1,197 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect, BuilderRun } from '@angular-devkit/architect'; +// eslint-disable-next-line import/no-extraneous-dependencies +import puppeteer, { Browser, Page } from 'puppeteer'; +import { debounceTime, switchMap, take } from 'rxjs/operators'; +import { createArchitect, host } from '../../../testing/test-utils'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare const document: any; +declare const getComputedStyle: any; +/* eslint-enable @typescript-eslint/no-explicit-any */ + +describe('Dev Server Builder HMR', () => { + const target = { project: 'app', target: 'serve' }; + const overrides = { hmr: true, watch: true, port: 0 }; + let architect: Architect; + let browser: Browser; + let page: Page; + let logs: string[] = []; + let runs: BuilderRun[]; + + beforeAll(async () => { + browser = await puppeteer.launch({ + // MacOSX users need to set the local binary manually because Chrome has lib files with + // spaces in them which Bazel does not support in runfiles + // See: https://github.com/angular/angular-cli/pull/17624 + // eslint-disable-next-line max-len + // executablePath: '/Users//git/angular-cli/node_modules/puppeteer/.local-chromium/mac-800071/chrome-mac/Chromium.app/Contents/MacOS/Chromium', + args: ['--no-sandbox', '--disable-gpu'], + }); + }); + + afterAll(async () => { + await browser.close(); + }); + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + + logs = []; + runs = []; + page = await browser.newPage(); + page.on('console', (msg) => logs.push(msg.text())); + + host.writeMultipleFiles({ + 'src/app/app.component.html': ` +

{{title}}

+ + + + + + + `, + }); + }); + + afterEach(async () => { + await host.restore().toPromise(); + await page.close(); + await Promise.all(runs.map((r) => r.stop())); + }); + + it('works for CSS changes', async () => { + const run = await architect.scheduleTarget(target, overrides); + runs.push(run); + + let buildCount = 0; + await run.output + .pipe( + debounceTime(1000), + switchMap(async (buildEvent) => { + expect(buildEvent.success).toBe(true); + const url = buildEvent.baseUrl as string; + switch (buildCount) { + case 0: + await page.goto(url); + expect(logs).toContain('[HMR] Waiting for update signal from WDS...'); + host.writeMultipleFiles({ + 'src/styles.css': 'p { color: rgb(255, 255, 0) }', + }); + break; + case 1: + expect(logs).toContain('[HMR] Updated modules:'); + expect(logs).toContain(`[HMR] css reload %s ${url}styles.css`); + expect(logs).toContain('[HMR] App is up to date.'); + + const pTagColor = await page.evaluate(() => { + const el = document.querySelector('p'); + + return getComputedStyle(el).color; + }); + + expect(pTagColor).toBe('rgb(255, 255, 0)'); + break; + } + + logs = []; + buildCount++; + }), + take(2), + ) + .toPromise(); + }); + + it('works for TS changes', async () => { + const run = await architect.scheduleTarget(target, overrides); + runs.push(run); + + let buildCount = 0; + await run.output + .pipe( + debounceTime(1000), + switchMap(async (buildEvent) => { + expect(buildEvent.success).toBe(true); + const url = buildEvent.baseUrl as string; + + switch (buildCount) { + case 0: + await page.goto(url); + expect(logs).toContain('[HMR] Waiting for update signal from WDS...'); + host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-hmr'`); + break; + case 1: + expect(logs).toContain('[HMR] Updated modules:'); + expect(logs).toContain('[HMR] App is up to date.'); + + const innerText = await page.evaluate(() => document.querySelector('p').innerText); + expect(innerText).toBe('app-hmr'); + break; + } + + logs = []; + buildCount++; + }), + take(2), + ) + .toPromise(); + }); + + it('restores input and select values', async () => { + const run = await architect.scheduleTarget(target, overrides); + runs.push(run); + + let buildCount = 0; + await run.output + .pipe( + debounceTime(1000), + switchMap(async (buildEvent) => { + expect(buildEvent.success).toBe(true); + const url = buildEvent.baseUrl as string; + switch (buildCount) { + case 0: + await page.goto(url); + expect(logs).toContain('[HMR] Waiting for update signal from WDS...'); + await page.evaluate(() => { + document.querySelector('input.visible').value = 'input value'; + document.querySelector('select').value = 'two'; + }); + + host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-hmr'`); + break; + case 1: + expect(logs).toContain('[HMR] Updated modules:'); + expect(logs).toContain('[HMR] App is up to date.'); + expect(logs).toContain('[NG HMR] Restoring input/textarea values.'); + expect(logs).toContain('[NG HMR] Restoring selected options.'); + + const inputValue = await page.evaluate( + () => document.querySelector('input.visible').value, + ); + expect(inputValue).toBe('input value'); + + const selectValue = await page.evaluate(() => document.querySelector('select').value); + expect(selectValue).toBe('two'); + break; + } + + logs = []; + buildCount++; + }), + take(2), + ) + .toPromise(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/specs/index_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/index_spec.ts new file mode 100644 index 000000000000..c654826bff01 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/index_spec.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { DevServerBuilderOutput } from '@angular-devkit/build-angular'; +import { workspaces } from '@angular-devkit/core'; +import fetch from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Dev Server Builder index', () => { + const targetSpec = { project: 'app', target: 'serve' }; + + beforeEach(async () => host.initialize().toPromise()); + afterEach(async () => host.restore().toPromise()); + + it('sets HTML lang attribute with the active locale', async () => { + const locale = 'fr'; + const { workspace } = await workspaces.readWorkspace( + host.root(), + workspaces.createWorkspaceHost(host), + ); + const app = workspace.projects.get('app'); + if (!app) { + fail('Test application "app" not found.'); + + return; + } + + app.extensions['i18n'] = { + locales: { + [locale]: [], + }, + }; + + const target = app.targets.get('build'); + if (!target) { + fail('Test application "app" target "build" not found.'); + + return; + } + if (!target.options) { + target.options = {}; + } + target.options.localize = [locale]; + + await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); + + const architect = (await createArchitect(host.root())).architect; + const run = await architect.scheduleTarget(targetSpec, { port: 0 }); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + const response = await fetch(output.baseUrl); + expect(await response.text()).toContain(`lang="${locale}"`); + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/ssl_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/ssl_spec.ts similarity index 93% rename from packages/angular_devkit/build_angular/src/dev-server/ssl_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/specs/ssl_spec.ts index f352ba0092c2..ab3583e7dae6 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/ssl_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/ssl_spec.ts @@ -1,17 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect, BuilderRun } from '@angular-devkit/architect'; import { DevServerBuilderOutput } from '@angular-devkit/build-angular'; import { tags } from '@angular-devkit/core'; import * as https from 'https'; -import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies -import { createArchitect, host } from '../test-utils'; - +import fetch from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies +import { createArchitect, host } from '../../../testing/test-utils'; describe('Dev Server Builder ssl', () => { const target = { project: 'app', target: 'serve' }; @@ -26,13 +26,13 @@ describe('Dev Server Builder ssl', () => { }); afterEach(async () => { await host.restore().toPromise(); - await Promise.all(runs.map(r => r.stop())); + await Promise.all(runs.map((r) => r.stop())); }); it('works', async () => { const run = await architect.scheduleTarget(target, { ssl: true, port: 0 }); runs.push(run); - const output = await run.result as DevServerBuilderOutput; + const output = (await run.result) as DevServerBuilderOutput; expect(output.success).toBe(true); expect(output.baseUrl).toMatch(/^https:\/\/localhost:\d+\//); @@ -109,7 +109,7 @@ describe('Dev Server Builder ssl', () => { const run = await architect.scheduleTarget(target, overrides); runs.push(run); - const output = await run.result as DevServerBuilderOutput; + const output = (await run.result) as DevServerBuilderOutput; expect(output.success).toBe(true); expect(output.baseUrl).toMatch(/^https:\/\/localhost:\d+\//); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/specs/works_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/works_spec.ts new file mode 100644 index 000000000000..edac384cca34 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/specs/works_spec.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect, BuilderRun } from '@angular-devkit/architect'; +import { DevServerBuilderOutput } from '@angular-devkit/build-angular'; +import { normalize, virtualFs } from '@angular-devkit/core'; +import fetch from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies +import { createArchitect, host } from '../../../testing/test-utils'; + +describe('Dev Server Builder', () => { + const target = { project: 'app', target: 'serve' }; + let architect: Architect; + let runs: BuilderRun[] = []; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + runs = []; + }); + afterEach(async () => { + await host.restore().toPromise(); + await Promise.all(runs.map((r) => r.stop())); + }); + + it(`doesn't serve files on the cwd directly`, async () => { + const run = await architect.scheduleTarget(target); + runs.push(run); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + + // When webpack-dev-server doesn't have `contentBase: false`, this will serve the repo README. + const response = await fetch(`http://localhost:${output.port}/README.md`, { + headers: { + 'Accept': 'text/html', + }, + }); + + const res = await response.text(); + expect(res).not.toContain('This file is automatically generated during release.'); + expect(res).toContain('HelloWorldApp'); + }); + + it('should not generate sourcemaps when running prod build', async () => { + // Production builds have sourcemaps turned off. + const run = await architect.scheduleTarget( + { ...target, configuration: 'production' }, + { port: 0 }, + ); + runs.push(run); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + const hasSourceMaps = + output.emittedFiles && output.emittedFiles.some((f) => f.extension === '.map'); + expect(hasSourceMaps).toBe(false, `Expected emitted files not to contain '.map' files.`); + }); + + it('serves custom headers', async () => { + const run = await architect.scheduleTarget(target, { + headers: { 'X-Header': 'Hello World' }, + port: 0, + }); + runs.push(run); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + const response = await fetch(output.baseUrl); + expect(response.headers.get('X-Header')).toBe('Hello World'); + }); + + it('uses source locale when not localizing', async () => { + const config = host.scopedSync().read(normalize('angular.json')); + const jsonConfig = JSON.parse(virtualFs.fileBufferToString(config)); + const applicationProject = jsonConfig.projects.app; + + applicationProject.i18n = { sourceLocale: 'fr' }; + + host.writeMultipleFiles({ + 'angular.json': JSON.stringify(jsonConfig), + }); + + const architect = (await createArchitect(host.root())).architect; + const run = await architect.scheduleTarget(target, { port: 0 }); + const output = (await run.result) as DevServerBuilderOutput; + expect(output.success).toBe(true); + + const indexResponse = await fetch(output.baseUrl); + expect(await indexResponse.text()).toContain('lang="fr"'); + const vendorResponse = await fetch(output.baseUrl + 'vendor.js'); + const vendorText = await vendorResponse.text(); + expect(vendorText).toContain('fr'); + expect(vendorText).toContain('octobre'); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts new file mode 100644 index 000000000000..48559d704967 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { serveWebpackBrowser } from '../../index'; +import { executeOnceAndFetch } from '../execute-fetch'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Behavior: "browser builder assets"', () => { + it('serves a project JavaScript asset unmodified', async () => { + const javascriptFileContent = '/* a comment */const foo = `bar`;\n\n\n'; + await harness.writeFile('src/extra.js', javascriptFileContent); + + setupBrowserTarget(harness, { + assets: ['src/extra.js'], + optimization: { + scripts: true, + }, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, 'extra.js'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toBe(javascriptFileContent); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-budgets_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-budgets_spec.ts similarity index 80% rename from packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-budgets_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-budgets_spec.ts index be5020619969..e979ac2d84d8 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-budgets_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-budgets_spec.ts @@ -1,13 +1,19 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Type as BudgetType } from '../../../'; + +import { Type as BudgetType } from '../../../..'; import { serveWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO, describeBuilder, setupBrowserTarget } from '../setup'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { describe('Behavior: "browser builder budgets"', () => { diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-deploy-url_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-deploy-url_spec.ts similarity index 85% rename from packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-deploy-url_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-deploy-url_spec.ts index f8a0cbaccc21..2ddce2f2ff5d 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-deploy-url_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-deploy-url_spec.ts @@ -1,13 +1,19 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; -import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO, describeBuilder, setupBrowserTarget } from '../setup'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { describe('Behavior: "browser builder deployUrl"', () => { @@ -27,7 +33,7 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { expect(result?.success).toBeTrue(); expect(result?.baseUrl).toMatch(/\/test$/); expect(response?.url).toMatch(/\/test\/runtime.js$/); - expect(await response?.text()).toContain('self["webpackChunk"]'); + expect(await response?.text()).toContain('self["webpackChunktest"]'); }); }); }); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-inline-critical-css_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-inline-critical-css_spec.ts similarity index 91% rename from packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-inline-critical-css_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-inline-critical-css_spec.ts index 6b92996d22ac..206bce6c590a 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/behavior/build-inline-critical-css_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-inline-critical-css_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; import { @@ -43,7 +44,7 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { const { result, response } = await executeOnceAndFetch(harness, '/'); expect(result?.success).toBeTrue(); - expect(await response?.text()).toContain('body{color:#000;}'); + expect(await response?.text()).toContain('body{color:#000}'); }); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts new file mode 100644 index 000000000000..cc6981723e5a --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts @@ -0,0 +1,82 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/* eslint-disable max-len */ +import fetch from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { URL } from 'url'; +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + BUILD_TIMEOUT, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Behavior: "i18n $localize calls are replaced during watching"', () => { + beforeEach(() => { + harness.useProject('test', { + root: '.', + sourceRoot: 'src', + cli: { + cache: { + enabled: false, + }, + }, + i18n: { + sourceLocale: { + 'code': 'fr', + }, + }, + }); + + setupBrowserTarget(harness, { localize: ['fr'] }); + }); + + it('$localize are replaced in watch', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + await harness.writeFile( + 'src/app/app.component.html', + ` +

Hello {{ title }}!

+ `, + ); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT * 2), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + const response = await fetch(new URL('main.js', `${result?.baseUrl}`)); + expect(await response?.text()).not.toContain('$localize`:'); + + switch (index) { + case 0: { + await harness.modifyFile('src/app/app.component.html', (content) => + content.replace('introduction', 'intro'), + ); + break; + } + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_translation_watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_translation_watch_spec.ts new file mode 100644 index 000000000000..630bd63e0f2c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_translation_watch_spec.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/* eslint-disable max-len */ +import fetch from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { URL } from 'url'; +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + BUILD_TIMEOUT, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Behavior: "i18n translation file watching"', () => { + beforeEach(() => { + harness.useProject('test', { + root: '.', + sourceRoot: 'src', + cli: { + cache: { + enabled: false, + }, + }, + i18n: { + locales: { + 'fr': 'src/locales/messages.fr.xlf', + }, + }, + }); + + setupBrowserTarget(harness, { localize: ['fr'] }); + }); + + it('watches i18n translation files by default', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + await harness.writeFile( + 'src/app/app.component.html', + ` +

Hello {{ title }}!

+ `, + ); + + await harness.writeFile('src/locales/messages.fr.xlf', TRANSLATION_FILE_CONTENT); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + const mainUrl = new URL('main.js', `${result?.baseUrl}`); + + switch (index) { + case 0: { + const response = await fetch(mainUrl); + expect(await response?.text()).toContain('Bonjour'); + + await harness.modifyFile('src/locales/messages.fr.xlf', (content) => + content.replace('Bonjour', 'Salut'), + ); + break; + } + case 1: { + const response = await fetch(mainUrl); + expect(await response?.text()).toContain('Salut'); + break; + } + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); + +const TRANSLATION_FILE_CONTENT = ` + + + + + + Bonjour ! + + src/app/app.component.html + 2,3 + + An introduction header for this sample + + + + +`; diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve-live-reload-proxies_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve-live-reload-proxies_spec.ts new file mode 100644 index 000000000000..ae19605a0edd --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve-live-reload-proxies_spec.ts @@ -0,0 +1,312 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/* eslint-disable import/no-extraneous-dependencies */ +import { tags } from '@angular-devkit/core'; +import { createServer } from 'http'; +import { createProxyServer } from 'http-proxy'; +import { AddressInfo } from 'net'; +import puppeteer, { Browser, Page } from 'puppeteer'; +import { count, debounceTime, finalize, switchMap, take, timeout } from 'rxjs/operators'; +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + BUILD_TIMEOUT, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const document: any; + +interface ProxyInstance { + server: typeof createProxyServer extends () => infer R ? R : never; + url: string; +} + +function findFreePort(): Promise { + return new Promise((resolve, reject) => { + const server = createServer(); + server.once('listening', () => { + const port = (server.address() as AddressInfo).port; + server.close((e) => (e ? reject(e) : resolve(port))); + }); + server.once('error', (e) => server.close(() => reject(e))); + server.listen(); + }); +} + +async function createProxy(target: string, secure: boolean, ws = true): Promise { + const proxyPort = await findFreePort(); + const server = createProxyServer({ + ws, + target, + secure, + ssl: secure && { + key: tags.stripIndents` + -----BEGIN RSA PRIVATE KEY----- + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEBRUsUz4rdcMt + CQGLvG3SzUinsmgdgOyTNQNA0eOMyRSrmS8L+F/kSLUnqqu4mzdeqDzo2Xj553jK + dRqMCRFGJuGnQ/VIbW2A+ywgrqILuDyF5i4PL1aQW4yJ7TnXfONKfpswQArlN6DF + gBYJtoJlf8XD1sOeJpsv/O46/ix/wngQ+GwQQ2cfqxQT0fE9SBCY23VNt3SPUJ3k + 9etJMvJ9U9GHSb1CFdNQe7Gyx7xdKf1TazB27ElNZEg2aF99if47uRskYjvvFivy + 7nxGx/ccIwjwNMpk29AsKG++0sn1yTK7tD5Px6aCSVK0BKbdXZS2euJor8hASGBJ + 3GpVGJvdAgMBAAECggEAapYo8TVCdPdP7ckb4hPP0/R0MVu9aW2VNmZ5ImH+zar5 + ZmWhQ20HF2bBupP/VB5yeTIaDLNUKO9Iqy4KBWNY1UCHKyC023FFPgFV+V98FctU + faqwGOmwtEZToRwxe48ZOISndhEc247oCPyg/x8SwIY9z0OUkwaDFBEAqWtUXxM3 + /SPpCT5ilLgxnRgVB8Fj5Z0q7ThnxNVOmVC1OSIakEj46PzmMXn1pCKLOCUmAAOQ + BnrOZuty2b8b2M/GHsktLZwojQQJmArnIBymTXQTVhaGgKSyOv1qvHLp9L1OJf0/ + Xm+/TqT6ztzhzlftcObdfQZZ5JuoEwlvyrsGFlA3MQKBgQDiQC3KYMG8ViJkWrv6 + XNAFEoAjVEKrtirGWJ66YfQ9KSJ7Zttrd1Y1V1OLtq3z4YMH39wdQ8rOD+yR8mWV + 6Tnsxma6yJXAH8uan8iVbxjIZKF1hnvNCxUoxYmWOmTLcEQMzmxvTzAiR+s6R6Uj + 9LgGqppt30nM4wnOhOJU6UxqbwKBgQDdy03KidbPZuycJSy1C9AIt0jlrxDsYm+U + fZrB6mHEZcgoZS5GbLKinQCdGcgERa05BXvJmNbfZtT5a37YEnbjsTImIhDiBP5P + nW36/9a3Vg1svd1KP2206/Bh3gfZbgTsQg4YogXgjf0Uzuvw18btgTtLVpVyeuqz + TU3eeF30cwKBgQCN6lvOmapsDEs+T3uhqx4AUH53qp63PmjOSUAnANJGmsq6ROZV + HmHAy6nn9Qpf85BRHCXhZWiMoIhvc3As/EINNtWxS6hC/q6jqp4SvcD50cVFBroY + /16iWGXZCX+37A+DSOfTWgSDPEFcKRx41UOpStHbITgVgEPieo/NWxlHmQKBgQDX + JOLs2RB6V0ilnpnjdPXzvncD9fHgmwvJap24BPeZX3HtXViqD76oZsu1mNCg9EW3 + zk3pnEyyoDlvSIreZerVq4kN3HWsCVP3Pqr0kz9g0CRtmy8RWr28hjHDfXD3xPUZ + iGnMEz7IOHOKv722/liFAprV1cNaLUmFbDNg3jmlaQKBgQDG5WwngPhOHmjTnSml + amfEz9a4yEhQqpqgVNW5wwoXOf6DbjL2m/maJh01giThj7inMcbpkZlIclxD0Eu6 + Lof+ctCeqSAJvaVPmd+nv8Yp26zsF1yM8ax9xXjrIvv9fSbycNveGTDCsNNTiYoW + QyvMqmN1kGy20SZbQDD/fLfqBQ== + -----END RSA PRIVATE KEY----- + `, + cert: tags.stripIndents` + -----BEGIN CERTIFICATE----- + MIIDXTCCAkWgAwIBAgIJALz8gD/gAt0OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV + BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX + aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDIzMTgyMTQ5WhcNMTkxMDIzMTgyMTQ5WjBF + MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 + ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB + CgKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kvC/hf5Ei1 + J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYuDy9WkFuM + ie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhsEENnH6sU + E9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2swduxJTWRI + NmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+T8emgklS + tASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABo1AwTjAdBgNVHQ4EFgQUDZBhVKdb + 3BRhLIhuuE522Vsul0IwHwYDVR0jBBgwFoAUDZBhVKdb3BRhLIhuuE522Vsul0Iw + DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABh9WWZwWLgb9/DcTxL72 + 6pI96t4jiF79Q+pPefkaIIi0mE6yodWrTAsBQu9I6bNRaEcCSoiXkP2bqskD/UGg + LwUFgSrDOAA3UjdHw3QU5g2NocduG7mcFwA40TB98sOsxsUyYlzSyWzoiQWwPYwb + hek1djuWkqPXsTjlj54PTPN/SjTFmo4p5Ip6nbRf2nOREl7v0rJpGbJvXiCMYyd+ + Zv+j4mRjCGo8ysMR2HjCUGkYReLAgKyyz3M7i8vevJhKslyOmy6Txn4F0nPVumaU + DDIy4xXPW1STWfsmSYJfYW3wa0wk+pJQ3j2cTzkPQQ8gwpvM3U9DJl43uwb37v6I + 7Q== + -----END CERTIFICATE----- + `, + }, + }).listen(proxyPort); + + return { + server, + url: `${secure ? 'https' : 'http'}://localhost:${proxyPort}`, + }; +} + +async function goToPageAndWaitForWS(page: Page, url: string): Promise { + const baseUrl = url.replace(/^http/, 'ws'); + const socksRequest = + baseUrl[baseUrl.length - 1] === '/' ? `${baseUrl}ng-cli-ws` : `${baseUrl}/ng-cli-ws`; + // Create a Chrome dev tools session so that we can capturing websocket request. + // https://github.com/puppeteer/puppeteer/issues/2974 + + // We do this, to ensure that we make the right request with the expected host, port etc... + const client = await page.target().createCDPSession(); + await client.send('Network.enable'); + await client.send('Page.enable'); + + await Promise.all([ + new Promise((resolve, reject) => { + const timeout = setTimeout( + () => reject(new Error(`A Websocket connected to ${socksRequest} was not established.`)), + 2000, + ); + client.on('Network.webSocketCreated', ({ url }) => { + if (url.startsWith(socksRequest)) { + clearTimeout(timeout); + resolve(); + } + }); + }), + page.goto(url), + ]); + + await client.detach(); +} + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Behavior: "Dev-server builder live-reload with proxies"', () => { + let browser: Browser; + let page: Page; + + const SERVE_OPTIONS = Object.freeze({ + ...BASE_OPTIONS, + hmr: false, + watch: true, + liveReload: true, + }); + + beforeAll(async () => { + browser = await puppeteer.launch({ + // MacOSX users need to set the local binary manually because Chrome has lib files with + // spaces in them which Bazel does not support in runfiles + // See: https://github.com/angular/angular-cli/pull/17624 + // eslint-disable-next-line max-len + // executablePath: '/Users//git/angular-cli/node_modules/puppeteer/.local-chromium/mac-818858/chrome-mac/Chromium.app/Contents/MacOS/Chromium', + ignoreHTTPSErrors: true, + args: ['--no-sandbox', '--disable-gpu'], + }); + }); + + afterAll(async () => { + await browser.close(); + }); + + beforeEach(async () => { + setupBrowserTarget(harness, { + polyfills: 'src/polyfills.ts', + }); + + page = await browser.newPage(); + }); + + afterEach(async () => { + await page.close(); + }); + + it('works without proxy', async () => { + harness.useTarget('serve', { + ...SERVE_OPTIONS, + }); + + await harness.writeFile('src/app/app.component.html', '

{{ title }}

'); + + const buildCount = await harness + .execute() + .pipe( + debounceTime(1000), + timeout(BUILD_TIMEOUT * 2), + switchMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + if (typeof result?.baseUrl !== 'string') { + throw new Error('Expected "baseUrl" to be a string.'); + } + + switch (index) { + case 0: + await goToPageAndWaitForWS(page, result.baseUrl); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`'app'`, `'app-live-reload'`), + ); + break; + case 1: + const innerText = await page.evaluate(() => document.querySelector('p').innerText); + expect(innerText).toBe('app-live-reload'); + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + + it('works without http -> http proxy', async () => { + harness.useTarget('serve', { + ...SERVE_OPTIONS, + }); + + await harness.writeFile('src/app/app.component.html', '

{{ title }}

'); + + let proxy: ProxyInstance | undefined; + const buildCount = await harness + .execute() + .pipe( + debounceTime(1000), + timeout(BUILD_TIMEOUT * 2), + switchMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + if (typeof result?.baseUrl !== 'string') { + throw new Error('Expected "baseUrl" to be a string.'); + } + + switch (index) { + case 0: + proxy = await createProxy(result.baseUrl, false); + await goToPageAndWaitForWS(page, proxy.url); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`'app'`, `'app-live-reload'`), + ); + break; + case 1: + const innerText = await page.evaluate(() => document.querySelector('p').innerText); + expect(innerText).toBe('app-live-reload'); + break; + } + }), + take(2), + count(), + finalize(() => { + proxy?.server.close(); + }), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + + it('works without https -> http proxy', async () => { + harness.useTarget('serve', { + ...SERVE_OPTIONS, + }); + + await harness.writeFile('src/app/app.component.html', '

{{ title }}

'); + + let proxy: ProxyInstance | undefined; + const buildCount = await harness + .execute() + .pipe( + debounceTime(1000), + timeout(BUILD_TIMEOUT * 2), + switchMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + if (typeof result?.baseUrl !== 'string') { + throw new Error('Expected "baseUrl" to be a string.'); + } + + switch (index) { + case 0: + proxy = await createProxy(result.baseUrl, true); + await goToPageAndWaitForWS(page, proxy.url); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`'app'`, `'app-live-reload'`), + ); + break; + case 1: + const innerText = await page.evaluate(() => document.querySelector('p').innerText); + expect(innerText).toBe('app-live-reload'); + break; + } + }), + take(2), + count(), + finalize(() => { + proxy?.server.close(); + }), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve_service-worker_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve_service-worker_spec.ts new file mode 100644 index 000000000000..51d663d61dcf --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/serve_service-worker_spec.ts @@ -0,0 +1,217 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import fetch from 'node-fetch'; +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { serveWebpackBrowser } from '../../index'; +import { executeOnceAndFetch } from '../execute-fetch'; +import { + BASE_OPTIONS, + BUILD_TIMEOUT, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + const manifest = { + index: '/index.html', + assetGroups: [ + { + name: 'app', + installMode: 'prefetch', + resources: { + files: ['/favicon.ico', '/index.html'], + }, + }, + { + name: 'assets', + installMode: 'lazy', + updateMode: 'prefetch', + resources: { + files: ['/assets/**', '/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)'], + }, + }, + ], + }; + + describe('Behavior: "dev-server builder serves service worker"', () => { + beforeEach(async () => { + // Application code is not needed for these tests + await harness.writeFile('src/main.ts', ''); + await harness.writeFile('src/polyfills.ts', ''); + + harness.useProject('test', { + root: '.', + sourceRoot: 'src', + cli: { + cache: { + enabled: false, + }, + }, + i18n: { + sourceLocale: { + 'code': 'fr', + }, + }, + }); + }); + + it('works with service worker', async () => { + setupBrowserTarget(harness, { + serviceWorker: true, + assets: ['src/favicon.ico', 'src/assets'], + styles: ['src/styles.css'], + }); + + await harness.writeFiles({ + 'ngsw-config.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/ngsw.json'); + + expect(result?.success).toBeTrue(); + + expect(await response?.json()).toEqual( + jasmine.objectContaining({ + configVersion: 1, + index: '/index.html', + navigationUrls: [ + { positive: true, regex: '^\\/.*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*\\.[^/]*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*$' }, + { positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$' }, + ], + assetGroups: [ + { + name: 'app', + installMode: 'prefetch', + updateMode: 'prefetch', + urls: ['/favicon.ico', '/index.html'], + cacheQueryOptions: { + ignoreVary: true, + }, + patterns: [], + }, + { + name: 'assets', + installMode: 'lazy', + updateMode: 'prefetch', + urls: ['/assets/folder-asset.txt', '/spectrum.png'], + cacheQueryOptions: { + ignoreVary: true, + }, + patterns: [], + }, + ], + dataGroups: [], + hashTable: { + '/favicon.ico': '84161b857f5c547e3699ddfbffc6d8d737542e01', + '/assets/folder-asset.txt': '617f202968a6a81050aa617c2e28e1dca11ce8d4', + '/index.html': '9d232e3e13b4605d197037224a2a6303dd337480', + '/spectrum.png': '8d048ece46c0f3af4b598a95fd8e4709b631c3c0', + }, + }), + ); + }); + + it('works with localize', async () => { + setupBrowserTarget(harness, { + serviceWorker: true, + assets: ['src/favicon.ico', 'src/assets'], + styles: ['src/styles.css'], + localize: ['fr'], + }); + + await harness.writeFiles({ + 'ngsw-config.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/ngsw.json'); + + expect(result?.success).toBeTrue(); + + expect(await response?.json()).toBeDefined(); + }); + + it('works in watch mode', async () => { + setupBrowserTarget(harness, { + serviceWorker: true, + watch: true, + assets: ['src/favicon.ico', 'src/assets'], + styles: ['src/styles.css'], + }); + + await harness.writeFiles({ + 'ngsw-config.json': JSON.stringify(manifest), + 'src/assets/folder-asset.txt': 'folder-asset.txt', + 'src/styles.css': `body { background: url(https://github.com/spectrum.png); }`, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBeTrue(); + const response = await fetch(new URL('ngsw.json', `${result?.baseUrl}`)); + const { hashTable } = await response.json(); + const hashTableEntries = Object.keys(hashTable); + + switch (index) { + case 0: + expect(hashTableEntries).toEqual([ + '/assets/folder-asset.txt', + '/favicon.ico', + '/index.html', + '/spectrum.png', + ]); + + await harness.writeFile( + 'src/assets/folder-new-asset.txt', + harness.readFile('src/assets/folder-asset.txt'), + ); + break; + + case 1: + expect(hashTableEntries).toEqual([ + '/assets/folder-asset.txt', + '/assets/folder-new-asset.txt', + '/favicon.ico', + '/index.html', + '/spectrum.png', + ]); + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/execute-fetch.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/execute-fetch.ts new file mode 100644 index 000000000000..e62476d5912f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/execute-fetch.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import fetch, { RequestInit, Response } from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies +import { mergeMap, take, timeout } from 'rxjs/operators'; +import { URL } from 'url'; +import { + BuilderHarness, + BuilderHarnessExecutionOptions, + BuilderHarnessExecutionResult, +} from '../../../testing/builder-harness'; + +export async function executeOnceAndFetch( + harness: BuilderHarness, + url: string, + options?: Partial & { request?: RequestInit }, +): Promise { + return harness + .execute() + .pipe( + timeout(30000), + mergeMap(async (executionResult) => { + let response = undefined; + if (executionResult.result?.success) { + let baseUrl = `${executionResult.result.baseUrl}`; + baseUrl = baseUrl[baseUrl.length - 1] === '/' ? baseUrl : `${baseUrl}/`; + const resolvedUrl = new URL(url, baseUrl); + response = await fetch(resolvedUrl, options?.request); + } + + return { ...executionResult, response }; + }), + take(1), + ) + .toPromise(); +} diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/allowed-hosts_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/allowed-hosts_spec.ts similarity index 97% rename from packages/angular_devkit/build_angular/src/dev-server/tests/options/allowed-hosts_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/allowed-hosts_spec.ts index 730fd9d4c50d..364067d19c9f 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/allowed-hosts_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/allowed-hosts_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; import { diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/disable-host-check_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/disable-host-check_spec.ts similarity index 97% rename from packages/angular_devkit/build_angular/src/dev-server/tests/options/disable-host-check_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/disable-host-check_spec.ts index ce583fb96d23..76702ff10fc3 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/disable-host-check_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/disable-host-check_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; import { diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/hmr_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/hmr_spec.ts new file mode 100644 index 000000000000..96d5da7a8f9b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/hmr_spec.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Option: "hmr"', () => { + beforeEach(() => { + setupBrowserTarget(harness); + }); + + it('should not show a CommonJS usage warning when enabled', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + hmr: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('CommonJS or AMD dependencies'), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/live-reload_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/live-reload_spec.ts new file mode 100644 index 000000000000..79d1c4ecd143 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/live-reload_spec.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Option: "liveReload"', () => { + beforeEach(() => { + setupBrowserTarget(harness); + }); + + it('should not show a CommonJS usage warning when enabled', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + liveReload: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('CommonJS or AMD dependencies'), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/port_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/port_spec.ts similarity index 98% rename from packages/angular_devkit/build_angular/src/dev-server/tests/options/port_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/port_spec.ts index 9811860d88e0..8413f2ad5f9d 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/port_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/port_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { URL } from 'url'; import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts new file mode 100644 index 000000000000..549b79f24a38 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/proxy-config_spec.ts @@ -0,0 +1,235 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as http from 'http'; +import { serveWebpackBrowser } from '../../index'; +import { executeOnceAndFetch } from '../execute-fetch'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('option: "proxyConfig"', () => { + beforeEach(async () => { + setupBrowserTarget(harness); + + // Application code is not needed for these tests + await harness.writeFile('src/main.ts', ''); + }); + + it('proxies requests based on the `.json` proxy configuration file provided in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.json', + }); + + const proxyServer = createProxyServer(); + try { + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address() as import('net').AddressInfo; + + await harness.writeFiles({ + 'proxy.config.json': `{ "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await new Promise((resolve) => proxyServer.close(() => resolve())); + } + }); + + it('proxies requests based on the `.json` (with comments) proxy configuration file provided in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.json', + }); + + const proxyServer = createProxyServer(); + try { + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address() as import('net').AddressInfo; + + await harness.writeFiles({ + 'proxy.config.json': ` + // JSON file with comments + { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } } + `, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await new Promise((resolve) => proxyServer.close(() => resolve())); + } + }); + + it('proxies requests based on the `.js` (CommonJS) proxy configuration file provided in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.js', + }); + + const proxyServer = createProxyServer(); + try { + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address() as import('net').AddressInfo; + + await harness.writeFiles({ + 'proxy.config.js': `module.exports = { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await new Promise((resolve) => proxyServer.close(() => resolve())); + } + }); + + it('proxies requests based on the `.js` (ESM) proxy configuration file provided in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.js', + }); + + const proxyServer = createProxyServer(); + try { + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address() as import('net').AddressInfo; + + await harness.writeFiles({ + 'proxy.config.js': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + 'package.json': '{ "type": "module" }', + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await new Promise((resolve) => proxyServer.close(() => resolve())); + } + }); + + it('proxies requests based on the `.cjs` proxy configuration file provided in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.cjs', + }); + + const proxyServer = createProxyServer(); + try { + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address() as import('net').AddressInfo; + + await harness.writeFiles({ + 'proxy.config.cjs': `module.exports = { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await new Promise((resolve) => proxyServer.close(() => resolve())); + } + }); + + it('proxies requests based on the `.mjs` proxy configuration file provided in the option', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.mjs', + }); + + const proxyServer = createProxyServer(); + try { + await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); + const proxyAddress = proxyServer.address() as import('net').AddressInfo; + + await harness.writeFiles({ + 'proxy.config.mjs': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/api/test'); + + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('TEST_API_RETURN'); + } finally { + await new Promise((resolve) => proxyServer.close(() => resolve())); + } + }); + + it('throws an error when proxy configuration file cannot be found', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'INVALID.json', + }); + + const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + + expect(result).toBeUndefined(); + expect(error).toEqual( + jasmine.objectContaining({ + message: jasmine.stringMatching('INVALID\\.json does not exist'), + }), + ); + }); + + it('throws an error when JSON proxy configuration file cannot be parsed', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + proxyConfig: 'proxy.config.json', + }); + + // Create a JSON file with a parse error (target property has no value) + await harness.writeFiles({ + 'proxy.config.json': ` + // JSON file with comments + { "/api/*": { "target": } } + `, + }); + + const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + + expect(result).toBeUndefined(); + expect(error).toEqual( + jasmine.objectContaining({ + message: jasmine.stringMatching('contains parse errors:\\n\\[3, 35\\] ValueExpected'), + }), + ); + }); + }); +}); + +/** + * Creates an HTTP Server used for proxy testing that provides a `/test` endpoint + * that returns a 200 response with a body of `TEST_API_RETURN`. All other requests + * will return a 404 response. + * + * @returns An HTTP Server instance. + */ +function createProxyServer() { + return http.createServer((request, response) => { + if (request.url?.endsWith('/test')) { + response.writeHead(200); + response.end('TEST_API_RETURN'); + } else { + response.writeHead(404); + response.end(); + } + }); +} diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/public-host_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/public-host_spec.ts similarity index 97% rename from packages/angular_devkit/build_angular/src/dev-server/tests/options/public-host_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/public-host_spec.ts index 6e1dfb1c9035..952d7e822662 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/public-host_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/public-host_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; import { diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/serve-path_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts similarity index 97% rename from packages/angular_devkit/build_angular/src/dev-server/tests/options/serve-path_spec.ts rename to packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts index 2741c16f0fd9..61a32d24d123 100644 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/serve-path_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { URL } from 'url'; import { serveWebpackBrowser } from '../../index'; import { executeOnceAndFetch } from '../execute-fetch'; @@ -34,7 +35,7 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { expect(result?.success).toBeTrue(); const baseUrl = new URL(`${result?.baseUrl}`); expect(baseUrl.pathname).toBe('/'); - expect(await response?.text()).toContain('self["webpackChunk"]'); + expect(await response?.text()).toContain('self["webpackChunktest"]'); }); it('serves application at specified path when option is used', async () => { @@ -48,7 +49,7 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { expect(result?.success).toBeTrue(); const baseUrl = new URL(`${result?.baseUrl}/`); expect(baseUrl.pathname).toBe('/test/'); - expect(await response?.text()).toContain('self["webpackChunk"]'); + expect(await response?.text()).toContain('self["webpackChunktest"]'); }); it('does not rewrite from root when option is used', async () => { diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/verbose_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/verbose_spec.ts new file mode 100644 index 000000000000..ed0082dc989d --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/verbose_spec.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +const VERBOSE_LOG_TEXT = /\[emitted\] \(name: main\)/; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Option: "verbose"', () => { + beforeEach(() => { + setupBrowserTarget(harness); + }); + + it('shows verbose logs in console when true', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + verbose: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).toContain( + jasmine.objectContaining({ message: jasmine.stringMatching(VERBOSE_LOG_TEXT) }), + ); + }); + + it('does not show verbose logs in console when false', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + verbose: false, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ message: jasmine.stringMatching(VERBOSE_LOG_TEXT) }), + ); + }); + + it('does not show verbose logs in console when not present', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ message: jasmine.stringMatching(VERBOSE_LOG_TEXT) }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/watch_spec.ts new file mode 100644 index 000000000000..4da51b5ba03e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/watch_spec.ts @@ -0,0 +1,126 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { TimeoutError } from 'rxjs'; +import { concatMap, count, take, timeout } from 'rxjs/operators'; +import { serveWebpackBrowser } from '../../index'; +import { + BASE_OPTIONS, + BUILD_TIMEOUT, + DEV_SERVER_BUILDER_INFO, + describeBuilder, + setupBrowserTarget, +} from '../setup'; + +describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { + describe('Option: "watch"', () => { + beforeEach(() => { + setupBrowserTarget(harness); + }); + + it('does not wait for file changes when false', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + watch: false, + }); + + await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: + await harness.modifyFile( + 'src/main.ts', + (content) => content + 'console.log("abcd1234");', + ); + break; + case 1: + fail('Expected files to not be watched.'); + break; + } + }), + take(2), + ) + .toPromise() + .catch((error) => { + // Timeout is expected if watching is disabled + if (error instanceof TimeoutError) { + return; + } + throw error; + }); + }); + + it('watches for file changes when not present', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: + await harness.modifyFile( + 'src/main.ts', + (content) => content + 'console.log("abcd1234");', + ); + break; + case 1: + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + + it('watches for file changes when true', async () => { + harness.useTarget('serve', { + ...BASE_OPTIONS, + watch: true, + }); + + const buildCount = await harness + .execute() + .pipe( + timeout(BUILD_TIMEOUT), + concatMap(async ({ result }, index) => { + expect(result?.success).toBe(true); + + switch (index) { + case 0: + await harness.modifyFile( + 'src/main.ts', + (content) => content + 'console.log("abcd1234");', + ); + break; + case 1: + break; + } + }), + take(2), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(2); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/setup.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/setup.ts new file mode 100644 index 000000000000..6ee052567874 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/setup.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { json } from '@angular-devkit/core'; +import { readFileSync } from 'fs'; +import { BuilderHarness } from '../../../testing/builder-harness'; +import { buildWebpackBrowser } from '../../browser'; +import { Schema as BrowserSchema } from '../../browser/schema'; +import { + BASE_OPTIONS as BROWSER_BASE_OPTIONS, + BROWSER_BUILDER_INFO, +} from '../../browser/tests/setup'; +import { Schema } from '../schema'; + +export { describeBuilder } from '../../../testing'; + +export const DEV_SERVER_BUILDER_INFO = Object.freeze({ + name: '@angular-devkit/build-angular:dev-server', + schemaPath: __dirname + '/../schema.json', +}); + +/** + * Contains all required dev-server builder fields. + * The port is also set to zero to ensure a free port is used for each test which + * supports parallel test execution. + */ +export const BASE_OPTIONS = Object.freeze({ + browserTarget: 'test:build', + port: 0, +}); + +/** + * Maximum time for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 25_000; + +/** + * Cached browser builder option schema + */ +let browserSchema: json.schema.JsonSchema | undefined = undefined; + +/** + * Adds a `build` target to a builder test harness for the browser builder with the base options + * used by the browser builder tests. + * + * @param harness The builder harness to use when setting up the browser builder target + * @param extraOptions The additional options that should be used when executing the target. + */ +export function setupBrowserTarget( + harness: BuilderHarness, + extraOptions?: Partial, +): void { + if (!browserSchema) { + browserSchema = JSON.parse( + readFileSync(BROWSER_BUILDER_INFO.schemaPath, 'utf8'), + ) as json.schema.JsonSchema; + } + + harness.withBuilderTarget( + 'build', + buildWebpackBrowser, + { + ...BROWSER_BASE_OPTIONS, + ...extraOptions, + }, + { + builderName: BROWSER_BUILDER_INFO.name, + optionSchema: browserSchema, + }, + ); +} diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/empty-loader.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/empty-loader.ts new file mode 100644 index 000000000000..791cb7c0c230 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/empty-loader.ts @@ -0,0 +1,11 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export default function () { + return `export default '';`; +} diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/index.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/index.ts new file mode 100644 index 000000000000..8f7e54708894 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/index.ts @@ -0,0 +1,308 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect'; +import { BuildResult, runWebpack } from '@angular-devkit/build-webpack'; +import { JsonObject } from '@angular-devkit/core'; +import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize'; +import type { Diagnostics } from '@angular/localize/tools'; +import * as fs from 'fs'; +import * as path from 'path'; +import webpack, { Configuration } from 'webpack'; +import { ExecutionTransformer } from '../../transforms'; +import { createI18nOptions } from '../../utils/i18n-options'; +import { loadEsmModule } from '../../utils/load-esm'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { assertCompatibleAngularVersion } from '../../utils/version'; +import { generateBrowserWebpackConfigFromContext } from '../../utils/webpack-browser-config'; +import { getCommonConfig } from '../../webpack/configs'; +import { createWebpackLoggingCallback } from '../../webpack/utils/stats'; +import { Schema as BrowserBuilderOptions, OutputHashing } from '../browser/schema'; +import { Format, Schema } from './schema'; + +export type ExtractI18nBuilderOptions = Schema; + +function getI18nOutfile(format: string | undefined) { + switch (format) { + case 'xmb': + return 'messages.xmb'; + case 'xlf': + case 'xlif': + case 'xliff': + case 'xlf2': + case 'xliff2': + return 'messages.xlf'; + case 'json': + case 'legacy-migrate': + return 'messages.json'; + case 'arb': + return 'messages.arb'; + default: + throw new Error(`Unsupported format "${format}"`); + } +} + +async function getSerializer( + localizeToolsModule: typeof import('@angular/localize/tools'), + format: Format, + sourceLocale: string, + basePath: string, + useLegacyIds: boolean, + diagnostics: Diagnostics, +) { + const { + XmbTranslationSerializer, + LegacyMessageIdMigrationSerializer, + ArbTranslationSerializer, + Xliff1TranslationSerializer, + Xliff2TranslationSerializer, + SimpleJsonTranslationSerializer, + } = localizeToolsModule; + + switch (format) { + case Format.Xmb: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new XmbTranslationSerializer(basePath as any, useLegacyIds); + case Format.Xlf: + case Format.Xlif: + case Format.Xliff: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new Xliff1TranslationSerializer(sourceLocale, basePath as any, useLegacyIds, {}); + case Format.Xlf2: + case Format.Xliff2: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new Xliff2TranslationSerializer(sourceLocale, basePath as any, useLegacyIds, {}); + case Format.Json: + return new SimpleJsonTranslationSerializer(sourceLocale); + case Format.LegacyMigrate: + return new LegacyMessageIdMigrationSerializer(diagnostics); + case Format.Arb: + const fileSystem = { + relative(from: string, to: string): string { + return path.relative(from, to); + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new ArbTranslationSerializer(sourceLocale, basePath as any, fileSystem as any); + } +} + +function normalizeFormatOption(options: ExtractI18nBuilderOptions): Format { + let format = options.format; + + switch (format) { + case Format.Xlf: + case Format.Xlif: + case Format.Xliff: + format = Format.Xlf; + break; + case Format.Xlf2: + case Format.Xliff2: + format = Format.Xlf2; + break; + } + + // Default format is xliff1 + return format ?? Format.Xlf; +} + +class NoEmitPlugin { + apply(compiler: webpack.Compiler): void { + compiler.hooks.shouldEmit.tap('angular-no-emit', () => false); + } +} + +/** + * @experimental Direct usage of this function is considered experimental. + */ +export async function execute( + options: ExtractI18nBuilderOptions, + context: BuilderContext, + transforms?: { + webpackConfiguration?: ExecutionTransformer; + }, +): Promise { + // Check Angular version. + assertCompatibleAngularVersion(context.workspaceRoot); + + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + const browserTarget = targetFromTargetString(options.browserTarget); + const browserOptions = await context.validateOptions( + await context.getTargetOptions(browserTarget), + await context.getBuilderNameForTarget(browserTarget), + ); + + const format = normalizeFormatOption(options); + + // We need to determine the outFile name so that AngularCompiler can retrieve it. + let outFile = options.outFile || getI18nOutfile(format); + if (options.outputPath) { + // AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead. + outFile = path.join(options.outputPath, outFile); + } + outFile = path.resolve(context.workspaceRoot, outFile); + + if (!context.target || !context.target.project) { + throw new Error('The builder requires a target.'); + } + + try { + require.resolve('@angular/localize'); + } catch { + return { + success: false, + error: `i18n extraction requires the '@angular/localize' package.`, + outputPath: outFile, + }; + } + + const metadata = await context.getProjectMetadata(context.target); + const i18n = createI18nOptions(metadata); + + let useLegacyIds = true; + + const ivyMessages: LocalizeMessage[] = []; + const builderOptions = { + ...browserOptions, + optimization: false, + sourceMap: { + scripts: true, + styles: false, + vendor: true, + }, + buildOptimizer: false, + aot: true, + progress: options.progress, + budgets: [], + assets: [], + scripts: [], + styles: [], + deleteOutputPath: false, + extractLicenses: false, + subresourceIntegrity: false, + outputHashing: OutputHashing.None, + namedChunks: true, + allowedCommonJsDependencies: undefined, + }; + const { config, projectRoot } = await generateBrowserWebpackConfigFromContext( + builderOptions, + context, + (wco) => { + // Default value for legacy message ids is currently true + useLegacyIds = wco.tsConfig.options.enableI18nLegacyMessageIdFormat ?? true; + + const partials: (Promise | Configuration)[] = [ + { plugins: [new NoEmitPlugin()] }, + getCommonConfig(wco), + ]; + + // Add Ivy application file extractor support + partials.unshift({ + module: { + rules: [ + { + test: /\.[cm]?[tj]sx?$/, + loader: require.resolve('./ivy-extract-loader'), + options: { + messageHandler: (messages: LocalizeMessage[]) => ivyMessages.push(...messages), + }, + }, + ], + }, + }); + + // Replace all stylesheets with empty content + partials.push({ + module: { + rules: [ + { + test: /\.(css|scss|sass|less)$/, + loader: require.resolve('./empty-loader'), + }, + ], + }, + }); + + return partials; + }, + // During extraction we don't need specific browser support. + { supportedBrowsers: undefined }, + ); + + // All the localize usages are setup to first try the ESM entry point then fallback to the deep imports. + // This provides interim compatibility while the framework is transitioned to bundled ESM packages. + const localizeToolsModule = await loadEsmModule( + '@angular/localize/tools', + ); + const webpackResult = await runWebpack( + (await transforms?.webpackConfiguration?.(config)) || config, + context, + { + logging: createWebpackLoggingCallback(builderOptions, context.logger), + webpackFactory: webpack, + }, + ).toPromise(); + + // Set the outputPath to the extraction output location for downstream consumers + webpackResult.outputPath = outFile; + + // Complete if Webpack build failed + if (!webpackResult.success) { + return webpackResult; + } + + const basePath = config.context || projectRoot; + + const { checkDuplicateMessages } = localizeToolsModule; + + // The filesystem is used to create a relative path for each file + // from the basePath. This relative path is then used in the error message. + const checkFileSystem = { + relative(from: string, to: string): string { + return path.relative(from, to); + }, + }; + const diagnostics = checkDuplicateMessages( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + checkFileSystem as any, + ivyMessages, + 'warning', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + basePath as any, + ); + if (diagnostics.messages.length > 0) { + context.logger.warn(diagnostics.formatDiagnostics('')); + } + + // Serialize all extracted messages + const serializer = await getSerializer( + localizeToolsModule, + format, + i18n.sourceLocale, + basePath, + useLegacyIds, + diagnostics, + ); + const content = serializer.serialize(ivyMessages); + + // Ensure directory exists + const outputPath = path.dirname(outFile); + if (!fs.existsSync(outputPath)) { + fs.mkdirSync(outputPath, { recursive: true }); + } + + // Write translation file + fs.writeFileSync(outFile, content); + + return webpackResult; +} + +export default createBuilder(execute); diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts new file mode 100644 index 000000000000..b19b9098d7de --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts @@ -0,0 +1,132 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as nodePath from 'path'; +import { loadEsmModule } from '../../utils/load-esm'; + +// Extract loader source map parameter type since it is not exported directly +type LoaderSourceMap = Parameters[1]; + +interface LocalizeExtractLoaderOptions { + messageHandler: (messages: import('@angular/localize').ɵParsedMessage[]) => void; +} + +export default function localizeExtractLoader( + this: import('webpack').LoaderContext, + content: string, + map: LoaderSourceMap, +) { + // This loader is not cacheable due to how message extraction works. + // Extracted messages are not part of webpack pipeline and hence they cannot be retrieved from cache. + // TODO: We should investigate in the future on making this deterministic and more cacheable. + this.cacheable(false); + + const options = this.getOptions(); + const callback = this.async(); + + extract(this, content, map, options).then( + () => { + // Pass through the original content now that messages have been extracted + callback(undefined, content, map); + }, + (error) => { + callback(error); + }, + ); +} + +async function extract( + loaderContext: import('webpack').LoaderContext, + content: string, + map: string | LoaderSourceMap | undefined, + options: LocalizeExtractLoaderOptions, +) { + // Try to load the `@angular/localize` message extractor. + // All the localize usages are setup to first try the ESM entry point then fallback to the deep imports. + // This provides interim compatibility while the framework is transitioned to bundled ESM packages. + let MessageExtractor; + try { + // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + const localizeToolsModule = await loadEsmModule( + '@angular/localize/tools', + ); + MessageExtractor = localizeToolsModule.MessageExtractor; + } catch { + throw new Error( + `Unable to load message extractor. Please ensure '@angular/localize' is installed.`, + ); + } + + // Setup a Webpack-based logger instance + const logger = { + // level 2 is warnings + level: 2, + debug(...args: string[]): void { + // eslint-disable-next-line no-console + console.debug(...args); + }, + info(...args: string[]): void { + loaderContext.emitWarning(new Error(args.join(''))); + }, + warn(...args: string[]): void { + loaderContext.emitWarning(new Error(args.join(''))); + }, + error(...args: string[]): void { + loaderContext.emitError(new Error(args.join(''))); + }, + }; + + let filename = loaderContext.resourcePath; + const mapObject = + typeof map === 'string' ? (JSON.parse(map) as Exclude) : map; + if (mapObject?.file) { + // The extractor's internal sourcemap handling expects the filenames to match + filename = nodePath.join(loaderContext.context, mapObject.file); + } + + // Setup a virtual file system instance for the extractor + // * MessageExtractor itself uses readFile, relative and resolve + // * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve + const filesystem = { + readFile(path: string): string { + if (path === filename) { + return content; + } else if (path === filename + '.map') { + return typeof map === 'string' ? map : JSON.stringify(map); + } else { + throw new Error('Unknown file requested: ' + path); + } + }, + relative(from: string, to: string): string { + return nodePath.relative(from, to); + }, + resolve(...paths: string[]): string { + return nodePath.resolve(...paths); + }, + exists(path: string): boolean { + return path === filename || path === filename + '.map'; + }, + dirname(path: string): string { + return nodePath.dirname(path); + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const extractor = new MessageExtractor(filesystem as any, logger, { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + basePath: loaderContext.rootContext as any, + useSourceMaps: !!map, + }); + + const messages = extractor.extractMessages(filename); + if (messages.length > 0) { + options?.messageHandler(messages); + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/schema.json b/packages/angular_devkit/build_angular/src/builders/extract-i18n/schema.json new file mode 100644 index 000000000000..f4bf78ba361d --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Extract i18n Target", + "description": "Extract i18n target options for Build Facade.", + "type": "object", + "properties": { + "browserTarget": { + "type": "string", + "description": "A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", + "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" + }, + "format": { + "type": "string", + "description": "Output format for the generated file.", + "default": "xlf", + "enum": ["xmb", "xlf", "xlif", "xliff", "xlf2", "xliff2", "json", "arb", "legacy-migrate"] + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console.", + "default": true + }, + "outputPath": { + "type": "string", + "description": "Path where output will be placed." + }, + "outFile": { + "type": "string", + "description": "Name of the file to output." + } + }, + "additionalProperties": false, + "required": ["browserTarget"] +} diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/works_spec.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/works_spec.ts new file mode 100644 index 000000000000..1a831c090f0b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/works_spec.ts @@ -0,0 +1,162 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { join, logging, normalize, virtualFs } from '@angular-devkit/core'; +import { createArchitect, extractI18nTargetSpec, host } from '../../testing/test-utils'; + +describe('Extract i18n Target', () => { + const extractionFile = join(normalize('src'), 'messages.xlf'); + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + + afterEach(() => host.restore().toPromise()); + + it('generates an extraction file', async () => { + host.appendToFile('src/app/app.component.html', '

i18n test

'); + + const run = await architect.scheduleTarget(extractI18nTargetSpec); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + const exists = host.scopedSync().exists(extractionFile); + expect(exists).toBe(true); + + if (exists) { + const content = virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)); + expect(content).toContain('i18n test'); + } + }); + + it('does not emit the application files', async () => { + host.appendToFile('src/app/app.component.html', '

i18n test

'); + + const run = await architect.scheduleTarget(extractI18nTargetSpec); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(normalize('dist/app/main.js'))).toBeFalse(); + }); + + it('shows errors', async () => { + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + host.appendToFile( + 'src/app/app.component.html', + '

Hello world inner

', + ); + + const run = await architect.scheduleTarget(extractI18nTargetSpec, undefined, { logger }); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); + + await run.stop(); + + expect(logs.join()).toMatch( + 'Cannot mark an element as translatable inside of a translatable section', + ); + }); + + it('supports out file', async () => { + host.appendToFile('src/app/app.component.html', '

i18n test

'); + const outFile = 'messages.fr.xlf'; + const extractionFile = join(normalize('src'), outFile); + const overrides = { outFile }; + + const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(extractionFile)).toBe(true); + expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile))).toMatch( + /i18n test/, + ); + }); + + it('supports output path', async () => { + host.appendToFile('src/app/app.component.html', '

i18n test

'); + // Note: this folder will not be created automatically. It must exist beforehand. + const outputPath = 'src/i18n'; + const extractionFile = join(normalize('src'), 'i18n', 'messages.xlf'); + const overrides = { outputPath }; + + const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(extractionFile)).toBe(true); + expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile))).toMatch( + /i18n test/, + ); + }); + + it('supports i18n format', async () => { + host.appendToFile('src/app/app.component.html', '

i18n test

'); + const extractionFile = join(normalize('src'), 'messages.xmb'); + const overrides = { format: 'xmb' }; + + const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(extractionFile)).toBe(true); + expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile))).toMatch( + /i18n test/, + ); + }); + + it('issues warnings for duplicate message identifiers', async () => { + host.appendToFile( + 'src/app/app.component.ts', + 'const c = $localize`:@@message-2:message contents`; const d = $localize`:@@message-2:different message contents`;', + ); + + const logger = new logging.Logger(''); + const logs: string[] = []; + logger.subscribe((e) => logs.push(e.message)); + + const run = await architect.scheduleTarget(extractI18nTargetSpec, undefined, { logger }); + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(extractionFile)).toBe(true); + + const fullLog = logs.join(); + expect(fullLog).toContain('Duplicate messages with id'); + }); + + it('ignores inline styles', async () => { + host.appendToFile('src/app/app.component.html', '

i18n test

'); + host.replaceInFile('src/app/app.component.ts', 'styleUrls', 'styles'); + host.replaceInFile('src/app/app.component.ts', './app.component.css', 'h1 { color: green; }'); + + const run = await architect.scheduleTarget(extractI18nTargetSpec); + + // This will fail if a style is processed since the style rules are not included during extraction + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/find-tests-plugin.ts b/packages/angular_devkit/build_angular/src/builders/karma/find-tests-plugin.ts new file mode 100644 index 000000000000..6fc935393ed8 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/find-tests-plugin.ts @@ -0,0 +1,157 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import assert from 'assert'; +import { PathLike, constants, promises as fs } from 'fs'; +import glob, { hasMagic } from 'glob'; +import { basename, dirname, extname, join, relative } from 'path'; +import { promisify } from 'util'; +import type { Compilation, Compiler } from 'webpack'; +import { addError } from '../../utils/webpack-diagnostics'; + +const globPromise = promisify(glob); + +/** + * The name of the plugin provided to Webpack when tapping Webpack compiler hooks. + */ +const PLUGIN_NAME = 'angular-find-tests-plugin'; + +export interface FindTestsPluginOptions { + include?: string[]; + workspaceRoot: string; + projectSourceRoot: string; +} + +export class FindTestsPlugin { + private compilation: Compilation | undefined; + + constructor(private options: FindTestsPluginOptions) {} + + apply(compiler: Compiler): void { + const { include = ['**/*.spec.ts'], projectSourceRoot, workspaceRoot } = this.options; + const webpackOptions = compiler.options; + const entry = + typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry; + + let originalImport: string[] | undefined; + + // Add tests files are part of the entry-point. + webpackOptions.entry = async () => { + const specFiles = await findTests(include, workspaceRoot, projectSourceRoot); + + if (!specFiles.length) { + assert(this.compilation, 'Compilation cannot be undefined.'); + addError( + this.compilation, + `Specified patterns: "${include.join(', ')}" did not match any spec files.`, + ); + } + + const entrypoints = await entry; + const entrypoint = entrypoints['main']; + if (!entrypoint.import) { + throw new Error(`Cannot find 'main' entrypoint.`); + } + + originalImport ??= entrypoint.import; + entrypoint.import = [...originalImport, ...specFiles]; + + return entrypoints; + }; + + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { + this.compilation = compilation; + compilation.contextDependencies.add(projectSourceRoot); + }); + } +} + +// go through all patterns and find unique list of files +async function findTests( + patterns: string[], + workspaceRoot: string, + projectSourceRoot: string, +): Promise { + const matchingTestsPromises = patterns.map((pattern) => + findMatchingTests(pattern, workspaceRoot, projectSourceRoot), + ); + const files = await Promise.all(matchingTestsPromises); + + // Unique file names + return [...new Set(files.flat())]; +} + +const normalizePath = (path: string): string => path.replace(/\\/g, '/'); + +async function findMatchingTests( + pattern: string, + workspaceRoot: string, + projectSourceRoot: string, +): Promise { + // normalize pattern, glob lib only accepts forward slashes + let normalizedPattern = normalizePath(pattern); + if (normalizedPattern.charAt(0) === '/') { + normalizedPattern = normalizedPattern.substring(1); + } + + const relativeProjectRoot = normalizePath(relative(workspaceRoot, projectSourceRoot) + '/'); + + // remove relativeProjectRoot to support relative paths from root + // such paths are easy to get when running scripts via IDEs + if (normalizedPattern.startsWith(relativeProjectRoot)) { + normalizedPattern = normalizedPattern.substring(relativeProjectRoot.length); + } + + // special logic when pattern does not look like a glob + if (!hasMagic(normalizedPattern)) { + if (await isDirectory(join(projectSourceRoot, normalizedPattern))) { + normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`; + } else { + // see if matching spec file exists + const fileExt = extname(normalizedPattern); + // Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts` + const potentialSpec = join( + projectSourceRoot, + dirname(normalizedPattern), + `${basename(normalizedPattern, fileExt)}.spec${fileExt}`, + ); + + if (await exists(potentialSpec)) { + return [potentialSpec]; + } + } + } + + return globPromise(normalizedPattern, { + cwd: projectSourceRoot, + root: projectSourceRoot, + nomount: true, + absolute: true, + ignore: ['**/node_modules/**'], + }); +} + +async function isDirectory(path: PathLike): Promise { + try { + const stats = await fs.stat(path); + + return stats.isDirectory(); + } catch { + return false; + } +} + +async function exists(path: PathLike): Promise { + try { + await fs.access(path, constants.F_OK); + + return true; + } catch { + return false; + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/karma/index.ts b/packages/angular_devkit/build_angular/src/builders/karma/index.ts new file mode 100644 index 000000000000..50da6fe0f24c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/index.ts @@ -0,0 +1,250 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { strings } from '@angular-devkit/core'; +import type { Config, ConfigOptions } from 'karma'; +import { createRequire } from 'module'; +import * as path from 'path'; +import { Observable, from } from 'rxjs'; +import { defaultIfEmpty, switchMap } from 'rxjs/operators'; +import { Configuration } from 'webpack'; +import { ExecutionTransformer } from '../../transforms'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { assertCompatibleAngularVersion } from '../../utils/version'; +import { generateBrowserWebpackConfigFromContext } from '../../utils/webpack-browser-config'; +import { getCommonConfig, getStylesConfig } from '../../webpack/configs'; +import { Schema as BrowserBuilderOptions, OutputHashing } from '../browser/schema'; +import { FindTestsPlugin } from './find-tests-plugin'; +import { Schema as KarmaBuilderOptions } from './schema'; + +export type KarmaConfigOptions = ConfigOptions & { + buildWebpack?: unknown; + configFile?: string; +}; + +async function initialize( + options: KarmaBuilderOptions, + context: BuilderContext, + webpackConfigurationTransformer?: ExecutionTransformer, +): Promise<[typeof import('karma'), Configuration]> { + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + const { config } = await generateBrowserWebpackConfigFromContext( + // only two properties are missing: + // * `outputPath` which is fixed for tests + // * `budgets` which might be incorrect due to extra dev libs + { + ...(options as unknown as BrowserBuilderOptions), + outputPath: '', + budgets: undefined, + optimization: false, + buildOptimizer: false, + aot: false, + vendorChunk: true, + namedChunks: true, + extractLicenses: false, + outputHashing: OutputHashing.None, + // The webpack tier owns the watch behavior so we want to force it in the config. + // When not in watch mode, webpack-dev-middleware will call `compiler.watch` anyway. + // https://github.com/webpack/webpack-dev-middleware/blob/698c9ae5e9bb9a013985add6189ff21c1a1ec185/src/index.js#L65 + // https://github.com/webpack/webpack/blob/cde1b73e12eb8a77eb9ba42e7920c9ec5d29c2c9/lib/Compiler.js#L379-L388 + watch: true, + }, + context, + (wco) => [getCommonConfig(wco), getStylesConfig(wco)], + ); + + const karma = await import('karma'); + + return [karma, (await webpackConfigurationTransformer?.(config)) ?? config]; +} + +/** + * @experimental Direct usage of this function is considered experimental. + */ +export function execute( + options: KarmaBuilderOptions, + context: BuilderContext, + transforms: { + webpackConfiguration?: ExecutionTransformer; + // The karma options transform cannot be async without a refactor of the builder implementation + karmaOptions?: (options: KarmaConfigOptions) => KarmaConfigOptions; + } = {}, +): Observable { + // Check Angular version. + assertCompatibleAngularVersion(context.workspaceRoot); + + let singleRun: boolean | undefined; + if (options.watch !== undefined) { + singleRun = !options.watch; + } + + return from(initialize(options, context, transforms.webpackConfiguration)).pipe( + switchMap(async ([karma, webpackConfig]) => { + // Determine project name from builder context target + const projectName = context.target?.project; + if (!projectName) { + throw new Error(`The 'karma' builder requires a target to be specified.`); + } + + const karmaOptions: KarmaConfigOptions = options.karmaConfig + ? {} + : getBuiltInKarmaConfig(karma, context.workspaceRoot, projectName); + + karmaOptions.singleRun = singleRun; + + // Convert browsers from a string to an array + if (options.browsers) { + karmaOptions.browsers = options.browsers.split(','); + } + + if (options.reporters) { + // Split along commas to make it more natural, and remove empty strings. + const reporters = options.reporters + .reduce((acc, curr) => acc.concat(curr.split(',')), []) + .filter((x) => !!x); + + if (reporters.length > 0) { + karmaOptions.reporters = reporters; + } + } + + if (!options.main) { + webpackConfig.entry ??= {}; + if (typeof webpackConfig.entry === 'object' && !Array.isArray(webpackConfig.entry)) { + if (Array.isArray(webpackConfig.entry['main'])) { + webpackConfig.entry['main'].push(getBuiltInMainFile()); + } else { + webpackConfig.entry['main'] = [getBuiltInMainFile()]; + } + } + } + + const projectMetadata = await context.getProjectMetadata(projectName); + const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '') as string; + + webpackConfig.plugins ??= []; + webpackConfig.plugins.push( + new FindTestsPlugin({ + include: options.include, + workspaceRoot: context.workspaceRoot, + projectSourceRoot: path.join(context.workspaceRoot, sourceRoot), + }), + ); + + karmaOptions.buildWebpack = { + options, + webpackConfig, + logger: context.logger, + }; + + const parsedKarmaConfig = await karma.config.parseConfig( + options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), + transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, + { promiseConfig: true, throwErrors: true }, + ); + + return [karma, parsedKarmaConfig] as [typeof karma, KarmaConfigOptions]; + }), + switchMap( + ([karma, karmaConfig]) => + new Observable((subscriber) => { + // Pass onto Karma to emit BuildEvents. + karmaConfig.buildWebpack ??= {}; + if (typeof karmaConfig.buildWebpack === 'object') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (karmaConfig.buildWebpack as any).failureCb ??= () => + subscriber.next({ success: false }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (karmaConfig.buildWebpack as any).successCb ??= () => + subscriber.next({ success: true }); + } + + // Complete the observable once the Karma server returns. + const karmaServer = new karma.Server(karmaConfig as Config, (exitCode) => { + subscriber.next({ success: exitCode === 0 }); + subscriber.complete(); + }); + + const karmaStart = karmaServer.start(); + + // Cleanup, signal Karma to exit. + return () => karmaStart.then(() => karmaServer.stop()); + }), + ), + defaultIfEmpty({ success: false }), + ); +} + +function getBuiltInKarmaConfig( + karma: typeof import('karma'), + workspaceRoot: string, + projectName: string, +): ConfigOptions & Record { + let coverageFolderName = projectName.charAt(0) === '@' ? projectName.slice(1) : projectName; + if (/[A-Z]/.test(coverageFolderName)) { + coverageFolderName = strings.dasherize(coverageFolderName); + } + + const workspaceRootRequire = createRequire(workspaceRoot + '/'); + + return { + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + 'karma-jasmine', + 'karma-chrome-launcher', + 'karma-jasmine-html-reporter', + 'karma-coverage', + '@angular-devkit/build-angular/plugins/karma', + ].map((p) => workspaceRootRequire(p)), + client: { + clearContext: false, // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true, // removes the duplicated traces + }, + coverageReporter: { + dir: path.join(workspaceRoot, 'coverage', coverageFolderName), + subdir: '.', + reporters: [{ type: 'html' }, { type: 'text-summary' }], + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: karma.constants.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + restartOnFileChange: true, + }; +} + +export { KarmaBuilderOptions }; +export default createBuilder & KarmaBuilderOptions>(execute); + +function getBuiltInMainFile(): string { + const content = Buffer.from( + ` + import { getTestBed } from '@angular/core/testing'; + import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, + } from '@angular/platform-browser-dynamic/testing'; + + // Initialize the Angular testing environment. + getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { + errorOnUnknownElements: true, + errorOnUnknownProperties: true + }); +`, + ).toString('base64'); + + return `ng-virtual-main.js!=!data:text/javascript;base64,${content}`; +} diff --git a/packages/angular_devkit/build_angular/src/builders/karma/schema.json b/packages/angular_devkit/build_angular/src/builders/karma/schema.json new file mode 100644 index 000000000000..f9a081b5bdd5 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/schema.json @@ -0,0 +1,294 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Karma Target", + "description": "Karma target options for Build Facade.", + "type": "object", + "properties": { + "main": { + "type": "string", + "description": "The name of the main entry-point file." + }, + "tsConfig": { + "type": "string", + "description": "The name of the TypeScript configuration file." + }, + "karmaConfig": { + "type": "string", + "description": "The name of the Karma configuration file." + }, + "polyfills": { + "description": "Polyfills to be included in the build.", + "oneOf": [ + { + "type": "array", + "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + { + "type": "string", + "description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'." + } + ] + }, + "assets": { + "type": "array", + "description": "List of static application assets.", + "default": [], + "items": { + "$ref": "#/definitions/assetPattern" + } + }, + "scripts": { + "description": "Global scripts to be included in the build.", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.[cm]?jsx?$" + } + ] + } + }, + "styles": { + "description": "Global styles to be included in the build.", + "type": "array", + "default": [], + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less)$" + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include.", + "pattern": "\\.(?:css|scss|sass|less)$" + } + ] + } + }, + "inlineStyleLanguage": { + "description": "The stylesheet language to use for the application's inline component styles.", + "type": "string", + "default": "css", + "enum": ["css", "less", "sass", "scss"] + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false + }, + "include": { + "type": "array", + "items": { + "type": "string" + }, + "default": ["**/*.spec.ts"], + "description": "Globs of files to include, relative to workspace or project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead." + }, + "sourceMap": { + "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", + "default": true, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output source maps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output source maps for all styles.", + "default": true + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages source maps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console while building.", + "default": true + }, + "watch": { + "type": "boolean", + "description": "Run build when files change." + }, + "poll": { + "type": "number", + "description": "Enable and define the file watching poll time period in milliseconds." + }, + "preserveSymlinks": { + "type": "boolean", + "description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set." + }, + "browsers": { + "type": "string", + "description": "Override which browsers tests are run against." + }, + "codeCoverage": { + "type": "boolean", + "description": "Output a code coverage report.", + "default": false + }, + "codeCoverageExclude": { + "type": "array", + "description": "Globs to exclude from code coverage.", + "items": { + "type": "string" + }, + "default": [] + }, + "fileReplacements": { + "description": "Replace compilation source files with other compilation source files in the build.", + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "replaceWith": { + "type": "string" + } + }, + "additionalProperties": false, + "required": ["src", "replaceWith"] + }, + { + "type": "object", + "properties": { + "replace": { + "type": "string" + }, + "with": { + "type": "string" + } + }, + "additionalProperties": false, + "required": ["replace", "with"] + } + ] + }, + "default": [] + }, + "reporters": { + "type": "array", + "description": "Karma reporters to use. Directly passed to the karma runner.", + "items": { + "type": "string" + } + }, + "webWorkerTsConfig": { + "type": "string", + "description": "TypeScript configuration for Web Worker modules." + } + }, + "additionalProperties": false, + "required": ["tsConfig"], + "definitions": { + "assetPattern": { + "oneOf": [ + { + "type": "object", + "properties": { + "glob": { + "type": "string", + "description": "The pattern to match." + }, + "input": { + "type": "string", + "description": "The input directory path in which to apply 'glob'. Defaults to the project root." + }, + "output": { + "type": "string", + "description": "Absolute path within the output." + }, + "ignore": { + "description": "An array of globs to ignore.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": ["glob", "input", "output"] + }, + { + "type": "string" + } + ] + } + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts new file mode 100644 index 000000000000..da0e83528562 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { tags } from '@angular-devkit/core'; +import { last, tap } from 'rxjs/operators'; +import { promisify } from 'util'; +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +// In each of the test below we'll have to call setTimeout to wait for the coverage +// analysis to be done. This is because karma-coverage performs the analysis +// asynchronously but the promise that it returns is not awaited by Karma. +// Coverage analysis begins when onRunComplete() is invoked, and output files +// are subsequently written to disk. For more information, see +// https://github.com/karma-runner/karma-coverage/blob/32acafa90ed621abd1df730edb44ae55a4009c2c/lib/reporter.js#L221 + +const setTimeoutPromise = promisify(setTimeout); +const coveragePath = 'coverage/lcov.info'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Behavior: "codeCoverage"', () => { + it('should generate coverage report when file was previously processed by Babel', async () => { + // Force Babel transformation. + await harness.appendToFile('src/app/app.component.ts', '// async'); + + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).toExist(); + }); + + it('should exit with non-zero code when coverage is below threshold', async () => { + await harness.modifyFile('karma.conf.js', (content) => + content.replace( + 'coverageReporter: {', + `coverageReporter: { + check: { + global: { + statements: 100, + lines: 100, + branches: 100, + functions: 100 + } + }, + `, + ), + ); + + await harness.appendToFile( + 'src/app/app.component.ts', + ` + export function nonCovered(): boolean { + return true; + } + `, + ); + + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + + await harness + .execute() + .pipe( + // In incremental mode, karma-coverage does not have the ability to mark a + // run as failed if code coverage does not pass. This is because it does + // the coverage asynchoronously and Karma does not await the promise + // returned by the plugin. + + // However the program must exit with non-zero exit code. + // This is a more common use case of coverage testing and must be supported. + last(), + tap((buildEvent) => expect(buildEvent.result?.success).toBeFalse()), + ) + .toPromise(); + }); + + it('should remapped instrumented code back to the original source', async () => { + await harness.modifyFile('karma.conf.js', (content) => content.replace('lcov', 'html')); + + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace( + `title = 'app'`, + tags.stripIndents` + title = 'app'; + + async foo() { + return 'foo'; + } + `, + ); + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + + harness + .expectFile('coverage/app.component.ts.html') + .content.toContain( + 'async foo()', + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts new file mode 100644 index 000000000000..b9fd460fa7c7 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Behavior: "Errors"', () => { + it('should fail when there is a TypeScript error', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + await harness.appendToFile('src/app/app.component.spec.ts', `console.lo('foo')`); + + const { result } = await harness.executeOnce({ + outputLogsOnFailure: false, + }); + + expect(result?.success).toBeFalse(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts new file mode 100644 index 000000000000..fdd972cb169c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Behavior: "module commonjs"', () => { + it('should work when module is commonjs', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + await harness.modifyFile('src/tsconfig.spec.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.compilerOptions.module = 'commonjs'; + + return JSON.stringify(tsConfig); + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts new file mode 100644 index 000000000000..10bd13eabf76 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { concatMap, count, debounceTime, take, timeout } from 'rxjs/operators'; +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds"', () => { + it('recovers from compilation failures in watch mode', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + watch: true, + }); + + const goodFile = await harness.readFile('src/app/app.component.spec.ts'); + + const buildCount = await harness + .execute({ outputLogsOnFailure: false }) + .pipe( + timeout(60000), + debounceTime(500), + concatMap(async ({ result }, index) => { + switch (index) { + case 0: + // Karma run should succeed. + // Add a compilation error. + expect(result?.success).toBeTrue(); + // Add an syntax error to a non-main file. + await harness.appendToFile('src/app/app.component.spec.ts', `error`); + break; + + case 1: + expect(result?.success).toBeFalse(); + await harness.writeFile('src/app/app.component.spec.ts', goodFile); + break; + + case 2: + expect(result?.success).toBeTrue(); + break; + } + }), + take(3), + count(), + ) + .toPromise(); + + expect(buildCount).toBe(3); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts new file mode 100644 index 000000000000..ddf04ff5e8b2 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts @@ -0,0 +1,101 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Option: "assets"', () => { + it('includes assets', async () => { + await harness.writeFiles({ + './src/string-file-asset.txt': 'string-file-asset.txt', + './src/string-folder-asset/file.txt': 'string-folder-asset.txt', + './src/glob-asset.txt': 'glob-asset.txt', + './src/folder/folder-asset.txt': 'folder-asset.txt', + './src/output-asset.txt': 'output-asset.txt', + './src/app/app.module.ts': ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + import { HttpClientModule } from '@angular/common/http'; + import { AppComponent } from './app.component'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + HttpClientModule + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + import { HttpClient } from '@angular/common/http'; + + @Component({ + selector: 'app-root', + template: '

{{ asset.content }}

' + }) + export class AppComponent { + public assets = [ + { path: './string-file-asset.txt', content: '' }, + { path: './string-folder-asset/file.txt', content: '' }, + { path: './glob-asset.txt', content: '' }, + { path: './folder/folder-asset.txt', content: '' }, + { path: './output-folder/output-asset.txt', content: '' }, + ]; + constructor(private http: HttpClient) { + this.assets.forEach(asset => http.get(asset.path, { responseType: 'text' }) + .subscribe(res => asset.content = res)); + } + }`, + './src/app/app.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { HttpClientModule } from '@angular/common/http'; + import { AppComponent } from './app.component'; + + describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + HttpClientModule + ], + declarations: [ + AppComponent + ] + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + });`, + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + assets: [ + 'src/string-file-asset.txt', + 'src/string-folder-asset', + { glob: 'glob-asset.txt', input: 'src/', output: '/' }, + { glob: 'output-asset.txt', input: 'src/', output: '/output-folder' }, + { glob: '**/*', input: 'src/folder', output: '/folder' }, + ], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts new file mode 100644 index 000000000000..db43d5f33b46 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { promisify } from 'util'; +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +// In each of the test below we'll have to call setTimeout to wait for the coverage +// analysis to be done. This is because karma-coverage performs the analysis +// asynchronously but the promise that it returns is not awaited by Karma. +// Coverage analysis begins when onRunComplete() is invoked, and output files +// are subsequently written to disk. For more information, see +// https://github.com/karma-runner/karma-coverage/blob/32acafa90ed621abd1df730edb44ae55a4009c2c/lib/reporter.js#L221 + +const setTimeoutPromise = promisify(setTimeout); +const coveragePath = 'coverage/lcov.info'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Option: "codeCoverageExclude"', () => { + it('should exclude file from coverage when set', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + codeCoverageExclude: ['**/app.component.ts'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).content.not.toContain('app.component.ts'); + }); + + it('should exclude file from coverage when set when glob starts with a forward slash', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + codeCoverageExclude: ['/**/app.component.ts'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).content.not.toContain('app.component.ts'); + }); + + it('should not exclude file from coverage when set', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).content.toContain('app.component.ts'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts new file mode 100644 index 000000000000..22af81094679 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts @@ -0,0 +1,112 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { promisify } from 'util'; +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +// In each of the test below we'll have to call setTimeout to wait for the coverage +// analysis to be done. This is because karma-coverage performs the analysis +// asynchronously but the promise that it returns is not awaited by Karma. +// Coverage analysis begins when onRunComplete() is invoked, and output files +// are subsequently written to disk. For more information, see +// https://github.com/karma-runner/karma-coverage/blob/32acafa90ed621abd1df730edb44ae55a4009c2c/lib/reporter.js#L221 + +const setTimeoutPromise = promisify(setTimeout); +const coveragePath = 'coverage/lcov.info'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Option: "codeCoverage"', () => { + it('should generate coverage report when option is set to true', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).toExist(); + }); + + it('should not generate coverage report when option is set to false', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: false, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).toNotExist(); + }); + + it('should not generate coverage report when option is unset', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).toNotExist(); + }); + + it(`should collect coverage from paths in 'sourceRoot'`, async () => { + await harness.writeFiles({ + './dist/my-lib/index.d.ts': ` + export declare const title = 'app'; + `, + './dist/my-lib/index.js': ` + export const title = 'app'; + `, + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + import { title } from 'my-lib'; + + @Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] + }) + export class AppComponent { + title = title; + } + `, + }); + await harness.modifyFile('tsconfig.json', (content) => + content.replace( + /"baseUrl": ".\/",/, + ` + "baseUrl": "./", + "paths": { + "my-lib": [ + "./dist/my-lib" + ] + }, + `, + ), + ); + + harness.useTarget('test', { + ...BASE_OPTIONS, + codeCoverage: true, + }); + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + await setTimeoutPromise(1000); + harness.expectFile(coveragePath).content.not.toContain('my-lib'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts new file mode 100644 index 000000000000..0d9370b971a4 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts @@ -0,0 +1,98 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Option: "include"', () => { + it(`should fail when includes doesn't match any files`, async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + include: ['abc.spec.ts', 'def.spec.ts'], + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringContaining( + 'Specified patterns: "abc.spec.ts, def.spec.ts" did not match any spec files.', + ), + }), + ); + }); + + [ + { + test: 'relative path from workspace to spec', + input: ['src/app/app.component.spec.ts'], + }, + { + test: 'relative path from workspace to file', + input: ['src/app/app.component.ts'], + }, + { + test: 'relative path from project root to spec', + input: ['app/services/test.service.spec.ts'], + }, + { + test: 'relative path from project root to file', + input: ['app/services/test.service.ts'], + }, + { + test: 'relative path from workspace to directory', + input: ['src/app/services'], + }, + { + test: 'relative path from project root to directory', + input: ['app/services'], + }, + { + test: 'glob with spec suffix', + input: ['**/*.pipe.spec.ts', '**/*.pipe.spec.ts', '**/*test.service.spec.ts'], + }, + { + test: 'glob with forward slash and spec suffix', + input: ['/**/*test.service.spec.ts'], + }, + ].forEach((options, index) => { + it(`should work with ${options.test} (${index})`, async () => { + await harness.writeFiles({ + 'src/app/services/test.service.spec.ts': ` + describe('TestService', () => { + it('should succeed', () => { + expect(true).toBe(true); + }); + });`, + 'src/app/failing.service.spec.ts': ` + describe('FailingService', () => { + it('should be ignored', () => { + expect(true).toBe(false); + }); + });`, + 'src/app/property.pipe.spec.ts': ` + describe('PropertyPipe', () => { + it('should succeed', () => { + expect(true).toBe(true); + }); + });`, + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + include: options.input, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts new file mode 100644 index 000000000000..699aeebcb551 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts @@ -0,0 +1,150 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Option: "styles"', () => { + it(`processes 'styles.css' styles`, async () => { + await harness.writeFiles({ + 'src/styles.css': 'p {display: none}', + 'src/app/app.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '

Hello World

' + }) + export class AppComponent { + } + `, + 'src/app/app.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { AppComponent } from './app.component'; + + describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + ], + declarations: [ + AppComponent + ] + }).compileComponents(); + }); + + it('should not contain text that is hidden via css', () => { + const fixture = TestBed.createComponent(AppComponent); + expect(fixture.nativeElement.innerText).not.toContain('Hello World'); + }); + });`, + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + + it('processes style with bundleName', async () => { + await harness.writeFiles({ + 'src/dark-theme.css': '', + 'src/app/app.module.ts': ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + import { HttpClientModule } from '@angular/common/http'; + import { AppComponent } from './app.component'; + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + HttpClientModule + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + 'src/app/app.component.ts': ` + import { Component } from '@angular/core'; + import { HttpClient } from '@angular/common/http'; + @Component({ + selector: 'app-root', + template: '

{{ asset.content }}

' + }) + export class AppComponent { + public assets = [ + { path: './dark-theme.css', content: '' }, + ]; + constructor(private http: HttpClient) { + this.assets.forEach(asset => http.get(asset.path, { responseType: 'text' }) + .subscribe(res => asset.content = res)); + } + }`, + 'src/app/app.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { HttpClientModule } from '@angular/common/http'; + import { AppComponent } from './app.component'; + describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + HttpClientModule + ], + declarations: [ + AppComponent + ] + }).compileComponents(); + }); + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + });`, + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + styles: [ + { + inject: false, + input: 'src/dark-theme.css', + bundleName: 'dark-theme', + }, + ], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + + it('fails and shows an error if style does not exist', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + styles: ['src/test-style-a.css'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(`Can't resolve 'src/test-style-a.css'`), + }), + ); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts new file mode 100644 index 000000000000..e45a5feddee8 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { + describe('Option: "webWorkerTsConfig"', () => { + beforeEach(async () => { + await harness.writeFiles({ + 'src/tsconfig.worker.json': ` + { + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/worker", + "lib": [ + "es2018", + "webworker" + ], + "types": [] + }, + "include": [ + "**/*.worker.ts", + ] + }`, + 'src/app/app.worker.ts': ` + /// + + const prefix: string = 'Data: '; + addEventListener('message', ({ data }) => { + postMessage(prefix + data); + }); + `, + 'src/app/app.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '' + }) + export class AppComponent { + worker = new Worker(new URL('./app.worker', import.meta.url)); + } + `, + './src/app/app.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { AppComponent } from './app.component'; + + describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + AppComponent + ] + }).compileComponents(); + }); + + it('worker should be defined', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.worker).toBeDefined(); + }); + });`, + }); + }); + + it(`should not parse web workers when "webWorkerTsConfig" is not set or set to undefined.`, async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + webWorkerTsConfig: undefined, + }); + + await harness.writeFile( + './src/app/app.component.spec.ts', + ` + import { TestBed } from '@angular/core/testing'; + import { AppComponent } from './app.component'; + + describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + AppComponent + ] + }).compileComponents(); + }); + + it('worker should throw', () => { + expect(() => TestBed.createComponent(AppComponent)) + .toThrowError(/Failed to construct 'Worker'/); + }); + });`, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + + it(`should parse web workers when "webWorkerTsConfig" is set.`, async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + webWorkerTsConfig: 'src/tsconfig.worker.json', + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts new file mode 100644 index 000000000000..86449d8c9d0a --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Schema } from '../schema'; + +export { describeBuilder } from '../../../testing'; + +export const KARMA_BUILDER_INFO = Object.freeze({ + name: '@angular-devkit/build-angular:karma', + schemaPath: __dirname + '/../schema.json', +}); + +/** + * Contains all required karma builder fields. + * Also disables progress reporting to minimize logging output. + */ +export const BASE_OPTIONS = Object.freeze({ + polyfills: ['zone.js', 'zone.js/testing'], + tsConfig: 'src/tsconfig.spec.json', + karmaConfig: 'karma.conf.js', + browsers: 'ChromeHeadlessCI', + progress: false, + watch: false, +}); diff --git a/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts b/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts new file mode 100644 index 000000000000..0122bce230e4 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { join, resolve } from 'path'; +import { Observable, from, of } from 'rxjs'; +import { catchError, mapTo, switchMap } from 'rxjs/operators'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { Schema as NgPackagrBuilderOptions } from './schema'; + +/** + * @experimental Direct usage of this function is considered experimental. + */ +export function execute( + options: NgPackagrBuilderOptions, + context: BuilderContext, +): Observable { + return from( + (async () => { + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + const root = context.workspaceRoot; + const packager = (await import('ng-packagr')).ngPackagr(); + + packager.forProject(resolve(root, options.project)); + + if (options.tsConfig) { + packager.withTsConfig(resolve(root, options.tsConfig)); + } + + const projectName = context.target?.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + + const metadata = await context.getProjectMetadata(projectName); + const { enabled: cacheEnabled, path: cacheDirectory } = normalizeCacheOptions( + metadata, + context.workspaceRoot, + ); + + const ngPackagrOptions = { + cacheEnabled, + cacheDirectory: join(cacheDirectory, 'ng-packagr'), + }; + + return { packager, ngPackagrOptions }; + })(), + ).pipe( + switchMap(({ packager, ngPackagrOptions }) => + options.watch ? packager.watch(ngPackagrOptions) : packager.build(ngPackagrOptions), + ), + mapTo({ success: true }), + catchError((err) => of({ success: false, error: err.message })), + ); +} + +export { NgPackagrBuilderOptions }; +export default createBuilder & NgPackagrBuilderOptions>(execute); diff --git a/packages/angular_devkit/build_angular/src/builders/ng-packagr/schema.json b/packages/angular_devkit/build_angular/src/builders/ng-packagr/schema.json new file mode 100644 index 000000000000..9fd60637cb00 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/ng-packagr/schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "ng-packagr Target", + "description": "ng-packagr target options for Build Architect. Use to build library projects.", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The file path for the ng-packagr configuration file, relative to the current workspace." + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + } + }, + "additionalProperties": false, + "required": ["project"] +} diff --git a/packages/angular_devkit/build_angular/src/builders/ng-packagr/works_spec.ts b/packages/angular_devkit/build_angular/src/builders/ng-packagr/works_spec.ts new file mode 100644 index 000000000000..14ab1f699d52 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/ng-packagr/works_spec.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; +import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing'; +import { + getSystemPath, + join, + normalize, + schema, + virtualFs, + workspaces, +} from '@angular-devkit/core'; +import { debounceTime, map, take, tap } from 'rxjs/operators'; + +// Default timeout for large specs is 2.5 minutes. +jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000; + +describe('NgPackagr Builder', () => { + const workspaceRoot = join(normalize(__dirname), `../../../test/hello-world-lib/`); + const host = new TestProjectHost(workspaceRoot); + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + + const registry = new schema.CoreSchemaRegistry(); + registry.addPostTransform(schema.transforms.addUndefinedDefaults); + + const workspaceSysPath = getSystemPath(host.root()); + const { workspace } = await workspaces.readWorkspace( + workspaceSysPath, + workspaces.createWorkspaceHost(host), + ); + const architectHost = new TestingArchitectHost( + workspaceSysPath, + workspaceSysPath, + new WorkspaceNodeModulesArchitectHost(workspace, workspaceSysPath), + ); + + architect = new Architect(architectHost, registry); + }); + + afterEach(() => host.restore().toPromise()); + + it('builds and packages a library', async () => { + const run = await architect.scheduleTarget({ project: 'lib', target: 'build' }); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + + expect(host.scopedSync().exists(normalize('./dist/lib/fesm2015/lib.mjs'))).toBe(true); + const content = virtualFs.fileBufferToString( + host.scopedSync().read(normalize('./dist/lib/fesm2015/lib.mjs')), + ); + expect(content).toContain('lib works'); + + expect(content).toContain('ɵcmp'); + }); + + it('rebuilds on TS file changes', async () => { + const goldenValueFiles: { [path: string]: string } = { + 'projects/lib/src/lib/lib.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'lib', + template: 'lib update works!' + }) + export class LibComponent { } + `, + }; + + const run = await architect.scheduleTarget( + { project: 'lib', target: 'build' }, + { watch: true }, + ); + + let buildNumber = 0; + + await run.output + .pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + debounceTime(1000), + map(() => { + const fileName = './dist/lib/fesm2015/lib.mjs'; + const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); + + return content; + }), + tap((content) => { + buildNumber += 1; + switch (buildNumber) { + case 1: + expect(content).toMatch(/lib works/); + host.writeMultipleFiles(goldenValueFiles); + break; + + case 2: + expect(content).toMatch(/lib update works/); + break; + default: + break; + } + }), + take(2), + ) + .toPromise(); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/protractor/index.ts b/packages/angular_devkit/build_angular/src/builders/protractor/index.ts new file mode 100644 index 000000000000..4da2983cae00 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/protractor/index.ts @@ -0,0 +1,175 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { + BuilderContext, + BuilderOutput, + createBuilder, + targetFromTargetString, +} from '@angular-devkit/architect'; +import { json, tags } from '@angular-devkit/core'; +import { resolve } from 'path'; +import * as url from 'url'; +import { runModuleAsObservableFork } from '../../utils'; +import { assertIsError } from '../../utils/error'; +import { DevServerBuilderOptions } from '../dev-server/index'; +import { Schema as ProtractorBuilderOptions } from './schema'; + +interface JasmineNodeOpts { + jasmineNodeOpts: { + grep?: string; + invertGrep?: boolean; + }; +} + +function runProtractor(root: string, options: ProtractorBuilderOptions): Promise { + const additionalProtractorConfig: Partial & Partial = { + baseUrl: options.baseUrl, + specs: options.specs && options.specs.length ? options.specs : undefined, + suite: options.suite, + jasmineNodeOpts: { + grep: options.grep, + invertGrep: options.invertGrep, + }, + }; + + // TODO: Protractor manages process.exit itself, so this target will allways quit the + // process. To work around this we run it in a subprocess. + // https://github.com/angular/protractor/issues/4160 + return runModuleAsObservableFork(root, 'protractor/built/launcher', 'init', [ + resolve(root, options.protractorConfig), + additionalProtractorConfig, + ]).toPromise() as Promise; +} + +async function updateWebdriver() { + // The webdriver-manager update command can only be accessed via a deep import. + const webdriverDeepImport = 'webdriver-manager/built/lib/cmds/update'; + + let path; + try { + const protractorPath = require.resolve('protractor'); + + path = require.resolve(webdriverDeepImport, { paths: [protractorPath] }); + } catch (error) { + assertIsError(error); + if (error.code !== 'MODULE_NOT_FOUND') { + throw error; + } + } + + if (!path) { + throw new Error(tags.stripIndents` + Cannot automatically find webdriver-manager to update. + Update webdriver-manager manually and run 'ng e2e --no-webdriver-update' instead. + `); + } + + const webdriverUpdate = await import(path); + // const webdriverUpdate = await import(path) as typeof import ('webdriver-manager/built/lib/cmds/update'); + + // run `webdriver-manager update --standalone false --gecko false --quiet` + // if you change this, update the command comment in prev line + return webdriverUpdate.program.run({ + standalone: false, + gecko: false, + quiet: true, + } as unknown as JSON); +} + +export { ProtractorBuilderOptions }; + +/** + * @experimental Direct usage of this function is considered experimental. + */ +export async function execute( + options: ProtractorBuilderOptions, + context: BuilderContext, +): Promise { + context.logger.warn( + 'Protractor has been deprecated including its support in the Angular CLI. For additional information and alternatives, please see https://github.com/angular/protractor/issues/5502.', + ); + + // ensure that only one of these options is used + if (options.devServerTarget && options.baseUrl) { + throw new Error(tags.stripIndents` + The 'baseUrl' option cannot be used with 'devServerTarget'. + When present, 'devServerTarget' will be used to automatically setup 'baseUrl' for Protractor. + `); + } + + if (options.webdriverUpdate) { + await updateWebdriver(); + } + + let baseUrl = options.baseUrl; + let server; + + try { + if (options.devServerTarget) { + const target = targetFromTargetString(options.devServerTarget); + const serverOptions = await context.getTargetOptions(target); + + const overrides = { + watch: false, + liveReload: false, + } as DevServerBuilderOptions & json.JsonObject; + + if (options.host !== undefined) { + overrides.host = options.host; + } else if (typeof serverOptions.host === 'string') { + options.host = serverOptions.host; + } else { + options.host = overrides.host = 'localhost'; + } + + if (options.port !== undefined) { + overrides.port = options.port; + } else if (typeof serverOptions.port === 'number') { + options.port = serverOptions.port; + } + + server = await context.scheduleTarget(target, overrides); + const result = await server.result; + if (!result.success) { + return { success: false }; + } + + if (typeof serverOptions.publicHost === 'string') { + let publicHost = serverOptions.publicHost; + if (!/^\w+:\/\//.test(publicHost)) { + publicHost = `${serverOptions.ssl ? 'https' : 'http'}://${publicHost}`; + } + const clientUrl = url.parse(publicHost); + baseUrl = url.format(clientUrl); + } else if (typeof result.baseUrl === 'string') { + baseUrl = result.baseUrl; + } else if (typeof result.port === 'number') { + baseUrl = url.format({ + protocol: serverOptions.ssl ? 'https' : 'http', + hostname: options.host, + port: result.port.toString(), + }); + } + } + + // Like the baseUrl in protractor config file when using the API we need to add + // a trailing slash when provide to the baseUrl. + if (baseUrl && !baseUrl.endsWith('/')) { + baseUrl += '/'; + } + + return await runProtractor(context.workspaceRoot, { ...options, baseUrl }); + } catch { + return { success: false }; + } finally { + await server?.stop(); + } +} + +export default createBuilder(execute); diff --git a/packages/angular_devkit/build_angular/src/builders/protractor/schema.json b/packages/angular_devkit/build_angular/src/builders/protractor/schema.json new file mode 100644 index 000000000000..286a315a96a9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/protractor/schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Protractor Target", + "description": "Protractor target options for Build Facade.", + "type": "object", + "properties": { + "protractorConfig": { + "type": "string", + "description": "The name of the Protractor configuration file." + }, + "devServerTarget": { + "type": "string", + "description": "A dev-server builder target to run tests against in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", + "pattern": "^([^:\\s]+:[^:\\s]+(:[^\\s]+)?)?$" + }, + "grep": { + "type": "string", + "description": "Execute specs whose names match the pattern, which is internally compiled to a RegExp." + }, + "invertGrep": { + "type": "boolean", + "description": "Invert the selection specified by the 'grep' option.", + "default": false + }, + "specs": { + "type": "array", + "description": "Override specs in the protractor config.", + "default": [], + "items": { + "type": "string", + "description": "Spec name." + } + }, + "suite": { + "type": "string", + "description": "Override suite in the protractor config." + }, + "webdriverUpdate": { + "type": "boolean", + "description": "Try to update webdriver.", + "default": true + }, + "port": { + "type": "number", + "description": "The port to use to serve the application." + }, + "host": { + "type": "string", + "description": "Host to listen on." + }, + "baseUrl": { + "type": "string", + "description": "Base URL for protractor to connect to." + } + }, + "additionalProperties": false, + "required": ["protractorConfig"] +} diff --git a/packages/angular_devkit/build_angular/src/builders/protractor/works_spec.ts b/packages/angular_devkit/build_angular/src/builders/protractor/works_spec.ts new file mode 100644 index 000000000000..acce20cf735d --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/protractor/works_spec.ts @@ -0,0 +1,136 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Architect } from '@angular-devkit/architect'; +import { JsonObject, normalize } from '@angular-devkit/core'; +import { createArchitect, host, protractorTargetSpec } from '../../testing/test-utils'; + +describe('Protractor Builder', () => { + let architect: Architect; + + beforeEach(async () => { + await host.initialize().toPromise(); + architect = (await createArchitect(host.root())).architect; + }); + + afterEach(() => host.restore().toPromise()); + + it('executes tests with automatic dev server usage', async () => { + const run = await architect.scheduleTarget(protractorTargetSpec); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); + + it('fails with no devServerTarget and no standalone server', async () => { + const overrides = { devServerTarget: undefined } as unknown as JsonObject; + const run = await architect.scheduleTarget(protractorTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); + + await run.stop(); + }); + + it('overrides protractor specs', async () => { + host + .scopedSync() + .rename(normalize('./e2e/app.e2e-spec.ts'), normalize('./e2e/renamed-app.e2e.spec.ts')); + + const overrides = { specs: ['./e2e/renamed-app.e2e.spec.ts'] }; + const run = await architect.scheduleTarget(protractorTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); + + it('overrides protractor suites', async () => { + host + .scopedSync() + .rename(normalize('./e2e/app.e2e-spec.ts'), normalize('./e2e/renamed-app.e2e-spec.ts')); + + // Suites block needs to be added in the protractor.conf.js file to test suites + host.replaceInFile( + 'protractor.conf.js', + `allScriptsTimeout: 11000,`, + ` + allScriptsTimeout: 11000, + suites: { + app: './e2e/app.e2e-spec.ts' + }, + `, + ); + + const overrides = { suite: 'app' }; + const run = await architect.scheduleTarget(protractorTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); + + it('supports automatic port assignment (port = 0)', async () => { + const overrides = { port: 0 }; + const run = await architect.scheduleTarget(protractorTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); + + it('supports dev server builder with browser builder base HREF option', async () => { + host.replaceInFile( + 'angular.json', + '"main": "src/main.ts",', + '"main": "src/main.ts", "baseHref": "/base/",', + ); + // Need to reset architect to use the modified config + architect = (await createArchitect(host.root())).architect; + + const run = await architect.scheduleTarget(protractorTargetSpec); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); + + it('supports running tests by pattern', async () => { + host.writeMultipleFiles({ + 'e2e/app.e2e-spec.ts': ` + it('should succeed', () => expect(true).toBeTruthy()); + it('should fail', () => expect(false).toBeTruthy()); + `, + }); + + const overrides = { grep: 'succeed' }; + + const run = await architect.scheduleTarget(protractorTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); + + it('supports running tests excluding a pattern', async () => { + host.writeMultipleFiles({ + 'e2e/app.e2e-spec.ts': ` + it('should succeed', () => expect(true).toBeTruthy()); + it('should fail', () => expect(false).toBeTruthy()); + `, + }); + + const overrides = { grep: 'fail', invertGrep: true }; + + const run = await architect.scheduleTarget(protractorTargetSpec, overrides); + + await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); + + await run.stop(); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/server/index.ts b/packages/angular_devkit/build_angular/src/builders/server/index.ts new file mode 100644 index 000000000000..dd38c82af7d2 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/index.ts @@ -0,0 +1,196 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { runWebpack } from '@angular-devkit/build-webpack'; +import * as path from 'path'; +import { Observable, from } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import webpack, { Configuration } from 'webpack'; +import { ExecutionTransformer } from '../../transforms'; +import { NormalizedBrowserBuilderSchema, deleteOutputDir } from '../../utils'; +import { i18nInlineEmittedFiles } from '../../utils/i18n-inlining'; +import { I18nOptions } from '../../utils/i18n-options'; +import { ensureOutputPaths } from '../../utils/output-paths'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { assertCompatibleAngularVersion } from '../../utils/version'; +import { + BrowserWebpackConfigOptions, + generateI18nBrowserWebpackConfigFromContext, +} from '../../utils/webpack-browser-config'; +import { getCommonConfig, getStylesConfig } from '../../webpack/configs'; +import { isPlatformServerInstalled } from '../../webpack/utils/helpers'; +import { webpackStatsLogger } from '../../webpack/utils/stats'; +import { Schema as ServerBuilderOptions } from './schema'; + +/** + * @experimental Direct usage of this type is considered experimental. + */ +export type ServerBuilderOutput = BuilderOutput & { + baseOutputPath: string; + /** + * @deprecated in version 14. Use 'outputs' instead. + */ + outputPaths: string[]; + /** + * @deprecated in version 9. Use 'outputs' instead. + */ + outputPath: string; + + outputs: { + locale?: string; + path: string; + }[]; +}; + +export { ServerBuilderOptions }; + +/** + * @experimental Direct usage of this function is considered experimental. + */ +export function execute( + options: ServerBuilderOptions, + context: BuilderContext, + transforms: { + webpackConfiguration?: ExecutionTransformer; + } = {}, +): Observable { + const root = context.workspaceRoot; + + // Check Angular version. + assertCompatibleAngularVersion(root); + + const baseOutputPath = path.resolve(root, options.outputPath); + let outputPaths: undefined | Map; + + return from(initialize(options, context, transforms.webpackConfiguration)).pipe( + concatMap(({ config, i18n }) => { + return runWebpack(config, context, { + webpackFactory: require('webpack') as typeof webpack, + logging: (stats, config) => { + if (options.verbose) { + context.logger.info(stats.toString(config.stats)); + } + }, + }).pipe( + concatMap(async (output) => { + const { emittedFiles = [], outputPath, webpackStats } = output; + if (!webpackStats) { + throw new Error('Webpack stats build result is required.'); + } + + let success = output.success; + if (success && i18n.shouldInline) { + outputPaths = ensureOutputPaths(baseOutputPath, i18n); + + success = await i18nInlineEmittedFiles( + context, + emittedFiles, + i18n, + baseOutputPath, + Array.from(outputPaths.values()), + [], + outputPath, + options.i18nMissingTranslation, + ); + } + + webpackStatsLogger(context.logger, webpackStats, config); + + return { ...output, success }; + }), + ); + }), + map((output) => { + if (!output.success) { + return output as ServerBuilderOutput; + } + + return { + ...output, + baseOutputPath, + outputPath: baseOutputPath, + outputPaths: outputPaths || [baseOutputPath], + outputs: (outputPaths && + [...outputPaths.entries()].map(([locale, path]) => ({ + locale, + path, + }))) || { + path: baseOutputPath, + }, + } as ServerBuilderOutput; + }), + ); +} + +export default createBuilder(execute); + +async function initialize( + options: ServerBuilderOptions, + context: BuilderContext, + webpackConfigurationTransform?: ExecutionTransformer, +): Promise<{ + config: webpack.Configuration; + i18n: I18nOptions; +}> { + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + const browserslist = (await import('browserslist')).default; + const originalOutputPath = options.outputPath; + const { config, i18n } = await generateI18nBrowserWebpackConfigFromContext( + { + ...options, + buildOptimizer: false, + aot: true, + platform: 'server', + } as NormalizedBrowserBuilderSchema, + context, + (wco) => { + // We use the platform to determine the JavaScript syntax output. + wco.buildOptions.supportedBrowsers ??= []; + wco.buildOptions.supportedBrowsers.push(...browserslist('maintained node versions')); + + return [getPlatformServerExportsConfig(wco), getCommonConfig(wco), getStylesConfig(wco)]; + }, + ); + + if (options.deleteOutputPath) { + deleteOutputDir(context.workspaceRoot, originalOutputPath); + } + + const transformedConfig = (await webpackConfigurationTransform?.(config)) ?? config; + + return { config: transformedConfig, i18n }; +} + +/** + * Add `@angular/platform-server` exports. + * This is needed so that DI tokens can be referenced and set at runtime outside of the bundle. + */ +function getPlatformServerExportsConfig(wco: BrowserWebpackConfigOptions): Partial { + // Add `@angular/platform-server` exports. + // This is needed so that DI tokens can be referenced and set at runtime outside of the bundle. + + // Only add `@angular/platform-server` exports when it is installed. + // In some cases this builder is used when `@angular/platform-server` is not installed. + // Example: when using `@nguniversal/common/clover` which does not need `@angular/platform-server`. + + return isPlatformServerInstalled(wco.root) + ? { + module: { + rules: [ + { + loader: require.resolve('./platform-server-exports-loader'), + include: [path.resolve(wco.root, wco.buildOptions.main)], + }, + ], + }, + } + : {}; +} diff --git a/packages/angular_devkit/build_angular/src/builders/server/platform-server-exports-loader.ts b/packages/angular_devkit/build_angular/src/builders/server/platform-server-exports-loader.ts new file mode 100644 index 000000000000..51b0c5741374 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/platform-server-exports-loader.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * This loader is needed to add additional exports and is a workaround for a Webpack bug that doesn't + * allow exports from multiple files in the same entry. + * @see https://github.com/webpack/webpack/issues/15936. + */ +export default function ( + this: import('webpack').LoaderContext<{}>, + content: string, + map: Parameters[1], +) { + const source = `${content} + + // EXPORTS added by @angular-devkit/build-angular + export { renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server'; + `; + + this.callback(null, source, map); + + return; +} diff --git a/packages/angular_devkit/build_angular/src/builders/server/schema.json b/packages/angular_devkit/build_angular/src/builders/server/schema.json new file mode 100644 index 000000000000..73d6088c1f38 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/schema.json @@ -0,0 +1,250 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "BuildAngularWebpackServerSchema", + "title": "Universal Target", + "type": "object", + "properties": { + "main": { + "type": "string", + "description": "The name of the main entry-point file." + }, + "tsConfig": { + "type": "string", + "default": "tsconfig.app.json", + "description": "The name of the TypeScript configuration file." + }, + "inlineStyleLanguage": { + "description": "The stylesheet language to use for the application's inline component styles.", + "type": "string", + "default": "css", + "enum": ["css", "less", "sass", "scss"] + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false + }, + "optimization": { + "description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking and dead-code elimination. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.", + "default": true, + "x-user-analytics": "ep.ng_optimization", + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Enables optimization of the scripts output.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Enables optimization of the styles output.", + "default": true + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "fileReplacements": { + "description": "Replace compilation source files with other compilation source files in the build.", + "type": "array", + "items": { + "$ref": "#/definitions/fileReplacement" + }, + "default": [] + }, + "outputPath": { + "type": "string", + "description": "Path where output will be placed." + }, + "resourcesOutputPath": { + "type": "string", + "description": "The path where style resources will be placed, relative to outputPath." + }, + "sourceMap": { + "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", + "default": false, + "oneOf": [ + { + "type": "object", + "properties": { + "scripts": { + "type": "boolean", + "description": "Output source maps for all scripts.", + "default": true + }, + "styles": { + "type": "boolean", + "description": "Output source maps for all styles.", + "default": true + }, + "hidden": { + "type": "boolean", + "description": "Output source maps used for error reporting tools.", + "default": false + }, + "vendor": { + "type": "boolean", + "description": "Resolve vendor packages source maps.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "deployUrl": { + "type": "string", + "description": "URL where files will be deployed.", + "x-deprecated": "Use \"baseHref\" browser builder option, \"APP_BASE_HREF\" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url." + }, + "vendorChunk": { + "type": "boolean", + "description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.", + "default": false + }, + "verbose": { + "type": "boolean", + "description": "Adds more details to output logging.", + "default": false + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console while building.", + "default": true + }, + "i18nMissingTranslation": { + "type": "string", + "description": "How to handle missing translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "i18nDuplicateTranslation": { + "type": "string", + "description": "How to handle duplicate translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "localize": { + "description": "Translate the bundles in one or more locales.", + "oneOf": [ + { + "type": "boolean", + "description": "Translate all locales." + }, + { + "type": "array", + "description": "List of locales ID's to translate.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$" + } + } + ] + }, + "outputHashing": { + "type": "string", + "description": "Define the output filename cache-busting hashing mode.", + "default": "none", + "enum": ["none", "all", "media", "bundles"] + }, + "deleteOutputPath": { + "type": "boolean", + "description": "Delete the output path before building.", + "default": true + }, + "preserveSymlinks": { + "type": "boolean", + "description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set." + }, + "extractLicenses": { + "type": "boolean", + "description": "Extract all licenses in a separate file, in the case of production builds only.", + "default": true + }, + "namedChunks": { + "type": "boolean", + "description": "Use file name for lazy loaded chunks.", + "default": false + }, + "externalDependencies": { + "description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "statsJson": { + "type": "boolean", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", + "default": false + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + }, + "poll": { + "type": "number", + "description": "Enable and define the file watching poll time period in milliseconds." + } + }, + "additionalProperties": false, + "required": ["outputPath", "main", "tsConfig"], + "definitions": { + "fileReplacement": { + "oneOf": [ + { + "type": "object", + "properties": { + "src": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + }, + "replaceWith": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + } + }, + "additionalProperties": false, + "required": ["src", "replaceWith"] + }, + { + "type": "object", + "properties": { + "replace": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + }, + "with": { + "type": "string", + "pattern": "\\.(([cm]?j|t)sx?|json)$" + } + }, + "additionalProperties": false, + "required": ["replace", "with"] + } + ] + } + } +} diff --git a/packages/angular_devkit/build_angular/src/builders/server/tests/behavior/web-workers_spec.ts b/packages/angular_devkit/build_angular/src/builders/server/tests/behavior/web-workers_spec.ts new file mode 100644 index 000000000000..b9cd9618cb78 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/tests/behavior/web-workers_spec.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => { + describe('Behavior: "Errors"', () => { + it('should not try to resolve web-workers', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + await harness.writeFiles({ + 'src/app/app.worker.ts': ` + /// + + const foo: string = 'hello world'; + addEventListener('message', ({ data }) => { + postMessage(foo); + }); + `, + 'src/main.server.ts': ` + if (typeof Worker !== 'undefined') { + const worker = new Worker(new URL('./app/app.worker', import.meta.url), { type: 'module' }); + worker.onmessage = ({ data }) => { + console.log('page got message:', data); + }; + worker.postMessage('hello'); + } + `, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/server/tests/options/external-dependencies_spec.ts b/packages/angular_devkit/build_angular/src/builders/server/tests/options/external-dependencies_spec.ts new file mode 100644 index 000000000000..5e04fd9e712b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/tests/options/external-dependencies_spec.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => { + describe('Option: "externalDependencies"', () => { + it(`should not bundle dependency when set "externalDependencies" is set.`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + externalDependencies: ['@angular/core'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness.expectFile('dist/main.js').content.toContain('require("@angular/core")'); + harness.expectFile('dist/main.js').content.not.toContain('require("@angular/common")'); + }); + + it(`should bundle all dependencies when "externalDependencies" is unset`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js').content.not.toContain('require("@angular/core")'); + harness.expectFile('dist/main.js').content.not.toContain('require("@angular/common")'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/server/tests/options/extract-licenses_spec.ts b/packages/angular_devkit/build_angular/src/builders/server/tests/options/extract-licenses_spec.ts new file mode 100644 index 000000000000..a6d9705f62c4 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/tests/options/extract-licenses_spec.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => { + describe('Option: "extractLicenses"', () => { + it(`should generate '3rdpartylicenses.txt' when 'extractLicenses' is true`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + extractLicenses: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/3rdpartylicenses.txt').content.toContain('MIT'); + }); + + it(`should not generate '3rdpartylicenses.txt' when 'extractLicenses' is false`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + extractLicenses: false, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/3rdpartylicenses.txt').toNotExist(); + }); + + it(`should generate '3rdpartylicenses.txt' when 'extractLicenses' is not set`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/3rdpartylicenses.txt').content.toContain('MIT'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/server/tests/options/resources-output-path_spec.ts b/packages/angular_devkit/build_angular/src/builders/server/tests/options/resources-output-path_spec.ts new file mode 100644 index 000000000000..581207ce4468 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/tests/options/resources-output-path_spec.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => { + describe('Option: "resourcesOutputPath"', () => { + beforeEach(async () => { + const img = await harness.readFile('src/spectrum.png'); + + await harness.writeFiles({ + 'src/assets/component-img-relative.png': img, + 'src/assets/component-img-absolute.png': img, + 'src/app/app.component.css': ` + h3 { background: url('https://github.com/assets/component-img-absolute.png'); } + h4 { background: url('https://github.com/assets/component-img-relative.png'); } + `, + }); + }); + + it(`should prepend value of "resourcesOutputPath" as part of the resource urls`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + resourcesOutputPath: 'out-assets', + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness + .expectFile('dist/main.js') + .content.toContain(`url(https://github.com/assets/component-img-absolute.png)`); + harness + .expectFile('dist/main.js') + .content.toContain(`url(https://github.com/out-assets/component-img-relative.png)`); + + // Assets are not emitted during a server builds. + harness.expectFile('dist/out-assets/component-img-relative.png').toNotExist(); + }); + + it(`should not prepend anything when value of "resourcesOutputPath" is unset.`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness + .expectFile('dist/main.js') + .content.toContain(`url(https://github.com/assets/component-img-absolute.png)`); + harness.expectFile('dist/main.js').content.toContain(`url(https://github.com/component-img-relative.png)`); + + // Assets are not emitted during a server builds. + harness.expectFile('dist/component-img-relative.png').toNotExist(); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/server/tests/options/source-map_spec.ts b/packages/angular_devkit/build_angular/src/builders/server/tests/options/source-map_spec.ts new file mode 100644 index 000000000000..9171304bfd5c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/tests/options/source-map_spec.ts @@ -0,0 +1,151 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, SERVER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(execute, SERVER_BUILDER_INFO, (harness) => { + describe('Option: "sourceMap"', () => { + const INLINE_SOURCEMAP_MARKER = + '/*# sourceMappingURL=data:application/json;charset=utf-8;base64,'; + + beforeEach(async () => { + await harness.writeFiles({ + 'src/app/app.component.css': `p { color: red; }`, + }); + }); + + it(`should not generate sourceMaps when "sourceMap" option is unset`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toNotExist(); + }); + + it(`should generate sourceMaps when "sourceMap" option is true`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + sourceMap: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toExist(); + }); + + it(`should not generate sourceMaps when "sourceMap" option is false`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + sourceMap: false, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toNotExist(); + }); + + it(`should not generate components sourceMaps when "styles" option is false`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + // Components sourcemaps are only present when optimization is false. + optimization: false, + sourceMap: { + styles: false, + scripts: true, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toExist(); + harness.expectFile('dist/main.js').content.toContain('sourceMappingURL=main.js.map'); + console.log(await harness.readFile('dist/main.js')); + harness.expectFile('dist/main.js').content.not.toContain(INLINE_SOURCEMAP_MARKER); + }); + + it(`should generate components sourceMaps when "styles" option is true`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + // Components sourcemaps are only present when optimization is false. + optimization: false, + sourceMap: { + styles: true, + scripts: true, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toExist(); + harness.expectFile('dist/main.js').content.toContain('sourceMappingURL=main.js.map'); + harness + .expectFile('dist/main.js') + .content.toContain('sourceMappingURL=data:application/json'); + }); + + it(`should generate components sourceMaps when "styles" option is unset`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + // Components sourcemaps are only present when optimization is false. + optimization: false, + sourceMap: { + scripts: true, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toExist(); + harness.expectFile('dist/main.js').content.toContain('sourceMappingURL=main.js.map'); + harness.expectFile('dist/main.js').content.toContain(INLINE_SOURCEMAP_MARKER); + }); + + it(`should generate scripts sourceMaps when "scripts" option is unset`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + sourceMap: {}, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toExist(); + harness.expectFile('dist/main.js').content.toContain('sourceMappingURL=main.js.map'); + }); + + it(`should not generate scripts sourceMaps when "scripts" option is false`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + sourceMap: { + scripts: false, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toNotExist(); + harness.expectFile('dist/main.js').content.not.toContain('sourceMappingURL=main.js.map'); + }); + + it(`should generate scripts sourceMaps when "scripts" option is true`, async () => { + harness.useTarget('server', { + ...BASE_OPTIONS, + sourceMap: { + scripts: true, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/main.js.map').toExist(); + harness.expectFile('dist/main.js').content.toContain('sourceMappingURL=main.js.map'); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/builders/server/tests/setup.ts b/packages/angular_devkit/build_angular/src/builders/server/tests/setup.ts new file mode 100644 index 000000000000..e2fa33445afc --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/server/tests/setup.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Schema } from '../schema'; + +export { describeBuilder } from '../../../testing'; + +export const SERVER_BUILDER_INFO = Object.freeze({ + name: '@angular-devkit/build-angular:server', + schemaPath: __dirname + '/../schema.json', +}); + +/** + * Contains all required Server builder fields. + * Also disables progress reporting to minimize logging output. + */ +export const BASE_OPTIONS = Object.freeze({ + main: 'src/main.server.ts', + tsConfig: 'src/tsconfig.server.json', + progress: false, + watch: false, + outputPath: 'dist', +}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/hmr_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/hmr_spec.ts deleted file mode 100644 index 56257ab2c0a7..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/hmr_spec.ts +++ /dev/null @@ -1,195 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect, BuilderRun } from '@angular-devkit/architect'; -// tslint:disable: no-implicit-dependencies -import { Browser } from 'puppeteer/lib/cjs/puppeteer/common/Browser'; -import { Page } from 'puppeteer/lib/cjs/puppeteer/common/Page'; -import puppeteer from 'puppeteer/lib/cjs/puppeteer/node'; -// tslint:enable: no-implicit-dependencies -import { debounceTime, switchMap, take } from 'rxjs/operators'; -import { createArchitect, host } from '../test-utils'; - -// tslint:disable: no-any -declare const document: any; -declare const getComputedStyle: any; -// tslint:enable: no-any - -describe('Dev Server Builder HMR', () => { - const target = { project: 'app', target: 'serve' }; - const overrides = { hmr: true, watch: true, port: 0 }; - let architect: Architect; - let browser: Browser; - let page: Page; - let logs: string[] = []; - let runs: BuilderRun[]; - - beforeAll(async () => { - browser = await puppeteer.launch({ - // MacOSX users need to set the local binary manually because Chrome has lib files with - // spaces in them which Bazel does not support in runfiles - // See: https://github.com/angular/angular-cli/pull/17624 - // tslint:disable-next-line: max-line-length - // executablePath: '/Users//git/angular-cli/node_modules/puppeteer/.local-chromium/mac-800071/chrome-mac/Chromium.app/Contents/MacOS/Chromium', - args: ['--no-sandbox', '--disable-gpu'], - }); - }); - - afterAll(async () => { - await browser.close(); - }); - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - - logs = []; - runs = []; - page = await browser.newPage(); - page.on('console', msg => logs.push(msg.text())); - - host.writeMultipleFiles({ - 'src/app/app.component.html': ` -

{{title}}

- - - - - `, - }); - }); - - afterEach(async () => { - await host.restore().toPromise(); - await page.close(); - await Promise.all(runs.map(r => r.stop())); - }); - - it('works for CSS changes', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let buildCount = 0; - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - await page.goto(url); - expect(logs).toContain('[HMR] Waiting for update signal from WDS...'); - host.writeMultipleFiles({ - 'src/styles.css': 'p { color: rgb(255, 255, 0) }', - }); - break; - case 1: - expect(logs).toContain('[HMR] Updated modules:'); - expect(logs).toContain(`[HMR] css reload %s ${url}styles.css`); - expect(logs).toContain('[HMR] App is up to date.'); - - const pTag = await page.evaluate(() => { - const el = document.querySelector('p'); - - return JSON.parse(JSON.stringify(getComputedStyle(el))); - }); - - expect(pTag.color).toBe('rgb(255, 255, 0)'); - break; - } - - logs = []; - buildCount++; - }), - take(2), - ) - .toPromise(); - }); - - it('works for TS changes', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let buildCount = 0; - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - - switch (buildCount) { - case 0: - await page.goto(url); - expect(logs).toContain('[HMR] Waiting for update signal from WDS...'); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-hmr'`); - break; - case 1: - expect(logs).toContain('[HMR] Updated modules:'); - expect(logs).toContain('[HMR] App is up to date.'); - - const innerText = await page.evaluate(() => document.querySelector('p').innerText); - expect(innerText).toBe('app-hmr'); - break; - } - - logs = []; - buildCount++; - }), - take(2), - ) - .toPromise(); - }); - - it('restores input and select values', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let buildCount = 0; - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - await page.goto(url); - expect(logs).toContain('[HMR] Waiting for update signal from WDS...'); - await page.evaluate(() => { - document.querySelector('input.visible').value = 'input value'; - document.querySelector('select').value = 'two'; - }); - - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-hmr'`); - break; - case 1: - expect(logs).toContain('[HMR] Updated modules:'); - expect(logs).toContain('[HMR] App is up to date.'); - expect(logs).toContain('[NG HMR] Restoring input/textarea values.'); - expect(logs).toContain('[NG HMR] Restoring selected options.'); - - const inputValue = await page.evaluate(() => document.querySelector('input.visible').value); - expect(inputValue).toBe('input value'); - - const selectValue = await page.evaluate(() => document.querySelector('select').value); - expect(selectValue).toBe('two'); - break; - } - - logs = []; - buildCount++; - }), - take(2), - ) - .toPromise(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/index.ts b/packages/angular_devkit/build_angular/src/dev-server/index.ts deleted file mode 100644 index 736fe3741eee..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/index.ts +++ /dev/null @@ -1,422 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { BuilderContext, createBuilder, targetFromTargetString } from '@angular-devkit/architect'; -import { - DevServerBuildOutput, - WebpackLoggingCallback, - runWebpackDevServer, -} from '@angular-devkit/build-webpack'; -import { json, tags } from '@angular-devkit/core'; -import * as path from 'path'; -import { Observable, from } from 'rxjs'; -import { concatMap, switchMap } from 'rxjs/operators'; -import * as ts from 'typescript'; -import * as url from 'url'; -import * as webpack from 'webpack'; -import * as webpackDevServer from 'webpack-dev-server'; -import { getAnalyticsConfig, getCompilerConfig } from '../browser'; -import { OutputHashing, Schema as BrowserBuilderSchema } from '../browser/schema'; -import { ExecutionTransformer } from '../transforms'; -import { BuildBrowserFeatures, normalizeOptimization } from '../utils'; -import { findCachePath } from '../utils/cache-path'; -import { checkPort } from '../utils/check-port'; -import { colors } from '../utils/color'; -import { I18nOptions } from '../utils/i18n-options'; -import { IndexHtmlTransform } from '../utils/index-file/index-html-generator'; -import { generateEntryPoints } from '../utils/package-chunk-sort'; -import { readTsconfig } from '../utils/read-tsconfig'; -import { assertCompatibleAngularVersion } from '../utils/version'; -import { generateI18nBrowserWebpackConfigFromContext, getIndexInputFile, getIndexOutputFile } from '../utils/webpack-browser-config'; -import { addError, addWarning } from '../utils/webpack-diagnostics'; -import { - getBrowserConfig, - getCommonConfig, - getDevServerConfig, - getStatsConfig, - getStylesConfig, - getWorkerConfig, -} from '../webpack/configs'; -import { IndexHtmlWebpackPlugin } from '../webpack/plugins/index-html-webpack-plugin'; -import { createWebpackLoggingCallback } from '../webpack/utils/stats'; -import { Schema } from './schema'; - -export type DevServerBuilderOptions = Schema & json.JsonObject; - -const devServerBuildOverriddenKeys: (keyof DevServerBuilderOptions)[] = [ - 'watch', - 'optimization', - 'aot', - 'sourceMap', - 'vendorChunk', - 'commonChunk', - 'baseHref', - 'progress', - 'poll', - 'verbose', - 'deployUrl', -]; - -/** - * @experimental Direct usage of this type is considered experimental. - */ -export type DevServerBuilderOutput = DevServerBuildOutput & { - baseUrl: string; -}; - -/** - * Reusable implementation of the Angular Webpack development server builder. - * @param options Dev Server options. - * @param context The build context. - * @param transforms A map of transforms that can be used to hook into some logic (such as - * transforming webpack configuration before passing it to webpack). - * - * @experimental Direct usage of this function is considered experimental. - */ -// tslint:disable-next-line: no-big-function -export function serveWebpackBrowser( - options: DevServerBuilderOptions, - context: BuilderContext, - transforms: { - webpackConfiguration?: ExecutionTransformer; - logging?: WebpackLoggingCallback; - indexHtml?: IndexHtmlTransform; - } = {}, -): Observable { - // Check Angular version. - const { logger, workspaceRoot } = context; - assertCompatibleAngularVersion(workspaceRoot, logger); - - const browserTarget = targetFromTargetString(options.browserTarget); - - async function setup(): Promise<{ - browserOptions: json.JsonObject & BrowserBuilderSchema; - webpackConfig: webpack.Configuration; - projectRoot: string; - locale: string | undefined; - }> { - // Get the browser configuration from the target name. - const rawBrowserOptions = (await context.getTargetOptions(browserTarget)) as json.JsonObject & BrowserBuilderSchema; - options.port = await checkPort(options.port ?? 4200, options.host || 'localhost'); - - // Override options we need to override, if defined. - const overrides = (Object.keys(options) as (keyof DevServerBuilderOptions)[]) - .filter(key => options[key] !== undefined && devServerBuildOverriddenKeys.includes(key)) - .reduce>( - (previous, key) => ({ - ...previous, - [key]: options[key], - }), - {}, - ); - - // Get dev-server only options. - type DevServerOptions = Partial>; - const devServerOptions: DevServerOptions = (Object.keys(options) as (keyof Schema)[]) - .filter(key => !devServerBuildOverriddenKeys.includes(key) && key !== 'browserTarget') - .reduce( - (previous, key) => ({ - ...previous, - [key]: options[key], - }), - {}, - ); - - // In dev server we should not have budgets because of extra libs such as socks-js - overrides.budgets = undefined; - - if (rawBrowserOptions.outputHashing && rawBrowserOptions.outputHashing !== OutputHashing.None) { - // Disable output hashing for dev build as this can cause memory leaks - // See: https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405 - overrides.outputHashing = OutputHashing.None; - logger.warn(`Warning: 'outputHashing' option is disabled when using the dev-server.`); - } - - // Webpack's live reload functionality adds the `strip-ansi` package which is commonJS - rawBrowserOptions.allowedCommonJsDependencies ??= []; - rawBrowserOptions.allowedCommonJsDependencies.push('strip-ansi'); - - const browserName = await context.getBuilderNameForTarget(browserTarget); - const browserOptions = await context.validateOptions( - { ...rawBrowserOptions, ...overrides }, - browserName, - ) as json.JsonObject & BrowserBuilderSchema; - - const { config, projectRoot, i18n } = await generateI18nBrowserWebpackConfigFromContext( - browserOptions, - context, - wco => [ - getDevServerConfig(wco), - getCommonConfig(wco), - getBrowserConfig(wco), - getStylesConfig(wco), - getStatsConfig(wco), - getAnalyticsConfig(wco, context), - getCompilerConfig(wco), - browserOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {}, - ], - devServerOptions, - ); - - if (!config.devServer) { - throw new Error( - 'Webpack Dev Server configuration was not set.', - ); - } - - if (options.liveReload && !options.hmr) { - // This is needed because we cannot use the inline option directly in the config - // because of the SuppressExtractedTextChunksWebpackPlugin - // Consider not using SuppressExtractedTextChunksWebpackPlugin when liveReload is enable. - // tslint:disable-next-line: no-any - webpackDevServer.addDevServerEntrypoints(config as any, { - ...config.devServer, - inline: true, - }); - - // Remove live-reload code from all entrypoints but not main. - // Otherwise this will break SuppressExtractedTextChunksWebpackPlugin because - // 'addDevServerEntrypoints' adds addional entry-points to all entries. - if (config.entry && typeof config.entry === 'object' && !Array.isArray(config.entry) && config.entry.main) { - for (const [key, value] of Object.entries(config.entry)) { - if (key === 'main' || !Array.isArray(value)) { - continue; - } - - const webpackClientScriptIndex = value.findIndex(x => x.includes('webpack-dev-server/client/index.js')); - if (webpackClientScriptIndex >= 0) { - // Remove the webpack-dev-server/client script from array. - value.splice(webpackClientScriptIndex, 1); - } - } - } - } - - if (options.hmr) { - logger.warn(tags.stripIndents`NOTICE: Hot Module Replacement (HMR) is enabled for the dev server. - See https://webpack.js.org/guides/hot-module-replacement for information on working with HMR for Webpack.`); - } - - if ( - options.host - && !/^127\.\d+\.\d+\.\d+/g.test(options.host) - && options.host !== 'localhost' - ) { - logger.warn(tags.stripIndent` - Warning: This is a simple server for use in testing or debugging Angular applications - locally. It hasn't been reviewed for security issues. - - Binding this server to an open connection can result in compromising your application or - computer. Using a different host than the one passed to the "--host" flag might result in - websocket connection issues. You might need to use "--disableHostCheck" if that's the - case. - `); - } - - if (options.disableHostCheck) { - logger.warn(tags.oneLine` - Warning: Running a server with --disable-host-check is a security risk. - See https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a - for more information. - `); - } - - let locale: string | undefined; - if (i18n.shouldInline) { - // Dev-server only supports one locale - locale = [...i18n.inlineLocales][0]; - } else if (i18n.hasDefinedSourceLocale) { - // use source locale if not localizing - locale = i18n.sourceLocale; - } - - let webpackConfig = config; - - // If a locale is defined, setup localization - if (locale) { - // Only supported with Ivy - const tsConfig = readTsconfig(browserOptions.tsConfig, workspaceRoot); - if (tsConfig.options.enableIvy !== false) { - if (i18n.inlineLocales.size > 1) { - throw new Error( - 'The development server only supports localizing a single locale per build.', - ); - } - - await setupLocalize(locale, i18n, browserOptions, webpackConfig); - } - } - - if (transforms.webpackConfiguration) { - webpackConfig = await transforms.webpackConfiguration(webpackConfig); - } - - return { - browserOptions, - webpackConfig, - projectRoot, - locale, - }; - } - - return from(setup()).pipe( - switchMap(({ browserOptions, webpackConfig, projectRoot, locale }) => { - const normalizedOptimization = normalizeOptimization(browserOptions.optimization); - - if (browserOptions.index) { - const { scripts = [], styles = [], baseHref, tsConfig } = browserOptions; - const { options: compilerOptions } = readTsconfig(tsConfig, workspaceRoot); - const target = compilerOptions.target || ts.ScriptTarget.ES5; - const buildBrowserFeatures = new BuildBrowserFeatures(projectRoot); - - const entrypoints = generateEntryPoints({ scripts, styles }); - const moduleEntrypoints = buildBrowserFeatures.isDifferentialLoadingNeeded(target) - ? generateEntryPoints({ scripts: [], styles }) - : []; - - webpackConfig.plugins = [...(webpackConfig.plugins || [])]; - webpackConfig.plugins.push( - new IndexHtmlWebpackPlugin({ - indexPath: path.resolve(workspaceRoot, getIndexInputFile(browserOptions.index)), - outputPath: getIndexOutputFile(browserOptions.index), - baseHref, - entrypoints, - moduleEntrypoints, - noModuleEntrypoints: ['polyfills-es5'], - deployUrl: browserOptions.deployUrl, - sri: browserOptions.subresourceIntegrity, - postTransform: transforms.indexHtml, - optimization: normalizedOptimization, - WOFFSupportNeeded: !buildBrowserFeatures.isFeatureSupported('woff2'), - crossOrigin: browserOptions.crossOrigin, - lang: locale, - }), - ); - } - - if (normalizedOptimization.scripts || normalizedOptimization.styles.minify) { - logger.error(tags.stripIndents` - **************************************************************************************** - This is a simple server for use in testing or debugging Angular applications locally. - It hasn't been reviewed for security issues. - - DON'T USE IT FOR PRODUCTION! - **************************************************************************************** - `); - } - - return runWebpackDevServer( - webpackConfig, - context, - { - logging: transforms.logging || createWebpackLoggingCallback(!!options.verbose, logger), - webpackFactory: require('webpack') as typeof webpack, - webpackDevServerFactory: require('webpack-dev-server') as typeof webpackDevServer, - }, - ).pipe( - concatMap(async (buildEvent, index) => { - // Resolve serve address. - const serverAddress = url.format({ - protocol: options.ssl ? 'https' : 'http', - hostname: options.host === '0.0.0.0' ? 'localhost' : options.host, - pathname: webpackConfig.devServer?.publicPath, - port: buildEvent.port, - }); - - if (index === 0) { - logger.info('\n' + tags.oneLine` - ** - Angular Live Development Server is listening on ${options.host}:${buildEvent.port}, - open your browser on ${serverAddress} - ** - ` + '\n'); - - if (options.open) { - const open = await import('open'); - await open(serverAddress); - } - } - - if (buildEvent.success) { - logger.info(`\n${colors.greenBright(colors.symbols.check)} Compiled successfully.`); - } - - return { ...buildEvent, baseUrl: serverAddress } as DevServerBuilderOutput; - }), - ); - }), - ); -} - -async function setupLocalize( - locale: string, - i18n: I18nOptions, - browserOptions: BrowserBuilderSchema, - webpackConfig: webpack.Configuration, -) { - const localeDescription = i18n.locales[locale]; - - // Modify main entrypoint to include locale data - if ( - localeDescription?.dataPath && - typeof webpackConfig.entry === 'object' && - !Array.isArray(webpackConfig.entry) && - webpackConfig.entry['main'] - ) { - if (Array.isArray(webpackConfig.entry['main'])) { - webpackConfig.entry['main'].unshift(localeDescription.dataPath); - } else { - webpackConfig.entry['main'] = [localeDescription.dataPath, webpackConfig.entry['main'] as string]; - } - } - - let missingTranslationBehavior = browserOptions.i18nMissingTranslation || 'ignore'; - let translation = localeDescription?.translation || {}; - - if (locale === i18n.sourceLocale) { - missingTranslationBehavior = 'ignore'; - translation = {}; - } - - const i18nLoaderOptions = { - locale, - missingTranslationBehavior, - translation: i18n.shouldInline ? translation : undefined, - }; - - const i18nRule: webpack.RuleSetRule = { - test: /\.(?:[cm]?js|ts)$/, - enforce: 'post', - use: [ - { - loader: require.resolve('../babel/webpack-loader'), - options: { - cacheDirectory: findCachePath('babel-dev-server-i18n'), - cacheIdentifier: JSON.stringify({ - locale, - translationIntegrity: localeDescription?.files.map((file) => file.integrity), - }), - i18n: i18nLoaderOptions, - }, - }, - ], - }; - - // Get the rules and ensure the Webpack configuration is setup properly - const rules = webpackConfig.module?.rules || []; - if (!webpackConfig.module) { - webpackConfig.module = { rules }; - } else if (!webpackConfig.module.rules) { - webpackConfig.module.rules = rules; - } - - rules.push(i18nRule); -} - -export default createBuilder(serveWebpackBrowser); diff --git a/packages/angular_devkit/build_angular/src/dev-server/index_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/index_spec.ts deleted file mode 100644 index 8e4f0e049dc6..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/index_spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { DevServerBuilderOutput } from '@angular-devkit/build-angular'; -import { workspaces } from '@angular-devkit/core'; -import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies -import { createArchitect, host } from '../test-utils'; - -describe('Dev Server Builder index', () => { - const targetSpec = { project: 'app', target: 'serve' }; - - beforeEach(async () => host.initialize().toPromise()); - afterEach(async () => host.restore().toPromise()); - - it(`adds 'type="module"' when differential loading is needed`, async () => { - host.writeMultipleFiles({ - '.browserslistrc': ` - last 1 chrome version - IE 10 - `, - }); - - const architect = (await createArchitect(host.root())).architect; - const run = await architect.scheduleTarget(targetSpec, { port: 0 }); - const output = (await run.result) as DevServerBuilderOutput; - expect(output.success).toBe(true); - const response = await fetch(output.baseUrl); - expect(await response.text()).toContain( - '' + - '' + - '' + - '', - ); - await run.stop(); - }); - - it(`does not add 'type="module"' to custom scripts when differential loading is needed`, async () => { - host.writeMultipleFiles({ - '.browserslistrc': ` - last 1 chrome version - IE 10 - `, - 'test.js': 'console.log("test");', - }); - - const { workspace } = await workspaces.readWorkspace(host.root(), workspaces.createWorkspaceHost(host)); - const app = workspace.projects.get('app'); - if (!app) { - fail('Test application "app" not found.'); - - return; - } - const target = app.targets.get('build'); - if (!target) { - fail('Test application "app" target "build" not found.'); - - return; - } - if (!target.options) { - target.options = {}; - } - target.options.scripts = ['test.js']; - await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); - - const architect = (await createArchitect(host.root())).architect; - const run = await architect.scheduleTarget(targetSpec, { port: 0 }); - const output = (await run.result) as DevServerBuilderOutput; - expect(output.success).toBe(true); - const response = await fetch(output.baseUrl); - expect(await response.text()).toContain( - '' + - '' + - '' + - '' + - '', - ); - await run.stop(); - }); - - it(`doesn't 'type="module"' when differential loading is not needed`, async () => { - host.writeMultipleFiles({ - '.browserslistrc': ` - last 1 chrome version - `, - }); - - const architect = (await createArchitect(host.root())).architect; - const run = await architect.scheduleTarget(targetSpec, { port: 0 }); - const output = (await run.result) as DevServerBuilderOutput; - expect(output.success).toBe(true); - const response = await fetch(output.baseUrl); - expect(await response.text()).toContain( - '' + - '' + - '' + - '', - ); - await run.stop(); - }); - - it('sets HTML lang attribute with the active locale', async () => { - const locale = 'fr'; - const { workspace } = await workspaces.readWorkspace(host.root(), workspaces.createWorkspaceHost(host)); - const app = workspace.projects.get('app'); - if (!app) { - fail('Test application "app" not found.'); - - return; - } - - app.extensions['i18n'] = { - locales: { - [locale]: [], - }, - }; - - const target = app.targets.get('build'); - if (!target) { - fail('Test application "app" target "build" not found.'); - - return; - } - if (!target.options) { - target.options = {}; - } - target.options.localize = [locale]; - - await workspaces.writeWorkspace(workspace, workspaces.createWorkspaceHost(host)); - - const architect = (await createArchitect(host.root())).architect; - const run = await architect.scheduleTarget(targetSpec, { port: 0 }); - const output = (await run.result) as DevServerBuilderOutput; - expect(output.success).toBe(true); - const response = await fetch(output.baseUrl); - expect(await response.text()).toContain(`lang="${locale}"`); - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/live-reload_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/live-reload_spec.ts deleted file mode 100644 index 04995ebf72d2..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/live-reload_spec.ts +++ /dev/null @@ -1,283 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable: no-implicit-dependencies -import { Architect, BuilderRun } from '@angular-devkit/architect'; -import { tags } from '@angular-devkit/core'; -import { createProxyServer } from 'http-proxy'; -import { HTTPResponse } from 'puppeteer/lib/cjs/puppeteer/api-docs-entry'; -import { Browser } from 'puppeteer/lib/cjs/puppeteer/common/Browser'; -import { Page } from 'puppeteer/lib/cjs/puppeteer/common/Page'; -import puppeteer from 'puppeteer/lib/cjs/puppeteer/node'; -import { debounceTime, switchMap, take } from 'rxjs/operators'; -import { createArchitect, host } from '../test-utils'; - -// tslint:disable-next-line: no-any -declare const document: any; - -interface ProxyInstance { - server: typeof createProxyServer extends () => infer R ? R : never; - url: string; -} - -let proxyPort = 9100; -function createProxy(target: string, secure: boolean, ws = true): ProxyInstance { - proxyPort++; - - const server = createProxyServer({ - ws, - target, - secure, - ssl: secure && { - key: tags.stripIndents` - -----BEGIN RSA PRIVATE KEY----- - MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEBRUsUz4rdcMt - CQGLvG3SzUinsmgdgOyTNQNA0eOMyRSrmS8L+F/kSLUnqqu4mzdeqDzo2Xj553jK - dRqMCRFGJuGnQ/VIbW2A+ywgrqILuDyF5i4PL1aQW4yJ7TnXfONKfpswQArlN6DF - gBYJtoJlf8XD1sOeJpsv/O46/ix/wngQ+GwQQ2cfqxQT0fE9SBCY23VNt3SPUJ3k - 9etJMvJ9U9GHSb1CFdNQe7Gyx7xdKf1TazB27ElNZEg2aF99if47uRskYjvvFivy - 7nxGx/ccIwjwNMpk29AsKG++0sn1yTK7tD5Px6aCSVK0BKbdXZS2euJor8hASGBJ - 3GpVGJvdAgMBAAECggEAapYo8TVCdPdP7ckb4hPP0/R0MVu9aW2VNmZ5ImH+zar5 - ZmWhQ20HF2bBupP/VB5yeTIaDLNUKO9Iqy4KBWNY1UCHKyC023FFPgFV+V98FctU - faqwGOmwtEZToRwxe48ZOISndhEc247oCPyg/x8SwIY9z0OUkwaDFBEAqWtUXxM3 - /SPpCT5ilLgxnRgVB8Fj5Z0q7ThnxNVOmVC1OSIakEj46PzmMXn1pCKLOCUmAAOQ - BnrOZuty2b8b2M/GHsktLZwojQQJmArnIBymTXQTVhaGgKSyOv1qvHLp9L1OJf0/ - Xm+/TqT6ztzhzlftcObdfQZZ5JuoEwlvyrsGFlA3MQKBgQDiQC3KYMG8ViJkWrv6 - XNAFEoAjVEKrtirGWJ66YfQ9KSJ7Zttrd1Y1V1OLtq3z4YMH39wdQ8rOD+yR8mWV - 6Tnsxma6yJXAH8uan8iVbxjIZKF1hnvNCxUoxYmWOmTLcEQMzmxvTzAiR+s6R6Uj - 9LgGqppt30nM4wnOhOJU6UxqbwKBgQDdy03KidbPZuycJSy1C9AIt0jlrxDsYm+U - fZrB6mHEZcgoZS5GbLKinQCdGcgERa05BXvJmNbfZtT5a37YEnbjsTImIhDiBP5P - nW36/9a3Vg1svd1KP2206/Bh3gfZbgTsQg4YogXgjf0Uzuvw18btgTtLVpVyeuqz - TU3eeF30cwKBgQCN6lvOmapsDEs+T3uhqx4AUH53qp63PmjOSUAnANJGmsq6ROZV - HmHAy6nn9Qpf85BRHCXhZWiMoIhvc3As/EINNtWxS6hC/q6jqp4SvcD50cVFBroY - /16iWGXZCX+37A+DSOfTWgSDPEFcKRx41UOpStHbITgVgEPieo/NWxlHmQKBgQDX - JOLs2RB6V0ilnpnjdPXzvncD9fHgmwvJap24BPeZX3HtXViqD76oZsu1mNCg9EW3 - zk3pnEyyoDlvSIreZerVq4kN3HWsCVP3Pqr0kz9g0CRtmy8RWr28hjHDfXD3xPUZ - iGnMEz7IOHOKv722/liFAprV1cNaLUmFbDNg3jmlaQKBgQDG5WwngPhOHmjTnSml - amfEz9a4yEhQqpqgVNW5wwoXOf6DbjL2m/maJh01giThj7inMcbpkZlIclxD0Eu6 - Lof+ctCeqSAJvaVPmd+nv8Yp26zsF1yM8ax9xXjrIvv9fSbycNveGTDCsNNTiYoW - QyvMqmN1kGy20SZbQDD/fLfqBQ== - -----END RSA PRIVATE KEY----- - `, - cert: tags.stripIndents` - -----BEGIN CERTIFICATE----- - MIIDXTCCAkWgAwIBAgIJALz8gD/gAt0OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV - BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX - aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDIzMTgyMTQ5WhcNMTkxMDIzMTgyMTQ5WjBF - MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 - ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB - CgKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kvC/hf5Ei1 - J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYuDy9WkFuM - ie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhsEENnH6sU - E9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2swduxJTWRI - NmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+T8emgklS - tASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABo1AwTjAdBgNVHQ4EFgQUDZBhVKdb - 3BRhLIhuuE522Vsul0IwHwYDVR0jBBgwFoAUDZBhVKdb3BRhLIhuuE522Vsul0Iw - DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABh9WWZwWLgb9/DcTxL72 - 6pI96t4jiF79Q+pPefkaIIi0mE6yodWrTAsBQu9I6bNRaEcCSoiXkP2bqskD/UGg - LwUFgSrDOAA3UjdHw3QU5g2NocduG7mcFwA40TB98sOsxsUyYlzSyWzoiQWwPYwb - hek1djuWkqPXsTjlj54PTPN/SjTFmo4p5Ip6nbRf2nOREl7v0rJpGbJvXiCMYyd+ - Zv+j4mRjCGo8ysMR2HjCUGkYReLAgKyyz3M7i8vevJhKslyOmy6Txn4F0nPVumaU - DDIy4xXPW1STWfsmSYJfYW3wa0wk+pJQ3j2cTzkPQQ8gwpvM3U9DJl43uwb37v6I - 7Q== - -----END CERTIFICATE----- - `, - }, - }) - .listen(proxyPort); - - return { - server, - url: `${secure ? 'https' : 'http'}://localhost:${proxyPort}`, - }; -} - -async function goToPageAndWaitForSockJs(page: Page, url: string): Promise { - const socksRequest = `${url.endsWith('/') ? url : url + '/'}sockjs-node/info?t=`; - - await Promise.all([ - page.waitForResponse((r: HTTPResponse) => r.url().startsWith(socksRequest) && r.status() === 200), - page.goto(url), - ]); -} - -describe('Dev Server Builder live-reload', () => { - const target = { project: 'app', target: 'serve' }; - // Avoid using port `0` as these tests will behave differrently and tests will pass when they shouldn't. - // Port 0 and host 0.0.0.0 have special meaning in dev-server. - const overrides = { hmr: false, watch: true, port: 4202, liveReload: true }; - let architect: Architect; - let browser: Browser; - let page: Page; - let runs: BuilderRun[]; - let proxy: ProxyInstance | undefined; - - beforeAll(async () => { - browser = await puppeteer.launch({ - // MacOSX users need to set the local binary manually because Chrome has lib files with - // spaces in them which Bazel does not support in runfiles - // See: https://github.com/angular/angular-cli/pull/17624 - // tslint:disable-next-line: max-line-length - // executablePath: '/Users//git/angular-cli/node_modules/puppeteer/.local-chromium/mac-818858/chrome-mac/Chromium.app/Contents/MacOS/Chromium', - ignoreHTTPSErrors: true, - args: [ - '--no-sandbox', - '--disable-gpu', - ], - }); - }); - - afterAll(async () => { - await browser.close(); - }); - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - - host.writeMultipleFiles({ - 'src/app/app.component.html': ` -

{{title}}

- `, - }); - - runs = []; - page = await browser.newPage(); - }); - - afterEach(async () => { - proxy?.server.close(); - proxy = undefined; - await host.restore().toPromise(); - await page.close(); - await Promise.all(runs.map(r => r.stop())); - }); - - it('works without proxy', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let buildCount = 0; - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - await goToPageAndWaitForSockJs(page, url); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); - break; - case 1: - const innerText = await page.evaluate(() => document.querySelector('p').innerText); - expect(innerText).toBe('app-live-reload'); - break; - } - - buildCount++; - }), - take(2), - ) - .toPromise(); - }); - - it('works without http -> http proxy', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let proxy: ProxyInstance | undefined; - let buildCount = 0; - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - proxy = createProxy(url, false); - await goToPageAndWaitForSockJs(page, proxy.url); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); - break; - case 1: - const innerText = await page.evaluate(() => document.querySelector('p').innerText); - expect(innerText).toBe('app-live-reload'); - break; - } - - buildCount++; - }), - take(2), - ) - .toPromise(); - }); - - it('works without https -> http proxy', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let proxy: ProxyInstance | undefined; - let buildCount = 0; - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - proxy = createProxy(url, true); - await goToPageAndWaitForSockJs(page, proxy.url); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); - break; - case 1: - const innerText = await page.evaluate(() => document.querySelector('p').innerText); - expect(innerText).toBe('app-live-reload'); - break; - } - - buildCount++; - }), - take(2), - ) - .toPromise(); - }); - - it('works without https -> http proxy without websockets (dotnet emulation)', async () => { - const run = await architect.scheduleTarget(target, overrides); - runs.push(run); - - let proxy: ProxyInstance | undefined; - let buildCount = 0; - - await run.output - .pipe( - debounceTime(1000), - switchMap(async buildEvent => { - expect(buildEvent.success).toBe(true); - const url = buildEvent.baseUrl as string; - switch (buildCount) { - case 0: - proxy = createProxy(url, true, false); - await goToPageAndWaitForSockJs(page, proxy.url); - await page.waitForResponse((response: HTTPResponse) => response.url().includes('xhr_streaming') && response.status() === 200); - host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`); - break; - case 1: - const innerText = await page.evaluate(() => document.querySelector('p').innerText); - expect(innerText).toBe('app-live-reload'); - break; - } - - buildCount++; - }), - take(2), - ) - .toPromise(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/schema.json b/packages/angular_devkit/build_angular/src/dev-server/schema.json deleted file mode 100644 index f65027d95197..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/schema.json +++ /dev/null @@ -1,208 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "title": "Dev Server Target", - "description": "Dev Server target options for Build Facade.", - "type": "object", - "properties": { - "browserTarget": { - "type": "string", - "description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", - "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" - }, - "port": { - "type": "number", - "description": "Port to listen on.", - "default": 4200 - }, - "host": { - "type": "string", - "description": "Host to listen on.", - "default": "localhost" - }, - "proxyConfig": { - "type": "string", - "description": "Proxy configuration file." - }, - "ssl": { - "type": "boolean", - "description": "Serve using HTTPS.", - "default": false - }, - "sslKey": { - "type": "string", - "description": "SSL key to use for serving HTTPS." - }, - "sslCert": { - "type": "string", - "description": "SSL certificate to use for serving HTTPS." - }, - "headers": { - "type": "object", - "description": "Custom HTTP headers to be added to all responses.", - "propertyNames": { - "pattern": "^[-_A-Za-z0-9]+$" - }, - "additionalProperties": { - "type": "string" - } - }, - "open": { - "type": "boolean", - "description": "Opens the url in default browser.", - "default": false, - "alias": "o" - }, - "verbose": { - "type": "boolean", - "description": "Adds more details to output logging." - }, - "liveReload": { - "type": "boolean", - "description": "Whether to reload the page on change, using live-reload.", - "default": true - }, - "publicHost": { - "type": "string", - "description": "The URL that the browser client (or live-reload client, if enabled) should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies." - }, - "allowedHosts": { - "type": "array", - "description": "List of hosts that are allowed to access the dev server.", - "default": [], - "items": { - "type": "string" - } - }, - "servePath": { - "type": "string", - "description": "The pathname where the app will be served." - }, - "disableHostCheck": { - "type": "boolean", - "description": "Don't verify connected clients are part of allowed hosts.", - "default": false - }, - "hmr": { - "type": "boolean", - "description": "Enable hot module replacement.", - "default": false - }, - "watch": { - "type": "boolean", - "description": "Rebuild on change.", - "default": true - }, - "hmrWarning": { - "type": "boolean", - "description": "Show a warning when the --hmr option is enabled.", - "default": true, - "x-deprecated": "No longer has an effect." - }, - "servePathDefaultWarning": { - "type": "boolean", - "description": "Show a warning when deploy-url/base-href use unsupported serve path values.", - "default": true, - "x-deprecated": "No longer has an effect." - }, - "optimization": { - "description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, tree-shaking and fonts inlining. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.", - "x-user-analytics": 16, - "oneOf": [ - { - "type": "object", - "properties": { - "scripts": { - "type": "boolean", - "description": "Enables optimization of the scripts output.", - "default": true - }, - "styles": { - "type": "boolean", - "description": "Enables optimization of the styles output.", - "default": true - } - }, - "additionalProperties": false - }, - { - "type": "boolean" - } - ], - "x-deprecated": "Use the \"optimization\" option in the browser builder instead." - }, - "aot": { - "type": "boolean", - "description": "Build using Ahead of Time compilation.", - "x-user-analytics": 13, - "x-deprecated": "Use the \"aot\" option in the browser builder instead." - }, - "sourceMap": { - "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", - "oneOf": [ - { - "type": "object", - "properties": { - "scripts": { - "type": "boolean", - "description": "Output source maps for all scripts.", - "default": true - }, - "styles": { - "type": "boolean", - "description": "Output source maps for all styles.", - "default": true - }, - "hidden": { - "type": "boolean", - "description": "Output source maps used for error reporting tools.", - "default": false - }, - "vendor": { - "type": "boolean", - "description": "Resolve vendor packages source maps.", - "default": false - } - }, - "additionalProperties": false - }, - { - "type": "boolean" - } - ], - "x-deprecated": "Use the \"sourceMap\" option in the browser builder instead." - }, - "vendorChunk": { - "type": "boolean", - "description": "Generate a seperate bundle containing only vendor libraries. This option should only used for development.", - "x-deprecated": "Use the \"vendorChunk\" option in the browser builder instead." - }, - "commonChunk": { - "type": "boolean", - "description": "Generate a seperate bundle containing code used across multiple bundles.", - "x-deprecated": "Use the \"commonChunk\" option in the browser builder instead." - }, - "baseHref": { - "type": "string", - "description": "Base url for the application being built.", - "x-deprecated": "Use the \"baseHref\" option in the browser builder instead." - }, - "deployUrl": { - "type": "string", - "description": "URL where files will be deployed.", - "x-deprecated": "Use the \"deployUrl\" option in the browser builder instead." - }, - "progress": { - "type": "boolean", - "description": "Log progress to the console while building.", - "x-deprecated": "Use the \"progress\" option in the browser builder instead." - }, - "poll": { - "type": "number", - "description": "Enable and define the file watching poll time period in milliseconds." - } - }, - "additionalProperties": false, - "required": [ - "browserTarget" - ] -} diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/execute-fetch.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/execute-fetch.ts deleted file mode 100644 index 4525fb558cec..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/execute-fetch.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import fetch, { RequestInit, Response } from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies -import { mergeMap, take, timeout } from 'rxjs/operators'; -import { URL } from 'url'; -import { - BuilderHarness, - BuilderHarnessExecutionOptions, - BuilderHarnessExecutionResult, -} from '../../testing/builder-harness'; - -export async function executeOnceAndFetch( - harness: BuilderHarness, - url: string, - options?: Partial & { request?: RequestInit }, -): Promise { - return harness - .execute() - .pipe( - timeout(30000), - mergeMap(async (executionResult) => { - let response = undefined; - if (executionResult.result?.success) { - const resolvedUrl = new URL(url, `${executionResult.result.baseUrl}/`); - response = await fetch(resolvedUrl, options?.request); - } - - return { ...executionResult, response }; - }), - take(1), - ) - .toPromise(); -} diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/hmr_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/options/hmr_spec.ts deleted file mode 100644 index d0f76f7f1ee5..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/hmr_spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { serveWebpackBrowser } from '../../index'; -import { - BASE_OPTIONS, - DEV_SERVER_BUILDER_INFO, - describeBuilder, - setupBrowserTarget, -} from '../setup'; - -describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { - describe('Option: "hmr"', () => { - beforeEach(() => { - setupBrowserTarget(harness); - }); - - it('should not show a CommonJS usage warning when enabled', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - hmr: true, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching('CommonJS or AMD dependencies'), - }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/live-reload_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/options/live-reload_spec.ts deleted file mode 100644 index 12ce756e1b3e..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/live-reload_spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { serveWebpackBrowser } from '../../index'; -import { - BASE_OPTIONS, - DEV_SERVER_BUILDER_INFO, - describeBuilder, - setupBrowserTarget, -} from '../setup'; - -describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { - describe('Option: "liveReload"', () => { - beforeEach(() => { - setupBrowserTarget(harness); - }); - - it('should not show a CommonJS usage warning when enabled', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - liveReload: true, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ - message: jasmine.stringMatching('CommonJS or AMD dependencies'), - }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/proxy-config_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/options/proxy-config_spec.ts deleted file mode 100644 index 2a703e1d7e2c..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/proxy-config_spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as http from 'http'; -import { serveWebpackBrowser } from '../../index'; -import { executeOnceAndFetch } from '../execute-fetch'; -import { - BASE_OPTIONS, - DEV_SERVER_BUILDER_INFO, - describeBuilder, - setupBrowserTarget, -} from '../setup'; - -describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { - describe('option: "proxyConfig"', () => { - beforeEach(async () => { - setupBrowserTarget(harness); - - // Application code is not needed for these tests - await harness.writeFile('src/main.ts', ''); - }); - - it('proxies requests based on the proxy configuration file provided in the option', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - proxyConfig: 'proxy.config.json', - }); - - const proxyServer = http.createServer((request, response) => { - if (request.url?.endsWith('/test')) { - response.writeHead(200); - response.end('TEST_API_RETURN'); - } else { - response.writeHead(404); - response.end(); - } - }); - - try { - await new Promise((resolve) => proxyServer.listen(0, '127.0.0.1', resolve)); - const proxyAddress = proxyServer.address() as import('net').AddressInfo; - - await harness.writeFiles({ - 'proxy.config.json': `{ "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`, - }); - - const { result, response } = await executeOnceAndFetch(harness, '/api/test'); - - expect(result?.success).toBeTrue(); - expect(await response?.text()).toContain('TEST_API_RETURN'); - } finally { - await new Promise((resolve) => proxyServer.close(() => resolve())); - } - }); - - it('throws an error when proxy configuration file cannot be found', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - proxyConfig: 'INVALID.json', - }); - - const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); - - expect(result).toBeUndefined(); - expect(error).toEqual( - jasmine.objectContaining({ - message: jasmine.stringMatching('INVALID\\.json does not exist'), - }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/verbose_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/options/verbose_spec.ts deleted file mode 100644 index 8d18b030eac8..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/verbose_spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { serveWebpackBrowser } from '../../index'; -import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO, describeBuilder, setupBrowserTarget } from '../setup'; - -const VERBOSE_LOG_TEXT = /\[emitted\] \(name: main\)/; - -describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { - describe('Option: "verbose"', () => { - beforeEach(() => { - setupBrowserTarget(harness); - }); - - it('shows verbose logs in console when true', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - verbose: true, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).toContain( - jasmine.objectContaining({ message: jasmine.stringMatching(VERBOSE_LOG_TEXT) }), - ); - }); - - it('does not show verbose logs in console when false', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - verbose: false, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ message: jasmine.stringMatching(VERBOSE_LOG_TEXT) }), - ); - }); - - it('does not show verbose logs in console when not present', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - }); - - const { result, logs } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - expect(logs).not.toContain( - jasmine.objectContaining({ message: jasmine.stringMatching(VERBOSE_LOG_TEXT) }), - ); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/options/watch_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/options/watch_spec.ts deleted file mode 100644 index 8d8d5399dd6b..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/options/watch_spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { TimeoutError } from 'rxjs'; -import { concatMap, count, take, timeout } from 'rxjs/operators'; -import { serveWebpackBrowser } from '../../index'; -import { - BASE_OPTIONS, - BUILD_TIMEOUT, - DEV_SERVER_BUILDER_INFO, - describeBuilder, - setupBrowserTarget, -} from '../setup'; - -describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { - describe('Option: "watch"', () => { - beforeEach(() => { - setupBrowserTarget(harness); - }); - - it('does not wait for file changes when false', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - watch: false, - }); - - await harness - .execute() - .pipe( - timeout(BUILD_TIMEOUT * 2), - concatMap(async ({ result }, index) => { - expect(result?.success).toBe(true); - - switch (index) { - case 0: - await harness.modifyFile( - 'src/main.ts', - (content) => content + 'console.log("abcd1234");', - ); - break; - case 1: - fail('Expected files to not be watched.'); - break; - } - }), - take(2), - ) - .toPromise() - .catch((error) => { - // Timeout is expected if watching is disabled - if (error instanceof TimeoutError) { - return; - } - throw error; - }); - }); - - it('watches for file changes when not present', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - }); - - const buildCount = await harness - .execute() - .pipe( - timeout(BUILD_TIMEOUT * 2), - concatMap(async ({ result }, index) => { - expect(result?.success).toBe(true); - - switch (index) { - case 0: - await harness.modifyFile( - 'src/main.ts', - (content) => content + 'console.log("abcd1234");', - ); - break; - case 1: - break; - } - }), - take(2), - count(), - ) - .toPromise(); - - expect(buildCount).toBe(2); - }); - - it('watches for file changes when true', async () => { - harness.useTarget('serve', { - ...BASE_OPTIONS, - watch: true, - }); - - const buildCount = await harness - .execute() - .pipe( - timeout(BUILD_TIMEOUT * 2), - concatMap(async ({ result }, index) => { - expect(result?.success).toBe(true); - - switch (index) { - case 0: - await harness.modifyFile( - 'src/main.ts', - (content) => content + 'console.log("abcd1234");', - ); - break; - case 1: - break; - } - }), - take(2), - count(), - ) - .toPromise(); - - expect(buildCount).toBe(2); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/dev-server/tests/setup.ts b/packages/angular_devkit/build_angular/src/dev-server/tests/setup.ts deleted file mode 100644 index cc864a9922a6..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/tests/setup.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { json } from '@angular-devkit/core'; -import { readFileSync } from 'fs'; -import { buildWebpackBrowser } from '../../browser'; -import { Schema as BrowserSchema } from '../../browser/schema'; -import { - BASE_OPTIONS as BROWSER_BASE_OPTIONS, - BROWSER_BUILDER_INFO, -} from '../../browser/tests/setup'; -import { BuilderHarness } from '../../testing/builder-harness'; -import { Schema } from '../schema'; - -export { describeBuilder } from '../../testing'; - -export const DEV_SERVER_BUILDER_INFO = Object.freeze({ - name: '@angular-devkit/build-angular:dev-server', - schemaPath: __dirname + '/../schema.json', -}); - -/** - * Contains all required extract-i18n builder fields. - * The port is also set to zero to ensure a free port is used for each test which - * supports parallel test execution. - */ -export const BASE_OPTIONS = Object.freeze({ - browserTarget: 'test:build', - port: 0, -}); - -/** - * Maximum time for single build/rebuild - * This accounts for CI variability. - */ -export const BUILD_TIMEOUT = 15000; - -/** - * Cached browser builder option schema - */ -let browserSchema: json.schema.JsonSchema | undefined = undefined; - -/** - * Adds a `build` target to a builder test harness for the browser builder with the base options - * used by the browser builder tests. - * - * @param harness The builder harness to use when setting up the browser builder target - * @param extraOptions The additional options that should be used when executing the target. - */ -export function setupBrowserTarget( - harness: BuilderHarness, - extraOptions?: Partial, -): void { - if (!browserSchema) { - browserSchema = JSON.parse( - readFileSync(BROWSER_BUILDER_INFO.schemaPath, 'utf8'), - ) as json.schema.JsonSchema; - } - - harness.withBuilderTarget( - 'build', - buildWebpackBrowser, - { - ...BROWSER_BASE_OPTIONS, - ...extraOptions, - }, - { - builderName: BROWSER_BUILDER_INFO.name, - optionSchema: browserSchema, - }, - ); -} diff --git a/packages/angular_devkit/build_angular/src/dev-server/works_spec.ts b/packages/angular_devkit/build_angular/src/dev-server/works_spec.ts deleted file mode 100644 index 493c2cbfe8f9..000000000000 --- a/packages/angular_devkit/build_angular/src/dev-server/works_spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect, BuilderRun } from '@angular-devkit/architect'; -import { DevServerBuilderOutput } from '@angular-devkit/build-angular'; -import { normalize, virtualFs } from '@angular-devkit/core'; -import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies -import { createArchitect, host } from '../test-utils'; - - -describe('Dev Server Builder', () => { - const target = { project: 'app', target: 'serve' }; - let architect: Architect; - let runs: BuilderRun[] = []; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - runs = []; - }); - afterEach(async () => { - await host.restore().toPromise(); - await Promise.all(runs.map(r => r.stop())); - }); - - it(`doesn't serve files on the cwd directly`, async () => { - const run = await architect.scheduleTarget(target); - runs.push(run); - const output = await run.result as DevServerBuilderOutput; - expect(output.success).toBe(true); - - // When webpack-dev-server doesn't have `contentBase: false`, this will serve the repo README. - const response = await fetch('https://github.com/service/http://localhost:4200/README.md', { - headers: { - 'Accept': 'text/html', - }, - }); - - const res = await response.text(); - expect(res).not.toContain('This file is automatically generated during release.'); - expect(res).toContain('HelloWorldApp'); - }); - - it('should not generate sourcemaps when running prod build', async () => { - // Production builds have sourcemaps turned off. - const run = await architect.scheduleTarget({ ...target, configuration: 'production' }, { port: 0 }); - runs.push(run); - const output = await run.result as DevServerBuilderOutput; - expect(output.success).toBe(true); - const hasSourceMaps = output.emittedFiles && output.emittedFiles.some(f => f.extension === '.map'); - expect(hasSourceMaps).toBe(false, `Expected emitted files not to contain '.map' files.`); - }); - - it('serves custom headers', async () => { - const run = await architect.scheduleTarget( - target, {headers: {'X-Header': 'Hello World'}, port: 0}); - runs.push(run); - const output = await run.result as DevServerBuilderOutput; - expect(output.success).toBe(true); - const response = await fetch(output.baseUrl); - expect(response.headers.get('X-Header')).toBe('Hello World'); - }); - - it('uses source locale when not localizing', async () => { - const config = host.scopedSync().read(normalize('angular.json')); - const jsonConfig = JSON.parse(virtualFs.fileBufferToString(config)); - const applicationProject = jsonConfig.projects.app; - - applicationProject.i18n = { sourceLocale: 'fr' }; - - host.writeMultipleFiles({ - 'angular.json': JSON.stringify(jsonConfig), - }); - - const architect = (await createArchitect(host.root())).architect; - const run = await architect.scheduleTarget(target, { port: 0 }); - const output = await run.result as DevServerBuilderOutput; - expect(output.success).toBe(true); - - const indexResponse = await fetch(output.baseUrl); - expect(await indexResponse.text()).toContain('lang="fr"'); - const vendorResponse = await fetch(output.baseUrl + 'vendor.js'); - const vendorText = await vendorResponse.text(); - expect(vendorText).toContain('fr'); - expect(vendorText).toContain('octobre'); - - await run.stop(); - }); - -}); diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/empty-export-default.js b/packages/angular_devkit/build_angular/src/extract-i18n/empty-export-default.js deleted file mode 100644 index 08d725cd4e46..000000000000 --- a/packages/angular_devkit/build_angular/src/extract-i18n/empty-export-default.js +++ /dev/null @@ -1 +0,0 @@ -export default ''; diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/index.ts b/packages/angular_devkit/build_angular/src/extract-i18n/index.ts deleted file mode 100644 index 6748db2a9e6d..000000000000 --- a/packages/angular_devkit/build_angular/src/extract-i18n/index.ts +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { - BuilderContext, - createBuilder, - targetFromTargetString, -} from '@angular-devkit/architect'; -import { BuildResult, runWebpack } from '@angular-devkit/build-webpack'; -import { JsonObject } from '@angular-devkit/core'; -import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize'; -import type { Diagnostics } from '@angular/localize/src/tools/src/diagnostics'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as webpack from 'webpack'; -import { Schema as BrowserBuilderOptions } from '../browser/schema'; -import { ExecutionTransformer } from '../transforms'; -import { createI18nOptions } from '../utils/i18n-options'; -import { assertCompatibleAngularVersion } from '../utils/version'; -import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config'; -import { getAotConfig, getBrowserConfig, getCommonConfig, getStatsConfig } from '../webpack/configs'; -import { createWebpackLoggingCallback } from '../webpack/utils/stats'; -import { Format, Schema } from './schema'; - -export type ExtractI18nBuilderOptions = Schema & JsonObject; - -function getI18nOutfile(format: string | undefined) { - switch (format) { - case 'xmb': - return 'messages.xmb'; - case 'xlf': - case 'xlif': - case 'xliff': - case 'xlf2': - case 'xliff2': - return 'messages.xlf'; - case 'json': - case 'legacy-migrate': - return 'messages.json'; - case 'arb': - return 'messages.arb'; - default: - throw new Error(`Unsupported format "${format}"`); - } -} - -async function getSerializer(format: Format, sourceLocale: string, basePath: string, useLegacyIds: boolean, diagnostics: Diagnostics) { - switch (format) { - case Format.Xmb: - const { XmbTranslationSerializer } = - await import('@angular/localize/src/tools/src/extract/translation_files/xmb_translation_serializer'); - - // tslint:disable-next-line: no-any - return new XmbTranslationSerializer(basePath as any, useLegacyIds); - case Format.Xlf: - case Format.Xlif: - case Format.Xliff: - const { Xliff1TranslationSerializer } = - await import('@angular/localize/src/tools/src/extract/translation_files/xliff1_translation_serializer'); - - // tslint:disable-next-line: no-any - return new Xliff1TranslationSerializer(sourceLocale, basePath as any, useLegacyIds, {}); - case Format.Xlf2: - case Format.Xliff2: - const { Xliff2TranslationSerializer } = - await import('@angular/localize/src/tools/src/extract/translation_files/xliff2_translation_serializer'); - - // tslint:disable-next-line: no-any - return new Xliff2TranslationSerializer(sourceLocale, basePath as any, useLegacyIds, {}); - case Format.Json: - const { SimpleJsonTranslationSerializer } = - await import('@angular/localize/src/tools/src/extract/translation_files/json_translation_serializer'); - - return new SimpleJsonTranslationSerializer(sourceLocale); - case Format.LegacyMigrate: - const { LegacyMessageIdMigrationSerializer } = - await import('@angular/localize/src/tools/src/extract/translation_files/legacy_message_id_migration_serializer'); - - return new LegacyMessageIdMigrationSerializer(diagnostics); - case Format.Arb: - const { ArbTranslationSerializer } = - await import('@angular/localize/src/tools/src/extract/translation_files/arb_translation_serializer'); - - const fileSystem = { - relative(from: string, to: string): string { - return path.relative(from, to); - }, - }; - - // tslint:disable-next-line: no-any - return new ArbTranslationSerializer(sourceLocale, basePath as any, fileSystem as any); - } -} - -function normalizeFormatOption(options: ExtractI18nBuilderOptions): Format { - let format = options.format; - - switch (format) { - case Format.Xlf: - case Format.Xlif: - case Format.Xliff: - format = Format.Xlf; - break; - case Format.Xlf2: - case Format.Xliff2: - format = Format.Xlf2; - break; - } - - // Default format is xliff1 - return format ?? Format.Xlf; -} - -class NoEmitPlugin { - apply(compiler: webpack.Compiler): void { - compiler.hooks.shouldEmit.tap('angular-no-emit', () => false); - } -} - -/** - * @experimental Direct usage of this function is considered experimental. - */ -export async function execute( - options: ExtractI18nBuilderOptions, - context: BuilderContext, - transforms?: { - webpackConfiguration?: ExecutionTransformer; - }, -): Promise { - // Check Angular version. - assertCompatibleAngularVersion(context.workspaceRoot, context.logger); - - const browserTarget = targetFromTargetString(options.browserTarget); - const browserOptions = await context.validateOptions( - await context.getTargetOptions(browserTarget), - await context.getBuilderNameForTarget(browserTarget), - ); - - const format = normalizeFormatOption(options); - - // We need to determine the outFile name so that AngularCompiler can retrieve it. - let outFile = options.outFile || getI18nOutfile(format); - if (options.outputPath) { - // AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead. - outFile = path.join(options.outputPath, outFile); - } - outFile = path.resolve(context.workspaceRoot, outFile); - - if (!context.target || !context.target.project) { - throw new Error('The builder requires a target.'); - } - - const metadata = await context.getProjectMetadata(context.target); - const i18n = createI18nOptions(metadata); - - let useLegacyIds = true; - - const ivyMessages: LocalizeMessage[] = []; - const { config, projectRoot } = await generateBrowserWebpackConfigFromContext( - { - ...browserOptions, - optimization: false, - sourceMap: { - scripts: true, - styles: false, - vendor: true, - }, - buildOptimizer: false, - aot: true, - progress: options.progress, - assets: [], - scripts: [], - styles: [], - deleteOutputPath: false, - extractLicenses: false, - subresourceIntegrity: false, - }, - context, - (wco) => { - if (wco.tsConfig.options.enableIvy === false) { - context.logger.warn( - 'Ivy extraction enabled but application is not Ivy enabled. Extraction may fail.', - ); - } - - // Default value for legacy message ids is currently true - useLegacyIds = wco.tsConfig.options.enableI18nLegacyMessageIdFormat ?? true; - - const partials = [ - { plugins: [new NoEmitPlugin()] }, - getCommonConfig(wco), - getBrowserConfig(wco), - getAotConfig(wco), - getStatsConfig(wco), - ]; - - // Add Ivy application file extractor support - partials.unshift({ - module: { - rules: [ - { - test: /\.[t|j]s$/, - loader: require.resolve('./ivy-extract-loader'), - options: { - messageHandler: (messages: LocalizeMessage[]) => ivyMessages.push(...messages), - }, - }, - ], - }, - }); - - // Replace all stylesheets with an empty default export - partials.push({ - plugins: [ - new webpack.NormalModuleReplacementPlugin( - /\.(css|scss|sass|styl|less)$/, - path.join(__dirname, 'empty-export-default.js'), - ), - ], - }); - - return partials; - }, - ); - - try { - require.resolve('@angular/localize'); - } catch { - return { - success: false, - error: `Ivy extraction requires the '@angular/localize' package.`, - outputPath: outFile, - }; - } - - const webpackResult = await runWebpack( - (await transforms?.webpackConfiguration?.(config)) || config, - context, - { - logging: createWebpackLoggingCallback(false, context.logger), - webpackFactory: webpack, - }, - ).toPromise(); - - // Set the outputPath to the extraction output location for downstream consumers - webpackResult.outputPath = outFile; - - // Complete if Webpack build failed - if (!webpackResult.success) { - return webpackResult; - } - - const basePath = config.context || projectRoot; - - const { checkDuplicateMessages } = await import( - // tslint:disable-next-line: trailing-comma - '@angular/localize/src/tools/src/extract/duplicates' - ); - - // The filesystem is used to create a relative path for each file - // from the basePath. This relative path is then used in the error message. - const checkFileSystem = { - relative(from: string, to: string): string { - return path.relative(from, to); - }, - }; - const diagnostics = checkDuplicateMessages( - // tslint:disable-next-line: no-any - checkFileSystem as any, - ivyMessages, - 'warning', - // tslint:disable-next-line: no-any - basePath as any, - ); - if (diagnostics.messages.length > 0) { - context.logger.warn(diagnostics.formatDiagnostics('')); - } - - // Serialize all extracted messages - const serializer = await getSerializer( - format, - i18n.sourceLocale, - basePath, - useLegacyIds, - diagnostics, - ); - const content = serializer.serialize(ivyMessages); - - // Ensure directory exists - const outputPath = path.dirname(outFile); - if (!fs.existsSync(outputPath)) { - fs.mkdirSync(outputPath, { recursive: true }); - } - - // Write translation file - fs.writeFileSync(outFile, content); - - return webpackResult; -} - -export default createBuilder(execute); diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/ivy-extract-loader.ts b/packages/angular_devkit/build_angular/src/extract-i18n/ivy-extract-loader.ts deleted file mode 100644 index af1bdddf4596..000000000000 --- a/packages/angular_devkit/build_angular/src/extract-i18n/ivy-extract-loader.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { MessageExtractor } from '@angular/localize/src/tools/src/extract/extraction'; -import { getOptions } from 'loader-utils'; -import * as nodePath from 'path'; - -interface LocalizeExtractLoaderOptions { - messageHandler: (messages: import('@angular/localize').ɵParsedMessage[]) => void; -} - -export default function localizeExtractLoader( - // tslint:disable-next-line: no-any - this: any, - content: string, - // Source map types are broken in the webpack type definitions - // tslint:disable-next-line: no-any - map: any, -) { - const loaderContext = this; - - // Casts are needed to workaround the loader-utils typings limited support for option values - const options = (getOptions(this) as unknown) as LocalizeExtractLoaderOptions | undefined; - - // Setup a Webpack-based logger instance - const logger = { - // level 2 is warnings - level: 2, - debug(...args: string[]): void { - // tslint:disable-next-line: no-console - console.debug(...args); - }, - info(...args: string[]): void { - loaderContext.emitWarning(args.join('')); - }, - warn(...args: string[]): void { - loaderContext.emitWarning(args.join('')); - }, - error(...args: string[]): void { - loaderContext.emitError(args.join('')); - }, - }; - - let filename = loaderContext.resourcePath; - if (map?.file) { - // The extractor's internal sourcemap handling expects the filenames to match - filename = nodePath.join(loaderContext.context, map.file); - } - - // Setup a virtual file system instance for the extractor - // * MessageExtractor itself uses readFile, relative and resolve - // * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve - const filesystem = { - readFile(path: string): string { - if (path === filename) { - return content; - } else if (path === filename + '.map') { - return typeof map === 'string' ? map : JSON.stringify(map); - } else { - throw new Error('Unknown file requested: ' + path); - } - }, - relative(from: string, to: string): string { - return nodePath.relative(from, to); - }, - resolve(...paths: string[]): string { - return nodePath.resolve(...paths); - }, - exists(path: string): boolean { - return path === filename || path === filename + '.map'; - }, - dirname(path: string): string { - return nodePath.dirname(path); - }, - }; - - // tslint:disable-next-line: no-any - const extractor = new MessageExtractor(filesystem as any, logger, { - // tslint:disable-next-line: no-any - basePath: this.rootContext as any, - useSourceMaps: !!map, - }); - - const messages = extractor.extractMessages(filename); - if (messages.length > 0) { - options?.messageHandler(messages); - } - - // Pass through the original content now that messages have been extracted - this.callback(undefined, content, map); -} diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/schema.json b/packages/angular_devkit/build_angular/src/extract-i18n/schema.json deleted file mode 100644 index 9f70bc8dfaa3..000000000000 --- a/packages/angular_devkit/build_angular/src/extract-i18n/schema.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "title": "Extract i18n Target", - "description": "Extract i18n target options for Build Facade.", - "type": "object", - "properties": { - "browserTarget": { - "type": "string", - "description": "A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", - "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$" - }, - "format": { - "type": "string", - "description": "Output format for the generated file.", - "default": "xlf", - "enum": [ - "xmb", - "xlf", - "xlif", - "xliff", - "xlf2", - "xliff2", - "json", - "arb", - "legacy-migrate" - ] - }, - "progress": { - "type": "boolean", - "description": "Log progress to the console.", - "default": true - }, - "outputPath": { - "type": "string", - "description": "Path where output will be placed." - }, - "outFile": { - "type": "string", - "description": "Name of the file to output." - } - }, - "additionalProperties": false, - "required": [ - "browserTarget" - ] -} diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/works_spec.ts b/packages/angular_devkit/build_angular/src/extract-i18n/works_spec.ts deleted file mode 100644 index cbc951852e96..000000000000 --- a/packages/angular_devkit/build_angular/src/extract-i18n/works_spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { join, logging, normalize, virtualFs } from '@angular-devkit/core'; -import { createArchitect, extractI18nTargetSpec, host } from '../test-utils'; - -describe('Extract i18n Target', () => { - const extractionFile = join(normalize('src'), 'messages.xlf'); - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(() => host.restore().toPromise()); - - it('generates an extraction file', async () => { - host.appendToFile('src/app/app.component.html', '

i18n test

'); - - const run = await architect.scheduleTarget(extractI18nTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - const exists = host.scopedSync().exists(extractionFile); - expect(exists).toBe(true); - - if (exists) { - const content = virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)); - expect(content).toContain('i18n test'); - } - }); - - it('does not emit the application files', async () => { - host.appendToFile('src/app/app.component.html', '

i18n test

'); - - const run = await architect.scheduleTarget(extractI18nTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - expect(host.scopedSync().exists(normalize('dist/app/main.js'))).toBeFalse(); - }); - - it('shows errors', async () => { - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe(e => logs.push(e.message)); - - host.appendToFile('src/app/app.component.html', - '

Hello world inner

'); - - const run = await architect.scheduleTarget(extractI18nTargetSpec, undefined, { logger }); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); - - await run.stop(); - - expect(logs.join()).toMatch('Cannot mark an element as translatable inside of a translatable section'); - }); - - it('supports out file', async () => { - host.appendToFile('src/app/app.component.html', '

i18n test

'); - const outFile = 'messages.fr.xlf'; - const extractionFile = join(normalize('src'), outFile); - const overrides = { outFile }; - - const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - expect(host.scopedSync().exists(extractionFile)).toBe(true); - expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile))) - .toMatch(/i18n test/); - }); - - it('supports output path', async () => { - host.appendToFile('src/app/app.component.html', '

i18n test

'); - // Note: this folder will not be created automatically. It must exist beforehand. - const outputPath = 'src/i18n'; - const extractionFile = join(normalize('src'), 'i18n', 'messages.xlf'); - const overrides = { outputPath }; - - const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - expect(host.scopedSync().exists(extractionFile)).toBe(true); - expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile))) - .toMatch(/i18n test/); - }); - - it('supports i18n format', async () => { - host.appendToFile('src/app/app.component.html', '

i18n test

'); - const extractionFile = join(normalize('src'), 'messages.xmb'); - const overrides = { format: 'xmb' }; - - const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - expect(host.scopedSync().exists(extractionFile)).toBe(true); - expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile))) - .toMatch(/i18n test/); - }); - - it('issues warnings for duplicate message identifiers', async () => { - host.appendToFile( - 'src/app/app.component.ts', - 'const c = $localize`:@@message-2:message contents`; const d = $localize`:@@message-2:different message contents`;', - ); - - const logger = new logging.Logger(''); - const logs: string[] = []; - logger.subscribe((e) => logs.push(e.message)); - - const run = await architect.scheduleTarget(extractI18nTargetSpec, undefined, { logger }); - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - expect(host.scopedSync().exists(extractionFile)).toBe(true); - - const fullLog = logs.join(); - expect(fullLog).toContain( - 'Duplicate messages with id', - ); - - }); -}); diff --git a/packages/angular_devkit/build_angular/src/index.ts b/packages/angular_devkit/build_angular/src/index.ts index 83613150fc33..5e9829236c5f 100644 --- a/packages/angular_devkit/build_angular/src/index.ts +++ b/packages/angular_devkit/build_angular/src/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -13,8 +13,6 @@ export { AssetPatternClass as AssetPatternObject, Budget, CrossOrigin, - ExtraEntryPoint, - ExtraEntryPointClass as ExtraEntryPointObject, FileReplacement, OptimizationClass as OptimizationObject, OptimizationUnion, @@ -24,42 +22,39 @@ export { SourceMapUnion, StylePreprocessorOptions, Type, -} from './browser/schema'; +} from './builders/browser/schema'; export { buildWebpackBrowser as executeBrowserBuilder, BrowserBuilderOutput, -} from './browser'; +} from './builders/browser'; export { serveWebpackBrowser as executeDevServerBuilder, DevServerBuilderOptions, DevServerBuilderOutput, -} from './dev-server'; +} from './builders/dev-server'; export { execute as executeExtractI18nBuilder, ExtractI18nBuilderOptions, -} from './extract-i18n'; +} from './builders/extract-i18n'; export { execute as executeKarmaBuilder, KarmaBuilderOptions, KarmaConfigOptions, -} from './karma'; +} from './builders/karma'; export { execute as executeProtractorBuilder, ProtractorBuilderOptions, -} from './protractor'; +} from './builders/protractor'; export { execute as executeServerBuilder, ServerBuilderOptions, ServerBuilderOutput, -} from './server'; +} from './builders/server'; -export { - execute as executeNgPackagrBuilder, - NgPackagrBuilderOptions, -} from './ng-packagr'; +export { execute as executeNgPackagrBuilder, NgPackagrBuilderOptions } from './builders/ng-packagr'; diff --git a/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts b/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts deleted file mode 100644 index 52d03f7848bb..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/code-coverage_spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -// tslint:disable:no-implicit-dependencies - -import { Architect } from '@angular-devkit/architect'; -import { normalize, virtualFs } from '@angular-devkit/core'; -import { last, tap } from 'rxjs/operators'; -import { promisify } from 'util'; -import { createArchitect, host, karmaTargetSpec } from '../test-utils'; - -// In each of the test below we'll have to call setTimeout to wait for the coverage -// analysis to be done. This is because karma-coverage performs the analysis -// asynchronously but the promise that it returns is not awaited by Karma. -// Coverage analysis begins when onRunComplete() is invoked, and output files -// are subsequently written to disk. For more information, see -// https://github.com/karma-runner/karma-coverage/blob/32acafa90ed621abd1df730edb44ae55a4009c2c/lib/reporter.js#L221 - -const setTimeoutPromise = promisify(setTimeout); - -describe('Karma Builder code coverage', () => { - const coverageFilePath = normalize('coverage/lcov.info'); - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(() => host.restore().toPromise()); - - it('supports code coverage option', async () => { - const run = await architect.scheduleTarget(karmaTargetSpec, { codeCoverage: true }); - - const {success} = await run.result; - expect(success).toBe(true); - - await run.stop(); - - await setTimeoutPromise(1000); - - const exists = host.scopedSync().exists(coverageFilePath); - expect(exists).toBe(true, `${coverageFilePath} does not exist`); - - if (exists) { - const content = virtualFs.fileBufferToString(host.scopedSync().read(coverageFilePath)); - expect(content).toContain('app.component.ts'); - expect(content).toContain('test.ts'); - } - }); - - it('supports code coverage exclude option', async () => { - const overrides = { - codeCoverage: true, - codeCoverageExclude: [ - '**/test.ts', - ], - }; - - const run = await architect.scheduleTarget(karmaTargetSpec, overrides); - - const {success} = await run.result; - expect(success).toBe(true); - - await run.stop(); - - await setTimeoutPromise(1000); - - const exists = host.scopedSync().exists(coverageFilePath); - expect(exists).toBe(true); - - if (exists) { - const content = virtualFs.fileBufferToString(host.scopedSync().read(coverageFilePath)); - expect(content).not.toContain('test.ts'); - } - }); - - it(`should collect coverage from paths in 'sourceRoot'`, async () => { - const files: { [path: string]: string } = { - './dist/my-lib/index.d.ts': ` - export declare const title = 'app'; - `, - './dist/my-lib/index.js': ` - export const title = 'app'; - `, - './src/app/app.component.ts': ` - import { Component } from '@angular/core'; - import { title } from 'my-lib'; - - @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - export class AppComponent { - title = title; - } - `, - }; - - host.writeMultipleFiles(files); - - host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, ` - "baseUrl": "./", - "paths": { - "my-lib": [ - "./dist/my-lib" - ] - }, - `); - - const run = await architect.scheduleTarget(karmaTargetSpec, { codeCoverage: true }); - - const {success} = await run.result; - expect(success).toBe(true); - - await run.stop(); - - await setTimeoutPromise(1000); - - const exists = host.scopedSync().exists(coverageFilePath); - expect(exists).toBe(true); - - if (exists) { - const content = virtualFs.fileBufferToString(host.scopedSync().read(coverageFilePath)); - expect(content).not.toContain('my-lib'); - } - }); - - it('should exit with non-zero code when coverage is below threshold', async () => { - host.replaceInFile('karma.conf.js', 'coverageReporter: {', ` - coverageReporter: { - check: { - global: { - statements: 100, - lines: 100, - branches: 100, - functions: 100 - } - }, - `); - - host.appendToFile('src/app/app.component.ts', ` - export function nonCovered(): boolean { - return true; - } - `); - - const run = await architect.scheduleTarget(karmaTargetSpec, { codeCoverage: true }); - - // In incremental mode, karma-coverage does not have the ability to mark a - // run as failed if code coverage does not pass. This is because it does - // the coverage asynchoronously and Karma does not await the promise - // returned by the plugin. - expect((await run.result).success).toBeTrue(); - - // However the program must exit with non-zero exit code. - // This is a more common use case of coverage testing and must be supported. - await run.output.pipe( - last(), - tap(buildEvent => expect(buildEvent.success).toBeFalse()), - ).toPromise(); - - await run.stop(); - - }); - - it('is able to process coverage plugin provided as string', async () => { - host.replaceInFile('karma.conf.js', /plugins: \[.+?\]/s, `plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('@angular-devkit/build-angular/plugins/karma'), - 'karma-coverage', // instead of require('karma-coverage') - ]`); - const run = await architect.scheduleTarget(karmaTargetSpec, { codeCoverage: true }); - - const {success} = await run.result; - expect(success).toBe(true); - await run.stop(); - }); - - it('is able to process coverage plugins provided as string karma-*', async () => { - host.replaceInFile('karma.conf.js', /plugins: \[.+?\]/s, `plugins: [ - 'karma-*', - require('@angular-devkit/build-angular/plugins/karma'), - ]`); - const run = await architect.scheduleTarget(karmaTargetSpec, { codeCoverage: true }); - - const {success} = await run.result; - expect(success).toBe(true); - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/karma/find-tests.ts b/packages/angular_devkit/build_angular/src/karma/find-tests.ts deleted file mode 100644 index 17f26197b9da..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/find-tests.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { existsSync } from 'fs'; -import * as glob from 'glob'; -import { basename, dirname, extname, join } from 'path'; -import { isDirectory } from '../utils/is-directory'; - -// go through all patterns and find unique list of files -export function findTests(patterns: string[], cwd: string, workspaceRoot: string): string[] { - return patterns.reduce( - (files, pattern) => { - const relativePathToMain = cwd.replace(workspaceRoot, '').substr(1); // remove leading slash - const tests = findMatchingTests(pattern, cwd, relativePathToMain); - tests.forEach(file => { - if (!files.includes(file)) { - files.push(file); - } - }); - - return files; - }, - [] as string[], - ); -} - -function findMatchingTests(pattern: string, cwd: string, relativePathToMain: string): string[] { - // normalize pattern, glob lib only accepts forward slashes - pattern = pattern.replace(/\\/g, '/'); - relativePathToMain = relativePathToMain.replace(/\\/g, '/'); - - // remove relativePathToMain to support relative paths from root - // such paths are easy to get when running scripts via IDEs - if (pattern.startsWith(relativePathToMain + '/')) { - pattern = pattern.substr(relativePathToMain.length + 1); // +1 to include slash - } - - // special logic when pattern does not look like a glob - if (!glob.hasMagic(pattern)) { - if (isDirectory(join(cwd, pattern))) { - pattern = `${pattern}/**/*.spec.@(ts|tsx)`; - } else { - // see if matching spec file exists - const extension = extname(pattern); - const matchingSpec = `${basename(pattern, extension)}.spec${extension}`; - - if (existsSync(join(cwd, dirname(pattern), matchingSpec))) { - pattern = join(dirname(pattern), matchingSpec).replace(/\\/g, '/'); - } - } - } - - const files = glob.sync(pattern, { - cwd, - }); - - return files; -} diff --git a/packages/angular_devkit/build_angular/src/karma/index.ts b/packages/angular_devkit/build_angular/src/karma/index.ts deleted file mode 100644 index 76678ec8c1d5..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/index.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; -import { getSystemPath, join, normalize } from '@angular-devkit/core'; -import { Config, ConfigOptions } from 'karma'; -import { dirname, resolve } from 'path'; -import { Observable, from } from 'rxjs'; -import { defaultIfEmpty, switchMap } from 'rxjs/operators'; -import * as webpack from 'webpack'; -import { Schema as BrowserBuilderOptions } from '../browser/schema'; -import { ExecutionTransformer } from '../transforms'; -import { assertCompatibleAngularVersion } from '../utils/version'; -import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config'; -import { - getCommonConfig, - getNonAotConfig, - getStylesConfig, - getTestConfig, - getWorkerConfig, -} from '../webpack/configs'; -import { SingleTestTransformLoader } from '../webpack/plugins/single-test-transform'; -import { findTests } from './find-tests'; -import { Schema as KarmaBuilderOptions } from './schema'; - -export type KarmaConfigOptions = ConfigOptions & { - buildWebpack?: unknown; - configFile?: string; -}; - -async function initialize( - options: KarmaBuilderOptions, - context: BuilderContext, - webpackConfigurationTransformer?: ExecutionTransformer, -): Promise<[typeof import('karma'), webpack.Configuration]> { - const { config } = await generateBrowserWebpackConfigFromContext( - // only two properties are missing: - // * `outputPath` which is fixed for tests - // * `budgets` which might be incorrect due to extra dev libs - { ...((options as unknown) as BrowserBuilderOptions), outputPath: '', budgets: undefined }, - context, - wco => [ - getCommonConfig(wco), - getStylesConfig(wco), - getNonAotConfig(wco), - getTestConfig(wco), - getWorkerConfig(wco), - ], - ); - - const karma = await import('karma'); - - return [ - karma, - webpackConfigurationTransformer ? await webpackConfigurationTransformer(config) : config, - ]; -} - -/** - * @experimental Direct usage of this function is considered experimental. - */ -export function execute( - options: KarmaBuilderOptions, - context: BuilderContext, - transforms: { - webpackConfiguration?: ExecutionTransformer; - // The karma options transform cannot be async without a refactor of the builder implementation - karmaOptions?: (options: KarmaConfigOptions) => KarmaConfigOptions; - } = {}, -): Observable { - // Check Angular version. - assertCompatibleAngularVersion(context.workspaceRoot, context.logger); - - return from(initialize(options, context, transforms.webpackConfiguration)).pipe( - switchMap(async ([karma, webpackConfig]) => { - const karmaOptions: KarmaConfigOptions = {}; - - if (options.watch !== undefined) { - karmaOptions.singleRun = !options.watch; - } - - // Convert browsers from a string to an array - if (options.browsers) { - karmaOptions.browsers = options.browsers.split(','); - } - - if (options.reporters) { - // Split along commas to make it more natural, and remove empty strings. - const reporters = options.reporters - .reduce((acc, curr) => acc.concat(curr.split(',')), []) - .filter(x => !!x); - - if (reporters.length > 0) { - karmaOptions.reporters = reporters; - } - } - - // prepend special webpack loader that will transform test.ts - if (options.include && options.include.length > 0) { - const mainFilePath = getSystemPath( - join(normalize(context.workspaceRoot), options.main), - ); - const files = findTests(options.include, dirname(mainFilePath), context.workspaceRoot); - // early exit, no reason to start karma - if (!files.length) { - throw new Error( - `Specified patterns: "${options.include.join(', ')}" did not match any spec files.`, - ); - } - - // Get the rules and ensure the Webpack configuration is setup properly - const rules = webpackConfig.module?.rules || []; - if (!webpackConfig.module) { - webpackConfig.module = { rules }; - } else if (!webpackConfig.module.rules) { - webpackConfig.module.rules = rules; - } - - rules.unshift({ - test: mainFilePath, - use: { - // cannot be a simple path as it differs between environments - loader: SingleTestTransformLoader, - options: { - files, - logger: context.logger, - }, - }, - }); - } - - karmaOptions.buildWebpack = { - options, - webpackConfig, - logger: context.logger, - }; - - const config = await karma.config.parseConfig( - resolve(context.workspaceRoot, options.karmaConfig), - transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, - { promiseConfig: true, throwErrors: true }, - ); - - return [karma, config] as [typeof karma, KarmaConfigOptions]; - }), - switchMap(([karma, karmaConfig]) => new Observable(subscriber => { - // Pass onto Karma to emit BuildEvents. - karmaConfig.buildWebpack ??= {}; - if (typeof karmaConfig.buildWebpack === 'object') { - // tslint:disable-next-line: no-any - (karmaConfig.buildWebpack as any).failureCb ??= () => subscriber.next({ success: false }); - // tslint:disable-next-line: no-any - (karmaConfig.buildWebpack as any).successCb ??= () => subscriber.next({ success: true }); - } - - // Complete the observable once the Karma server returns. - const karmaServer = new karma.Server( - karmaConfig as Config, - exitCode => { - subscriber.next({ success: exitCode === 0 }); - subscriber.complete(); - }, - ); - - const karmaStart = karmaServer.start(); - - // Cleanup, signal Karma to exit. - return () => karmaStart.then(() => karmaServer.stop()); - })), - defaultIfEmpty({ success: false }), - ); -} - -export { KarmaBuilderOptions }; -export default createBuilder & KarmaBuilderOptions>(execute); diff --git a/packages/angular_devkit/build_angular/src/karma/rebuilds_spec.ts b/packages/angular_devkit/build_angular/src/karma/rebuilds_spec.ts deleted file mode 100644 index fd73eb706184..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/rebuilds_spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { Subject, timer } from 'rxjs'; -import { - catchError, - debounceTime, - delay, - map, - switchMap, - takeUntil, - takeWhile, - tap, -} from 'rxjs/operators'; -import { createArchitect, host, karmaTargetSpec } from '../test-utils'; - -describe('Karma Builder watch mode', () => { - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(() => host.restore().toPromise()); - - it('performs initial build', async () => { - const run = await architect.scheduleTarget(karmaTargetSpec, { watch: true }); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('recovers from compilation failures in watch mode', async () => { - let buildCount = 0; - let phase = 1; - - // The current linux-based CI environments may not fully settled in regards to filesystem - // changes from previous tests which reuse the same directory and fileset. - // The initial delay helps mitigate false positive rebuild triggers in such scenarios. - const { run } = await timer(1000).pipe( - switchMap(() => architect.scheduleTarget(karmaTargetSpec, { watch: true })), - switchMap(run => run.output.pipe(map(output => ({ run, output })))), - debounceTime(500), - tap(({ output }) => { - buildCount += 1; - switch (phase) { - case 1: - // Karma run should succeed. - // Add a compilation error. - expect(output.success).toBe(true); - // Add an syntax error to a non-main file. - host.appendToFile('src/app/app.component.spec.ts', `]]]`); - phase = 2; - break; - - case 2: - // Karma run should fail due to compilation error. Fix it. - expect(output.success).toBe(false); - host.replaceInFile('src/app/app.component.spec.ts', `]]]`, ''); - phase = 3; - break; - - case 3: - // Karma run should succeed again. - expect(output.success).toBe(true); - phase = 4; - break; - } - }), - takeWhile(() => phase < 4), - catchError((_, caught) => { - fail(`stuck at phase ${phase} [builds: ${buildCount}]`); - - return caught; - }), - ).toPromise(); - - await run.stop(); - }); - - it('does not rebuild when nothing changed', async () => { - let phase = 1; - - const stopSubject = new Subject(); - const stop$ = stopSubject.asObservable().pipe(delay(5000)); - - // The current linux-based CI environments may not fully settled in regards to filesystem - // changes from previous tests which reuse the same directory and fileset. - // The initial delay helps mitigate false positive rebuild triggers in such scenarios. - const { run } = await timer(1000).pipe( - switchMap(() => architect.scheduleTarget(karmaTargetSpec, { watch: true })), - switchMap(run => run.output.pipe(map(output => ({ run, output })))), - debounceTime(500), - tap(({ output }) => { - switch (phase) { - case 1: - // Karma run should succeed. - expect(output.success).toBe(true); - // Touch the file. - host.appendToFile('src/app/app.component.spec.ts', ``); - // Signal the stopper, which delays emission by 5s. - // If there's no rebuild within that time then the test is successful. - stopSubject.next(); - phase = 2; - break; - - case 2: - // Should never trigger this second build. - fail('Should not trigger second build.'); - break; - } - }), - takeUntil(stop$), - ).toPromise(); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/karma/schema.json b/packages/angular_devkit/build_angular/src/karma/schema.json deleted file mode 100644 index 75cd984ec0f1..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/schema.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "title": "Karma Target", - "description": "Karma target options for Build Facade.", - "type": "object", - "properties": { - "main": { - "type": "string", - "description": "The name of the main entry-point file." - }, - "tsConfig": { - "type": "string", - "description": "The name of the TypeScript configuration file." - }, - "karmaConfig": { - "type": "string", - "description": "The name of the Karma configuration file." - }, - "polyfills": { - "type": "string", - "description": "The name of the polyfills file." - }, - "assets": { - "type": "array", - "description": "List of static application assets.", - "default": [], - "items": { - "$ref": "#/definitions/assetPattern" - } - }, - "scripts": { - "description": "Global scripts to be included in the build.", - "type": "array", - "default": [], - "items": { - "$ref": "#/definitions/extraEntryPoint" - } - }, - "styles": { - "description": "Global styles to be included in the build.", - "type": "array", - "default": [], - "items": { - "$ref": "#/definitions/extraEntryPoint" - } - }, - "inlineStyleLanguage": { - "description": "The stylesheet language to use for the application's inline component styles.", - "type": "string", - "default": "css", - "enum": [ - "css", - "less", - "sass", - "scss" - ] - }, - "stylePreprocessorOptions": { - "description": "Options to pass to style preprocessors", - "type": "object", - "properties": { - "includePaths": { - "description": "Paths to include. Paths will be resolved to project root.", - "type": "array", - "items": { - "type": "string" - }, - "default": [] - } - }, - "additionalProperties": false - }, - "include": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Globs of files to include, relative to workspace or project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead" - }, - "sourceMap": { - "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", - "default": true, - "oneOf": [ - { - "type": "object", - "properties": { - "scripts": { - "type": "boolean", - "description": "Output source maps for all scripts.", - "default": true - }, - "styles": { - "type": "boolean", - "description": "Output source maps for all styles.", - "default": true - }, - "vendor": { - "type": "boolean", - "description": "Resolve vendor packages source maps.", - "default": false - } - }, - "additionalProperties": false - }, - { - "type": "boolean" - } - ] - }, - "progress": { - "type": "boolean", - "description": "Log progress to the console while building.", - "default": true - }, - "watch": { - "type": "boolean", - "description": "Run build when files change." - }, - "poll": { - "type": "number", - "description": "Enable and define the file watching poll time period in milliseconds." - }, - "preserveSymlinks": { - "type": "boolean", - "description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set." - }, - "browsers": { - "type": "string", - "description": "Override which browsers tests are run against." - }, - "codeCoverage": { - "type": "boolean", - "description": "Output a code coverage report.", - "default": false - }, - "codeCoverageExclude": { - "type": "array", - "description": "Globs to exclude from code coverage.", - "items": { - "type": "string" - }, - "default": [] - }, - "fileReplacements": { - "description": "Replace compilation source files with other compilation source files in the build.", - "type": "array", - "items": { - "oneOf": [ - { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "replaceWith": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "src", - "replaceWith" - ] - }, - { - "type": "object", - "properties": { - "replace": { - "type": "string" - }, - "with": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "replace", - "with" - ] - } - ] - }, - "default": [] - }, - "reporters": { - "type": "array", - "description": "Karma reporters to use. Directly passed to the karma runner.", - "items": { - "type": "string" - } - }, - "webWorkerTsConfig": { - "type": "string", - "description": "TypeScript configuration for Web Worker modules." - } - }, - "additionalProperties": false, - "required": [ - "main", - "tsConfig", - "karmaConfig" - ], - "definitions": { - "assetPattern": { - "oneOf": [ - { - "type": "object", - "properties": { - "glob": { - "type": "string", - "description": "The pattern to match." - }, - "input": { - "type": "string", - "description": "The input directory path in which to apply 'glob'. Defaults to the project root." - }, - "output": { - "type": "string", - "description": "Absolute path within the output." - }, - "ignore": { - "description": "An array of globs to ignore.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false, - "required": [ - "glob", - "input", - "output" - ] - }, - { - "type": "string" - } - ] - }, - "extraEntryPoint": { - "oneOf": [ - { - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "The file to include." - }, - "bundleName": { - "type": "string", - "pattern": "^[\\w\\-.]*$", - "description": "The bundle name for this extra entry point." - }, - "inject": { - "type": "boolean", - "description": "If the bundle will be referenced in the HTML file.", - "default": true - } - }, - "additionalProperties": false, - "required": [ - "input" - ] - }, - { - "type": "string", - "description": "The file to include." - } - ] - } - } -} \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/src/karma/selected_spec.ts b/packages/angular_devkit/build_angular/src/karma/selected_spec.ts deleted file mode 100644 index 84623f836359..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/selected_spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { logging } from '@angular-devkit/core'; -import { createArchitect, host, karmaTargetSpec } from '../test-utils'; - -describe('Karma Builder', () => { - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(() => host.restore().toPromise()); - - describe('with include option', () => { - it('should fail when include does not match any files', async () => { - const overrides = { - include: ['abc.spec.ts', 'def.spec.ts'], - }; - const run = await architect.scheduleTarget(karmaTargetSpec, overrides); - - await expectAsync(run.result).toBeRejectedWithError( - `Specified patterns: "abc.spec.ts, def.spec.ts" did not match any spec files.`, - ); - - await run.stop(); - }); - - it('should fail when main test file does not include require.context usage', async () => { - let lastErrorLogEntry: logging.LogEntry | undefined; - const logger = new logging.Logger('test'); - logger.subscribe(m => { - if (m.level === 'error') { - lastErrorLogEntry = m; - } - }); - - const mockedRequireContext = 'Object.assign(() => { }, { keys: () => [] as string[] })'; - const regex = /require\.context\(.*/; - host.replaceInFile('src/test.ts', regex, mockedRequireContext); - - const overrides = { - include: ['**/*.spec.ts'], - }; - - const run = await architect.scheduleTarget(karmaTargetSpec, overrides, { - logger, - }); - - await expectAsync(run.result).toBeResolved(); - - expect(lastErrorLogEntry && lastErrorLogEntry.message).toContain( - 'const context = require.context', - ); - expect(lastErrorLogEntry && lastErrorLogEntry.message) - // tslint:disable-next-line:max-line-length - .toContain( - "The 'include' option requires that the 'main' file for tests includes the below line:", - ); - - await run.stop(); - }); - - it('should work with test.ts that filters found keys', async () => { - // the replacement below is only to prove a point that resulting test.ts file will compile! - host.replaceInFile('src/test.ts', 'context.keys().map(context);', 'context.keys().filter(k => !!k).map(context);'); - - const overrides = { - include: ['src/app/app.component.spec.ts'], - }; - const logger = new logging.Logger('test'); - logger.subscribe(m => { - if (m.level === 'error') { - fail(m); - } - }); - const run = await architect.scheduleTarget(karmaTargetSpec, overrides, { - logger, - }); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - }); - - [ - { - test: 'relative path from workspace to spec', - input: ['src/app/app.component.spec.ts'], - }, - { - test: 'relative path from workspace to file', - input: ['src/app/app.component.ts'], - }, - { - test: 'relative path from project root to spec', - input: ['app/services/test.service.spec.ts'], - }, - { - test: 'relative path from project root to file', - input: ['app/services/test.service.ts'], - }, - { - test: 'relative path from workspace to directory', - input: ['src/app/services'], - }, - { - test: 'relative path from project root to directory', - input: ['app/services'], - }, - { - test: 'glob with spec suffix', - input: ['**/*.pipe.spec.ts', '**/*.pipe.spec.ts', '**/*test.service.spec.ts'], - }, - ].forEach((options, index) => { - it(`should work with ${options.test} (${index})`, async () => { - host.writeMultipleFiles({ - 'src/app/services/test.service.spec.ts': ` - describe('TestService', () => { - it('should succeed', () => { - expect(true).toBe(true); - }); - });`, - 'src/app/failing.service.spec.ts': ` - describe('FailingService', () => { - it('should be ignored', () => { - expect(true).toBe(false); - }); - });`, - 'src/app/property.pipe.spec.ts': ` - describe('PropertyPipe', () => { - it('should succeed', () => { - expect(true).toBe(true); - }); - });`, - }); - - const overrides = { - include: options.input, - }; - const logger = new logging.Logger('test'); - logger.subscribe(m => { - if (m.level === 'error') { - fail(m); - } - }); - const run = await architect.scheduleTarget(karmaTargetSpec, overrides, { - logger, - }); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/karma/works_spec.ts b/packages/angular_devkit/build_angular/src/karma/works_spec.ts deleted file mode 100644 index 4a8701464ce3..000000000000 --- a/packages/angular_devkit/build_angular/src/karma/works_spec.ts +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { createArchitect, host, karmaTargetSpec } from '../test-utils'; - - -// tslint:disable-next-line:no-big-function -describe('Karma Builder', () => { - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(() => host.restore().toPromise()); - - it('runs', async () => { - const run = await architect.scheduleTarget(karmaTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('fails with broken compilation', async () => { - host.writeMultipleFiles({ - 'src/app/app.component.spec.ts': '

definitely not typescript

', - }); - - const run = await architect.scheduleTarget(karmaTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); - - await run.stop(); - }); - - it('generates and uses global styles', async () => { - host.writeMultipleFiles({ - 'src/styles.css': 'p {display: none}', - 'src/app/app.component.ts': ` - import { Component } from '@angular/core'; - - @Component({ - selector: 'app-root', - template: '

Hello World

' - }) - export class AppComponent { - } - `, - 'src/app/app.component.spec.ts': ` - import { TestBed } from '@angular/core/testing'; - import { AppComponent } from './app.component'; - - describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - ], - declarations: [ - AppComponent - ] - }).compileComponents(); - }); - - it('should not contain text that is hidden via css', () => { - const fixture = TestBed.createComponent(AppComponent); - expect(fixture.nativeElement.innerText).not.toContain('Hello World'); - }); - });`, - }); - - const run = await architect.scheduleTarget(karmaTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('generates and uses assets', async () => { - const assets: { [path: string]: string } = { - './src/string-file-asset.txt': 'string-file-asset.txt', - './src/string-folder-asset/file.txt': 'string-folder-asset.txt', - './src/glob-asset.txt': 'glob-asset.txt', - './src/folder/folder-asset.txt': 'folder-asset.txt', - './src/output-asset.txt': 'output-asset.txt', - }; - host.writeMultipleFiles(assets); - host.writeMultipleFiles({ - 'src/app/app.module.ts': ` - import { BrowserModule } from '@angular/platform-browser'; - import { NgModule } from '@angular/core'; - import { HttpClientModule } from '@angular/common/http'; - import { AppComponent } from './app.component'; - - @NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - HttpClientModule - ], - providers: [], - bootstrap: [AppComponent] - }) - export class AppModule { } - `, - 'src/app/app.component.ts': ` - import { Component } from '@angular/core'; - import { HttpClient } from '@angular/common/http'; - - @Component({ - selector: 'app-root', - template: '

{{asset.content }}

' - }) - export class AppComponent { - public assets = [ - { path: './string-file-asset.txt', content: '' }, - { path: './string-folder-asset/file.txt', content: '' }, - { path: './glob-asset.txt', content: '' }, - { path: './folder/folder-asset.txt', content: '' }, - { path: './output-folder/output-asset.txt', content: '' }, - ]; - constructor(private http: HttpClient) { - this.assets.forEach(asset => http.get(asset.path, { responseType: 'text' }) - .subscribe(res => asset.content = res)); - } - }`, - 'src/app/app.component.spec.ts': ` - import { TestBed } from '@angular/core/testing'; - import { HttpClientModule } from '@angular/common/http'; - import { AppComponent } from './app.component'; - - describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - HttpClientModule - ], - declarations: [ - AppComponent - ] - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - }); - });`, - }); - - const overrides = { - assets: [ - 'src/string-file-asset.txt', - 'src/string-folder-asset', - { glob: 'glob-asset.txt', input: 'src/', output: '/' }, - { glob: 'output-asset.txt', input: 'src/', output: '/output-folder' }, - { glob: '**/*', input: 'src/folder', output: '/folder' }, - ], - }; - - const run = await architect.scheduleTarget(karmaTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('allows file replacements', async () => { - host.writeMultipleFiles({ - 'src/meaning-too.ts': 'export var meaning = 42;', - 'src/meaning.ts': `export var meaning = 10;`, - - 'src/test.ts': ` - import { meaning } from './meaning'; - - describe('Test file replacement', () => { - it('should replace file', () => { - expect(meaning).toBe(42); - }); - }); - `, - }); - - const overrides = { - fileReplacements: [{ - replace: '/src/meaning.ts', - with: '/src/meaning-too.ts', - }], - }; - - const run = await architect.scheduleTarget(karmaTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/ng-packagr/index.ts b/packages/angular_devkit/build_angular/src/ng-packagr/index.ts deleted file mode 100644 index 09aad0f68717..000000000000 --- a/packages/angular_devkit/build_angular/src/ng-packagr/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; -import { resolve } from 'path'; -import { Observable, from } from 'rxjs'; -import { mapTo, switchMap } from 'rxjs/operators'; -import { Schema as NgPackagrBuilderOptions } from './schema'; - -async function initialize( - options: NgPackagrBuilderOptions, - root: string, -): Promise { - const packager = (await import('ng-packagr')).ngPackagr(); - - packager.forProject(resolve(root, options.project)); - - if (options.tsConfig) { - packager.withTsConfig(resolve(root, options.tsConfig)); - } - - return packager; -} - -/** - * @experimental Direct usage of this function is considered experimental. - */ -export function execute( - options: NgPackagrBuilderOptions, - context: BuilderContext, -): Observable { - return from(initialize(options, context.workspaceRoot)).pipe( - switchMap(packager => options.watch ? packager.watch() : packager.build()), - mapTo({ success: true }), - ); -} - -export { NgPackagrBuilderOptions }; -export default createBuilder & NgPackagrBuilderOptions>(execute); diff --git a/packages/angular_devkit/build_angular/src/ng-packagr/schema.json b/packages/angular_devkit/build_angular/src/ng-packagr/schema.json deleted file mode 100644 index a72def315064..000000000000 --- a/packages/angular_devkit/build_angular/src/ng-packagr/schema.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "title": "ng-packagr Target", - "description": "ng-packagr target options for Build Architect. Use to build library projects.", - "type": "object", - "properties": { - "project": { - "type": "string", - "description": "The file path for the ng-packagr configuration file, relative to the current workspace." - }, - "tsConfig": { - "type": "string", - "description": "The full path for the TypeScript configuration file, relative to the current workspace." - }, - "watch": { - "type": "boolean", - "description": "Run build when files change.", - "default": false - } - }, - "additionalProperties": false, - "required": [ - "project" - ] -} diff --git a/packages/angular_devkit/build_angular/src/ng-packagr/works_spec.ts b/packages/angular_devkit/build_angular/src/ng-packagr/works_spec.ts deleted file mode 100644 index 52eea6f67a10..000000000000 --- a/packages/angular_devkit/build_angular/src/ng-packagr/works_spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; -import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing'; -import { - getSystemPath, - join, - normalize, - schema, - virtualFs, - workspaces, -} from '@angular-devkit/core'; -import { map, take, tap } from 'rxjs/operators'; - -// Default timeout for large specs is 2.5 minutes. -jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000; - - -describe('NgPackagr Builder', () => { - const workspaceRoot = join(normalize(__dirname), `../../test/hello-world-lib/`); - const host = new TestProjectHost(workspaceRoot); - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - - const registry = new schema.CoreSchemaRegistry(); - registry.addPostTransform(schema.transforms.addUndefinedDefaults); - - const workspaceSysPath = getSystemPath(host.root()); - const { workspace } = await workspaces.readWorkspace( - workspaceSysPath, - workspaces.createWorkspaceHost(host), - ); - const architectHost = new TestingArchitectHost( - workspaceSysPath, - workspaceSysPath, - new WorkspaceNodeModulesArchitectHost(workspace, workspaceSysPath), - ); - - architect = new Architect(architectHost, registry); - }); - - afterEach(() => host.restore().toPromise()); - - it('builds and packages a library', async () => { - const run = await architect.scheduleTarget({ project: 'lib', target: 'build' }); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - - expect(host.scopedSync().exists(normalize('./dist/lib/fesm2015/lib.js'))).toBe(true); - const content = virtualFs.fileBufferToString( - host.scopedSync().read(normalize('./dist/lib/fesm2015/lib.js')), - ); - expect(content).toContain('lib works'); - - expect(content).toContain('ɵcmp'); - }); - - it('rebuilds on TS file changes', async () => { - const goldenValueFiles: { [path: string]: string } = { - 'projects/lib/src/lib/lib.component.ts': ` - import { Component } from '@angular/core'; - - @Component({ - selector: 'lib', - template: 'lib update works!' - }) - export class LibComponent { } - `, - }; - - const run = await architect.scheduleTarget( - { project: 'lib', target: 'build' }, - { watch: true }, - ); - - let buildNumber = 0; - - await run.output.pipe( - tap((buildEvent) => expect(buildEvent.success).toBe(true)), - map(() => { - const fileName = './dist/lib/fesm2015/lib.js'; - const content = virtualFs.fileBufferToString( - host.scopedSync().read(normalize(fileName)), - ); - - return content; - }), - tap(content => { - buildNumber += 1; - switch (buildNumber) { - case 1: - expect(content).toMatch(/lib works/); - host.writeMultipleFiles(goldenValueFiles); - break; - - case 2: - expect(content).toMatch(/lib update works/); - break; - default: - break; - } - }), - take(2), - ).toPromise(); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/protractor/index.ts b/packages/angular_devkit/build_angular/src/protractor/index.ts deleted file mode 100644 index 7d3aca6cd704..000000000000 --- a/packages/angular_devkit/build_angular/src/protractor/index.ts +++ /dev/null @@ -1,175 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { - BuilderContext, - BuilderOutput, - createBuilder, - targetFromTargetString, -} from '@angular-devkit/architect'; -import { JsonObject, tags } from '@angular-devkit/core'; -import { resolve } from 'path'; -import * as url from 'url'; -import { DevServerBuilderOptions } from '../dev-server/index'; -import { runModuleAsObservableFork } from '../utils'; -import { Schema as ProtractorBuilderOptions } from './schema'; - - -interface JasmineNodeOpts { - jasmineNodeOpts: { - grep?: string; - invertGrep?: boolean; - }; -} - -function runProtractor(root: string, options: ProtractorBuilderOptions): Promise { - const additionalProtractorConfig: Partial & Partial = { - baseUrl: options.baseUrl, - specs: options.specs && options.specs.length ? options.specs : undefined, - suite: options.suite, - jasmineNodeOpts: { - grep: options.grep, - invertGrep: options.invertGrep, - }, - }; - - // TODO: Protractor manages process.exit itself, so this target will allways quit the - // process. To work around this we run it in a subprocess. - // https://github.com/angular/protractor/issues/4160 - return runModuleAsObservableFork( - root, - 'protractor/built/launcher', - 'init', - [resolve(root, options.protractorConfig), additionalProtractorConfig], - ).toPromise() as Promise; -} - -async function updateWebdriver() { - // The webdriver-manager update command can only be accessed via a deep import. - const webdriverDeepImport = 'webdriver-manager/built/lib/cmds/update'; - - let path; - try { - const protractorPath = require.resolve('protractor'); - - path = require.resolve(webdriverDeepImport, { paths: [protractorPath] }); - } catch (error) { - if (error.code !== 'MODULE_NOT_FOUND') { - throw error; - } - } - - if (!path) { - throw new Error(tags.stripIndents` - Cannot automatically find webdriver-manager to update. - Update webdriver-manager manually and run 'ng e2e --no-webdriver-update' instead. - `); - } - - // tslint:disable-next-line:max-line-length no-implicit-dependencies - const webdriverUpdate = await import(path); - // const webdriverUpdate = await import(path) as typeof import ('webdriver-manager/built/lib/cmds/update'); - - // run `webdriver-manager update --standalone false --gecko false --quiet` - // if you change this, update the command comment in prev line - return webdriverUpdate.program.run({ - standalone: false, - gecko: false, - quiet: true, - } as unknown as JSON); -} - -export { ProtractorBuilderOptions }; - -/** - * @experimental Direct usage of this function is considered experimental. - */ -export async function execute( - options: ProtractorBuilderOptions, - context: BuilderContext, -): Promise { - // ensure that only one of these options is used - if (options.devServerTarget && options.baseUrl) { - throw new Error(tags.stripIndents` - The 'baseUrl' option cannot be used with 'devServerTarget'. - When present, 'devServerTarget' will be used to automatically setup 'baseUrl' for Protractor. - `); - } - - if (options.webdriverUpdate) { - await updateWebdriver(); - } - - let baseUrl = options.baseUrl; - let server; - if (options.devServerTarget) { - const target = targetFromTargetString(options.devServerTarget); - const serverOptions = await context.getTargetOptions(target); - - const overrides = { - watch: false, - liveReload: false, - } as DevServerBuilderOptions; - - if (options.host !== undefined) { - overrides.host = options.host; - } else if (typeof serverOptions.host === 'string') { - options.host = serverOptions.host; - } else { - options.host = overrides.host = 'localhost'; - } - - if (options.port !== undefined) { - overrides.port = options.port; - } else if (typeof serverOptions.port === 'number') { - options.port = serverOptions.port; - } - - server = await context.scheduleTarget(target, overrides); - const result = await server.result; - if (!result.success) { - return { success: false }; - } - - if (typeof serverOptions.publicHost === 'string') { - let publicHost = serverOptions.publicHost as string; - if (!/^\w+:\/\//.test(publicHost)) { - publicHost = `${serverOptions.ssl - ? 'https' - : 'http'}://${publicHost}`; - } - const clientUrl = url.parse(publicHost); - baseUrl = url.format(clientUrl); - } else if (typeof result.baseUrl === 'string') { - baseUrl = result.baseUrl; - } else if (typeof result.port === 'number') { - baseUrl = url.format({ - protocol: serverOptions.ssl ? 'https' : 'http', - hostname: options.host, - port: result.port.toString(), - }); - } - } - - // Like the baseUrl in protractor config file when using the API we need to add - // a trailing slash when provide to the baseUrl. - if (baseUrl && !baseUrl.endsWith('/')) { - baseUrl += '/'; - } - - try { - return await runProtractor(context.workspaceRoot, { ...options, baseUrl }); - } catch { - return { success: false }; - } finally { - if (server) { - await server.stop(); - } - } -} - -export default createBuilder(execute); diff --git a/packages/angular_devkit/build_angular/src/protractor/schema.json b/packages/angular_devkit/build_angular/src/protractor/schema.json deleted file mode 100644 index ff431254bdfe..000000000000 --- a/packages/angular_devkit/build_angular/src/protractor/schema.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "title": "Protractor Target", - "description": "Protractor target options for Build Facade.", - "type": "object", - "properties": { - "protractorConfig": { - "type": "string", - "description": "The name of the Protractor configuration file." - }, - "devServerTarget": { - "type": "string", - "description": "A dev-server builder target to run tests against in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.", - "pattern": "^([^:\\s]+:[^:\\s]+(:[^\\s]+)?)?$" - }, - "grep": { - "type": "string", - "description": "Execute specs whose names match the pattern, which is internally compiled to a RegExp." - }, - "invertGrep": { - "type": "boolean", - "description": "Invert the selection specified by the 'grep' option.", - "default": false - }, - "specs": { - "type": "array", - "description": "Override specs in the protractor config.", - "default": [], - "items": { - "type": "string", - "description": "Spec name." - } - }, - "suite": { - "type": "string", - "description": "Override suite in the protractor config." - }, - "webdriverUpdate": { - "type": "boolean", - "description": "Try to update webdriver.", - "default": true - }, - "port": { - "type": "number", - "description": "The port to use to serve the application." - }, - "host": { - "type": "string", - "description": "Host to listen on." - }, - "baseUrl": { - "type": "string", - "description": "Base URL for protractor to connect to." - } - }, - "additionalProperties": false, - "required": [ - "protractorConfig" - ] -} diff --git a/packages/angular_devkit/build_angular/src/protractor/works_spec.ts b/packages/angular_devkit/build_angular/src/protractor/works_spec.ts deleted file mode 100644 index 36678ec3b362..000000000000 --- a/packages/angular_devkit/build_angular/src/protractor/works_spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect } from '@angular-devkit/architect'; -import { JsonObject, normalize } from '@angular-devkit/core'; -import { createArchitect, host, protractorTargetSpec } from '../test-utils'; - -describe('Protractor Builder', () => { - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - - afterEach(() => host.restore().toPromise()); - - it('executes tests with automatic dev server usage', async () => { - const run = await architect.scheduleTarget(protractorTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('fails with no devServerTarget and no standalone server', async () => { - const overrides = { devServerTarget: undefined } as unknown as JsonObject; - const run = await architect.scheduleTarget(protractorTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false })); - - await run.stop(); - }); - - it('overrides protractor specs', async () => { - host.scopedSync().rename( - normalize('./e2e/app.e2e-spec.ts'), - normalize('./e2e/renamed-app.e2e.spec.ts'), - ); - - const overrides = { specs: ['./e2e/renamed-app.e2e.spec.ts'] }; - const run = await architect.scheduleTarget(protractorTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('overrides protractor suites', async () => { - host.scopedSync().rename( - normalize('./e2e/app.e2e-spec.ts'), - normalize('./e2e/renamed-app.e2e-spec.ts'), - ); - - // Suites block needs to be added in the protractor.conf.js file to test suites - host.replaceInFile('protractor.conf.js', `allScriptsTimeout: 11000,`, ` - allScriptsTimeout: 11000, - suites: { - app: './e2e/app.e2e-spec.ts' - }, - `); - - const overrides = { suite: 'app' }; - const run = await architect.scheduleTarget(protractorTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('supports automatic port assignment (port = 0)', async () => { - const overrides = { port: 0 }; - const run = await architect.scheduleTarget(protractorTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('supports dev server builder with browser builder base HREF option', async () => { - host.replaceInFile( - 'angular.json', - '"main": "src/main.ts",', - '"main": "src/main.ts", "baseHref": "/base/",', - ); - // Need to reset architect to use the modified config - architect = (await createArchitect(host.root())).architect; - - const run = await architect.scheduleTarget(protractorTargetSpec); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('supports running tests by pattern', async () => { - host.writeMultipleFiles({ - 'e2e/app.e2e-spec.ts': ` - it('should succeed', () => expect(true).toBeTruthy()); - it('should fail', () => expect(false).toBeTruthy()); - `, - }); - - const overrides = { grep: 'succeed' }; - - const run = await architect.scheduleTarget(protractorTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); - - it('supports running tests excluding a pattern', async () => { - host.writeMultipleFiles({ - 'e2e/app.e2e-spec.ts': ` - it('should succeed', () => expect(true).toBeTruthy()); - it('should fail', () => expect(false).toBeTruthy()); - `, - }); - - const overrides = { grep: 'fail', invertGrep: true }; - - const run = await architect.scheduleTarget(protractorTargetSpec, overrides); - - await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true })); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/sass/rebasing-importer.ts b/packages/angular_devkit/build_angular/src/sass/rebasing-importer.ts new file mode 100644 index 000000000000..2bbf12e5f7ee --- /dev/null +++ b/packages/angular_devkit/build_angular/src/sass/rebasing-importer.ts @@ -0,0 +1,474 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { RawSourceMap } from '@ampproject/remapping'; +import MagicString from 'magic-string'; +import { Dirent, readFileSync, readdirSync } from 'node:fs'; +import { basename, dirname, extname, join, relative } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import type { FileImporter, Importer, ImporterResult, Syntax } from 'sass'; + +/** + * A Sass Importer base class that provides the load logic to rebase all `url()` functions + * within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler + * reflect the final filesystem location of the output CSS file. + * + * This class provides the core of the rebasing functionality. To ensure that each file is processed + * by this importer's load implementation, the Sass compiler requires the importer's canonicalize + * function to return a non-null value with the resolved location of the requested stylesheet. + * Concrete implementations of this class must provide this canonicalize functionality for rebasing + * to be effective. + */ +abstract class UrlRebasingImporter implements Importer<'sync'> { + /** + * @param entryDirectory The directory of the entry stylesheet that was passed to the Sass compiler. + * @param rebaseSourceMaps When provided, rebased files will have an intermediate sourcemap added to the Map + * which can be used to generate a final sourcemap that contains original sources. + */ + constructor( + private entryDirectory: string, + private rebaseSourceMaps?: Map, + ) {} + + abstract canonicalize(url: string, options: { fromImport: boolean }): URL | null; + + load(canonicalUrl: URL): ImporterResult | null { + const stylesheetPath = fileURLToPath(canonicalUrl); + const stylesheetDirectory = dirname(stylesheetPath); + let contents = readFileSync(stylesheetPath, 'utf-8'); + + // Rebase any URLs that are found + let updatedContents; + for (const { start, end, value } of findUrls(contents)) { + // Skip if value is empty or a Sass variable + if (value.length === 0 || value.startsWith('$')) { + continue; + } + + // Skip if root-relative, absolute or protocol relative url + if (/^((?:\w+:)?\/\/|data:|chrome:|#|\/)/.test(value)) { + continue; + } + + const rebasedPath = relative(this.entryDirectory, join(stylesheetDirectory, value)); + + // Normalize path separators and escape characters + // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax + const rebasedUrl = './' + rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&'); + + updatedContents ??= new MagicString(contents); + updatedContents.update(start, end, rebasedUrl); + } + + if (updatedContents) { + contents = updatedContents.toString(); + if (this.rebaseSourceMaps) { + // Generate an intermediate source map for the rebasing changes + const map = updatedContents.generateMap({ + hires: true, + includeContent: true, + source: canonicalUrl.href, + }); + this.rebaseSourceMaps.set(canonicalUrl.href, map as RawSourceMap); + } + } + + let syntax: Syntax | undefined; + switch (extname(stylesheetPath).toLowerCase()) { + case 'css': + syntax = 'css'; + break; + case 'sass': + syntax = 'indented'; + break; + default: + syntax = 'scss'; + break; + } + + return { + contents, + syntax, + sourceMapUrl: canonicalUrl, + }; + } +} + +/** + * Determines if a unicode code point is a CSS whitespace character. + * @param code The unicode code point to test. + * @returns true, if the code point is CSS whitespace; false, otherwise. + */ +function isWhitespace(code: number): boolean { + // Based on https://www.w3.org/TR/css-syntax-3/#whitespace + switch (code) { + case 0x0009: // tab + case 0x0020: // space + case 0x000a: // line feed + case 0x000c: // form feed + case 0x000d: // carriage return + return true; + default: + return false; + } +} + +/** + * Scans a CSS or Sass file and locates all valid url function values as defined by the CSS + * syntax specification. + * @param contents A string containing a CSS or Sass file to scan. + * @returns An iterable that yields each CSS url function value found. + */ +function* findUrls(contents: string): Iterable<{ start: number; end: number; value: string }> { + let pos = 0; + let width = 1; + let current = -1; + const next = () => { + pos += width; + current = contents.codePointAt(pos) ?? -1; + width = current > 0xffff ? 2 : 1; + + return current; + }; + + // Based on https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token + while ((pos = contents.indexOf('url('https://github.com/,%20pos)) !== -1) { + // Set to position of the ( + pos += 3; + width = 1; + + // Consume all leading whitespace + while (isWhitespace(next())) { + /* empty */ + } + + // Initialize URL state + const url = { start: pos, end: -1, value: '' }; + let complete = false; + + // If " or ', then consume the value as a string + if (current === 0x0022 || current === 0x0027) { + const ending = current; + // Based on https://www.w3.org/TR/css-syntax-3/#consume-string-token + while (!complete) { + switch (next()) { + case -1: // EOF + return; + case 0x000a: // line feed + case 0x000c: // form feed + case 0x000d: // carriage return + // Invalid + complete = true; + break; + case 0x005c: // \ -- character escape + // If not EOF or newline, add the character after the escape + switch (next()) { + case -1: + return; + case 0x000a: // line feed + case 0x000c: // form feed + case 0x000d: // carriage return + // Skip when inside a string + break; + default: + // TODO: Handle hex escape codes + url.value += String.fromCodePoint(current); + break; + } + break; + case ending: + // Full string position should include the quotes for replacement + url.end = pos + 1; + complete = true; + yield url; + break; + default: + url.value += String.fromCodePoint(current); + break; + } + } + + next(); + continue; + } + + // Based on https://www.w3.org/TR/css-syntax-3/#consume-url-token + while (!complete) { + switch (current) { + case -1: // EOF + return; + case 0x0022: // " + case 0x0027: // ' + case 0x0028: // ( + // Invalid + complete = true; + break; + case 0x0029: // ) + // URL is valid and complete + url.end = pos; + complete = true; + break; + case 0x005c: // \ -- character escape + // If not EOF or newline, add the character after the escape + switch (next()) { + case -1: // EOF + return; + case 0x000a: // line feed + case 0x000c: // form feed + case 0x000d: // carriage return + // Invalid + complete = true; + break; + default: + // TODO: Handle hex escape codes + url.value += String.fromCodePoint(current); + break; + } + break; + default: + if (isWhitespace(current)) { + while (isWhitespace(next())) { + /* empty */ + } + // Unescaped whitespace is only valid before the closing ) + if (current === 0x0029) { + // URL is valid + url.end = pos; + } + complete = true; + } else { + // Add the character to the url value + url.value += String.fromCodePoint(current); + } + break; + } + next(); + } + + // An end position indicates a URL was found + if (url.end !== -1) { + yield url; + } + } +} + +/** + * Provides the Sass importer logic to resolve relative stylesheet imports via both import and use rules + * and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that + * the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file. + */ +export class RelativeUrlRebasingImporter extends UrlRebasingImporter { + constructor( + entryDirectory: string, + private directoryCache = new Map(), + rebaseSourceMaps?: Map, + ) { + super(entryDirectory, rebaseSourceMaps); + } + + canonicalize(url: string, options: { fromImport: boolean }): URL | null { + return this.resolveImport(url, options.fromImport, true); + } + + /** + * Attempts to resolve a provided URL to a stylesheet file using the Sass compiler's resolution algorithm. + * Based on https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart + * @param url The file protocol URL to resolve. + * @param fromImport If true, URL was from an import rule; otherwise from a use rule. + * @param checkDirectory If true, try checking for a directory with the base name containing an index file. + * @returns A full resolved URL of the stylesheet file or `null` if not found. + */ + private resolveImport(url: string, fromImport: boolean, checkDirectory: boolean): URL | null { + let stylesheetPath; + try { + stylesheetPath = fileURLToPath(url); + } catch { + // Only file protocol URLs are supported by this importer + return null; + } + + const directory = dirname(stylesheetPath); + const extension = extname(stylesheetPath); + const hasStyleExtension = + extension === '.scss' || extension === '.sass' || extension === '.css'; + // Remove the style extension if present to allow adding the `.import` suffix + const filename = basename(stylesheetPath, hasStyleExtension ? extension : undefined); + + let entries; + try { + entries = this.directoryCache.get(directory); + if (!entries) { + entries = readdirSync(directory, { withFileTypes: true }); + this.directoryCache.set(directory, entries); + } + } catch { + return null; + } + + const importPotentials = new Set(); + const defaultPotentials = new Set(); + + if (hasStyleExtension) { + if (fromImport) { + importPotentials.add(filename + '.import' + extension); + importPotentials.add('_' + filename + '.import' + extension); + } + defaultPotentials.add(filename + extension); + defaultPotentials.add('_' + filename + extension); + } else { + if (fromImport) { + importPotentials.add(filename + '.import.scss'); + importPotentials.add(filename + '.import.sass'); + importPotentials.add(filename + '.import.css'); + importPotentials.add('_' + filename + '.import.scss'); + importPotentials.add('_' + filename + '.import.sass'); + importPotentials.add('_' + filename + '.import.css'); + } + defaultPotentials.add(filename + '.scss'); + defaultPotentials.add(filename + '.sass'); + defaultPotentials.add(filename + '.css'); + defaultPotentials.add('_' + filename + '.scss'); + defaultPotentials.add('_' + filename + '.sass'); + defaultPotentials.add('_' + filename + '.css'); + } + + const foundDefaults: string[] = []; + const foundImports: string[] = []; + let hasPotentialIndex = false; + for (const entry of entries) { + // Record if the name should be checked as a directory with an index file + if (checkDirectory && !hasStyleExtension && entry.name === filename && entry.isDirectory()) { + hasPotentialIndex = true; + } + + if (!entry.isFile()) { + continue; + } + + if (importPotentials.has(entry.name)) { + foundImports.push(join(directory, entry.name)); + } + + if (defaultPotentials.has(entry.name)) { + foundDefaults.push(join(directory, entry.name)); + } + } + + // `foundImports` will only contain elements if `options.fromImport` is true + const result = this.checkFound(foundImports) ?? this.checkFound(foundDefaults); + + if (result === null && hasPotentialIndex) { + // Check for index files using filename as a directory + return this.resolveImport(url + '/index', fromImport, false); + } + + return result; + } + + /** + * Checks an array of potential stylesheet files to determine if there is a valid + * stylesheet file. More than one discovered file may indicate an error. + * @param found An array of discovered stylesheet files. + * @returns A fully resolved URL for a stylesheet file or `null` if not found. + * @throws If there are ambiguous files discovered. + */ + private checkFound(found: string[]): URL | null { + if (found.length === 0) { + // Not found + return null; + } + + // More than one found file may be an error + if (found.length > 1) { + // Presence of CSS files alongside a Sass file does not cause an error + const foundWithoutCss = found.filter((element) => extname(element) !== '.css'); + // If the length is zero then there are two or more css files + // If the length is more than one than there are two or more sass/scss files + if (foundWithoutCss.length !== 1) { + throw new Error('Ambiguous import detected.'); + } + + // Return the non-CSS file (sass/scss files have priority) + // https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart#L44-L47 + return pathToFileURL(foundWithoutCss[0]); + } + + return pathToFileURL(found[0]); + } +} + +/** + * Provides the Sass importer logic to resolve module (npm package) stylesheet imports via both import and + * use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that + * the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file. + */ +export class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter { + constructor( + entryDirectory: string, + directoryCache: Map, + rebaseSourceMaps: Map | undefined, + private finder: FileImporter<'sync'>['findFileUrl'], + ) { + super(entryDirectory, directoryCache, rebaseSourceMaps); + } + + override canonicalize(url: string, options: { fromImport: boolean }): URL | null { + if (url.startsWith('file://')) { + return super.canonicalize(url, options); + } + + const result = this.finder(url, options); + + return result ? super.canonicalize(result.href, options) : null; + } +} + +/** + * Provides the Sass importer logic to resolve load paths located stylesheet imports via both import and + * use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that + * the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file. + */ +export class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter { + constructor( + entryDirectory: string, + directoryCache: Map, + rebaseSourceMaps: Map | undefined, + private loadPaths: Iterable, + ) { + super(entryDirectory, directoryCache, rebaseSourceMaps); + } + + override canonicalize(url: string, options: { fromImport: boolean }): URL | null { + if (url.startsWith('file://')) { + return super.canonicalize(url, options); + } + + let result = null; + for (const loadPath of this.loadPaths) { + result = super.canonicalize(pathToFileURL(join(loadPath, url)).href, options); + if (result !== null) { + break; + } + } + + return result; + } +} + +/** + * Workaround for Sass not calling instance methods with `this`. + * The `canonicalize` and `load` methods will be bound to the class instance. + * @param importer A Sass importer to bind. + * @returns The bound Sass importer. + */ +export function sassBindWorkaround(importer: T): T { + importer.canonicalize = importer.canonicalize.bind(importer); + importer.load = importer.load.bind(importer); + + return importer; +} diff --git a/packages/angular_devkit/build_angular/src/sass/sass-service-legacy.ts b/packages/angular_devkit/build_angular/src/sass/sass-service-legacy.ts new file mode 100644 index 000000000000..e1f64abad2d7 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/sass/sass-service-legacy.ts @@ -0,0 +1,251 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { join } from 'path'; +import { + LegacyAsyncImporter as AsyncImporter, + LegacyResult as CompileResult, + LegacyException as Exception, + LegacyImporterResult as ImporterResult, + LegacyImporterThis as ImporterThis, + LegacyOptions as Options, + LegacySyncImporter as SyncImporter, +} from 'sass'; +import { MessageChannel, Worker } from 'worker_threads'; +import { maxWorkers } from '../utils/environment-options'; + +/** + * The maximum number of Workers that will be created to execute render requests. + */ +const MAX_RENDER_WORKERS = maxWorkers; + +/** + * The callback type for the `dart-sass` asynchronous render function. + */ +type RenderCallback = (error?: Exception, result?: CompileResult) => void; + +/** + * An object containing the contextual information for a specific render request. + */ +interface RenderRequest { + id: number; + workerIndex: number; + callback: RenderCallback; + importers?: (SyncImporter | AsyncImporter)[]; +} + +/** + * A response from the Sass render Worker containing the result of the operation. + */ +interface RenderResponseMessage { + id: number; + error?: Exception; + result?: CompileResult; +} + +/** + * A Sass renderer implementation that provides an interface that can be used by Webpack's + * `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering + * with the `dart-sass` package. The `dart-sass` synchronous render function is used within + * the worker which can be up to two times faster than the asynchronous variant. + */ +export class SassLegacyWorkerImplementation { + private readonly workers: Worker[] = []; + private readonly availableWorkers: number[] = []; + private readonly requests = new Map(); + private readonly workerPath = join(__dirname, './worker-legacy.js'); + private idCounter = 1; + private nextWorkerIndex = 0; + + /** + * Provides information about the Sass implementation. + * This mimics enough of the `dart-sass` value to be used with the `sass-loader`. + */ + get info(): string { + return 'dart-sass\tworker'; + } + + /** + * The synchronous render function is not used by the `sass-loader`. + */ + renderSync(): never { + throw new Error('Sass renderSync is not supported.'); + } + + /** + * Asynchronously request a Sass stylesheet to be renderered. + * + * @param options The `dart-sass` options to use when rendering the stylesheet. + * @param callback The function to execute when the rendering is complete. + */ + render(options: Options<'async'>, callback: RenderCallback): void { + // The `functions`, `logger` and `importer` options are JavaScript functions that cannot be transferred. + // If any additional function options are added in the future, they must be excluded as well. + const { functions, importer, logger, ...serializableOptions } = options; + + // The CLI's configuration does not use or expose the ability to defined custom Sass functions + if (functions && Object.keys(functions).length > 0) { + throw new Error('Sass custom functions are not supported.'); + } + + let workerIndex = this.availableWorkers.pop(); + if (workerIndex === undefined) { + if (this.workers.length < MAX_RENDER_WORKERS) { + workerIndex = this.workers.length; + this.workers.push(this.createWorker()); + } else { + workerIndex = this.nextWorkerIndex++; + if (this.nextWorkerIndex >= this.workers.length) { + this.nextWorkerIndex = 0; + } + } + } + + const request = this.createRequest(workerIndex, callback, importer); + this.requests.set(request.id, request); + + this.workers[workerIndex].postMessage({ + id: request.id, + hasImporter: !!importer, + options: serializableOptions, + }); + } + + /** + * Shutdown the Sass render worker. + * Executing this method will stop any pending render requests. + */ + close(): void { + for (const worker of this.workers) { + try { + void worker.terminate(); + } catch {} + } + this.requests.clear(); + } + + private createWorker(): Worker { + const { port1: mainImporterPort, port2: workerImporterPort } = new MessageChannel(); + const importerSignal = new Int32Array(new SharedArrayBuffer(4)); + + const worker = new Worker(this.workerPath, { + workerData: { workerImporterPort, importerSignal }, + transferList: [workerImporterPort], + }); + + worker.on('message', (response: RenderResponseMessage) => { + const request = this.requests.get(response.id); + if (!request) { + return; + } + + this.requests.delete(response.id); + this.availableWorkers.push(request.workerIndex); + + if (response.result) { + // The results are expected to be Node.js `Buffer` objects but will each be transferred as + // a Uint8Array that does not have the expected `toString` behavior of a `Buffer`. + const { css, map, stats } = response.result; + const result: CompileResult = { + // This `Buffer.from` override will use the memory directly and avoid making a copy + css: Buffer.from(css.buffer, css.byteOffset, css.byteLength), + stats, + }; + if (map) { + // This `Buffer.from` override will use the memory directly and avoid making a copy + result.map = Buffer.from(map.buffer, map.byteOffset, map.byteLength); + } + request.callback(undefined, result); + } else { + request.callback(response.error); + } + }); + + mainImporterPort.on( + 'message', + ({ + id, + url, + prev, + fromImport, + }: { + id: number; + url: string; + prev: string; + fromImport: boolean; + }) => { + const request = this.requests.get(id); + if (!request?.importers) { + mainImporterPort.postMessage(null); + Atomics.store(importerSignal, 0, 1); + Atomics.notify(importerSignal, 0); + + return; + } + + this.processImporters(request.importers, url, prev, fromImport) + .then((result) => { + mainImporterPort.postMessage(result); + }) + .catch((error) => { + mainImporterPort.postMessage(error); + }) + .finally(() => { + Atomics.store(importerSignal, 0, 1); + Atomics.notify(importerSignal, 0); + }); + }, + ); + + mainImporterPort.unref(); + + return worker; + } + + private async processImporters( + importers: Iterable, + url: string, + prev: string, + fromImport: boolean, + ): Promise { + let result = null; + for (const importer of importers) { + result = await new Promise((resolve) => { + // Importers can be both sync and async + const innerResult = (importer as AsyncImporter).call( + { fromImport } as ImporterThis, + url, + prev, + resolve, + ); + if (innerResult !== undefined) { + resolve(innerResult); + } + }); + + if (result) { + break; + } + } + + return result; + } + + private createRequest( + workerIndex: number, + callback: RenderCallback, + importer: SyncImporter | AsyncImporter | (SyncImporter | AsyncImporter)[] | undefined, + ): RenderRequest { + return { + id: this.idCounter++, + workerIndex, + callback, + importers: !importer || Array.isArray(importer) ? importer : [importer], + }; + } +} diff --git a/packages/angular_devkit/build_angular/src/sass/sass-service.ts b/packages/angular_devkit/build_angular/src/sass/sass-service.ts new file mode 100644 index 000000000000..abdf6e76aa68 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/sass/sass-service.ts @@ -0,0 +1,317 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { dirname, join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { MessageChannel, Worker } from 'node:worker_threads'; +import { + CompileResult, + Exception, + FileImporter, + Importer, + Logger, + SourceSpan, + StringOptionsWithImporter, + StringOptionsWithoutImporter, +} from 'sass'; +import { maxWorkers } from '../utils/environment-options'; + +/** + * The maximum number of Workers that will be created to execute render requests. + */ +const MAX_RENDER_WORKERS = maxWorkers; + +/** + * The callback type for the `dart-sass` asynchronous render function. + */ +type RenderCallback = (error?: Exception, result?: CompileResult) => void; + +type FileImporterOptions = Parameters[1]; + +export interface FileImporterWithRequestContextOptions extends FileImporterOptions { + /** + * This is a custom option and is required as SASS does not provide context from which the file is being resolved. + * This breaks Yarn PNP as transitive deps cannot be resolved from the workspace root. + * + * Workaround until https://github.com/sass/sass/issues/3247 is addressed. + */ + previousResolvedModules?: Set; +} + +/** + * An object containing the contextual information for a specific render request. + */ +interface RenderRequest { + id: number; + workerIndex: number; + callback: RenderCallback; + logger?: Logger; + importers?: Importers[]; + previousResolvedModules?: Set; +} + +/** + * All available importer types. + */ +type Importers = + | Importer<'sync'> + | Importer<'async'> + | FileImporter<'sync'> + | FileImporter<'async'>; + +/** + * A response from the Sass render Worker containing the result of the operation. + */ +interface RenderResponseMessage { + id: number; + error?: Exception; + result?: Omit & { loadedUrls: string[] }; + warnings?: { + message: string; + deprecation: boolean; + stack?: string; + span?: Omit & { url?: string }; + }[]; +} + +/** + * A Sass renderer implementation that provides an interface that can be used by Webpack's + * `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering + * with the `dart-sass` package. The `dart-sass` synchronous render function is used within + * the worker which can be up to two times faster than the asynchronous variant. + */ +export class SassWorkerImplementation { + private readonly workers: Worker[] = []; + private readonly availableWorkers: number[] = []; + private readonly requests = new Map(); + private readonly workerPath = join(__dirname, './worker.js'); + private idCounter = 1; + private nextWorkerIndex = 0; + + constructor(private rebase = false) {} + + /** + * Provides information about the Sass implementation. + * This mimics enough of the `dart-sass` value to be used with the `sass-loader`. + */ + get info(): string { + return 'dart-sass\tworker'; + } + + /** + * The synchronous render function is not used by the `sass-loader`. + */ + compileString(): never { + throw new Error('Sass compileString is not supported.'); + } + + /** + * Asynchronously request a Sass stylesheet to be renderered. + * + * @param source The contents to compile. + * @param options The `dart-sass` options to use when rendering the stylesheet. + */ + compileStringAsync( + source: string, + options: StringOptionsWithImporter<'async'> | StringOptionsWithoutImporter<'async'>, + ): Promise { + // The `functions`, `logger` and `importer` options are JavaScript functions that cannot be transferred. + // If any additional function options are added in the future, they must be excluded as well. + const { functions, importers, url, logger, ...serializableOptions } = options; + + // The CLI's configuration does not use or expose the ability to defined custom Sass functions + if (functions && Object.keys(functions).length > 0) { + throw new Error('Sass custom functions are not supported.'); + } + + return new Promise((resolve, reject) => { + let workerIndex = this.availableWorkers.pop(); + if (workerIndex === undefined) { + if (this.workers.length < MAX_RENDER_WORKERS) { + workerIndex = this.workers.length; + this.workers.push(this.createWorker()); + } else { + workerIndex = this.nextWorkerIndex++; + if (this.nextWorkerIndex >= this.workers.length) { + this.nextWorkerIndex = 0; + } + } + } + + const callback: RenderCallback = (error, result) => { + if (error) { + const url = error.span?.url as string | undefined; + if (url) { + error.span.url = pathToFileURL(url); + } + + reject(error); + + return; + } + + if (!result) { + reject(new Error('No result.')); + + return; + } + + resolve(result); + }; + + const request = this.createRequest(workerIndex, callback, logger, importers); + this.requests.set(request.id, request); + + this.workers[workerIndex].postMessage({ + id: request.id, + source, + hasImporter: !!importers?.length, + hasLogger: !!logger, + rebase: this.rebase, + options: { + ...serializableOptions, + // URL is not serializable so to convert to string here and back to URL in the worker. + url: url ? fileURLToPath(url) : undefined, + }, + }); + }); + } + + /** + * Shutdown the Sass render worker. + * Executing this method will stop any pending render requests. + */ + close(): void { + for (const worker of this.workers) { + try { + void worker.terminate(); + } catch {} + } + this.requests.clear(); + } + + private createWorker(): Worker { + const { port1: mainImporterPort, port2: workerImporterPort } = new MessageChannel(); + const importerSignal = new Int32Array(new SharedArrayBuffer(4)); + + const worker = new Worker(this.workerPath, { + workerData: { workerImporterPort, importerSignal }, + transferList: [workerImporterPort], + }); + + worker.on('message', (response: RenderResponseMessage) => { + const request = this.requests.get(response.id); + if (!request) { + return; + } + + this.requests.delete(response.id); + this.availableWorkers.push(request.workerIndex); + + if (response.warnings && request.logger?.warn) { + for (const { message, span, ...options } of response.warnings) { + request.logger.warn(message, { + ...options, + span: span && { + ...span, + url: span.url ? pathToFileURL(span.url) : undefined, + }, + }); + } + } + + if (response.result) { + request.callback(undefined, { + ...response.result, + // URL is not serializable so in the worker we convert to string and here back to URL. + loadedUrls: response.result.loadedUrls.map((p) => pathToFileURL(p)), + }); + } else { + request.callback(response.error); + } + }); + + mainImporterPort.on( + 'message', + ({ id, url, options }: { id: number; url: string; options: FileImporterOptions }) => { + const request = this.requests.get(id); + if (!request?.importers) { + mainImporterPort.postMessage(null); + Atomics.store(importerSignal, 0, 1); + Atomics.notify(importerSignal, 0); + + return; + } + + this.processImporters(request.importers, url, { + ...options, + previousResolvedModules: request.previousResolvedModules, + }) + .then((result) => { + if (result) { + request.previousResolvedModules ??= new Set(); + request.previousResolvedModules.add(dirname(result)); + } + + mainImporterPort.postMessage(result); + }) + .catch((error) => { + mainImporterPort.postMessage(error); + }) + .finally(() => { + Atomics.store(importerSignal, 0, 1); + Atomics.notify(importerSignal, 0); + }); + }, + ); + + mainImporterPort.unref(); + + return worker; + } + + private async processImporters( + importers: Iterable, + url: string, + options: FileImporterWithRequestContextOptions, + ): Promise { + for (const importer of importers) { + if (this.isImporter(importer)) { + // Importer + throw new Error('Only File Importers are supported.'); + } + + // File importer (Can be sync or aync). + const result = await importer.findFileUrl(url, options); + if (result) { + return fileURLToPath(result); + } + } + + return null; + } + + private createRequest( + workerIndex: number, + callback: RenderCallback, + logger: Logger | undefined, + importers: Importers[] | undefined, + ): RenderRequest { + return { + id: this.idCounter++, + workerIndex, + callback, + logger, + importers, + }; + } + + private isImporter(value: Importers): value is Importer { + return 'canonicalize' in value && 'load' in value; + } +} diff --git a/packages/angular_devkit/build_angular/src/sass/worker-legacy.ts b/packages/angular_devkit/build_angular/src/sass/worker-legacy.ts new file mode 100644 index 000000000000..bcd978b3258b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/sass/worker-legacy.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { ImporterResult, LegacyOptions as Options, renderSync } from 'sass'; +import { MessagePort, parentPort, receiveMessageOnPort, workerData } from 'worker_threads'; + +/** + * A request to render a Sass stylesheet using the supplied options. + */ +interface RenderRequestMessage { + /** + * The unique request identifier that links the render action with a callback and optional + * importer on the main thread. + */ + id: number; + /** + * The Sass options to provide to the `dart-sass` render function. + */ + options: Options<'sync'>; + /** + * Indicates the request has a custom importer function on the main thread. + */ + hasImporter: boolean; +} + +if (!parentPort || !workerData) { + throw new Error('Sass worker must be executed as a Worker.'); +} + +// The importer variables are used to proxy import requests to the main thread +const { workerImporterPort, importerSignal } = workerData as { + workerImporterPort: MessagePort; + importerSignal: Int32Array; +}; + +parentPort.on('message', ({ id, hasImporter, options }: RenderRequestMessage) => { + try { + if (hasImporter) { + // When a custom importer function is present, the importer request must be proxied + // back to the main thread where it can be executed. + // This process must be synchronous from the perspective of dart-sass. The `Atomics` + // functions combined with the shared memory `importSignal` and the Node.js + // `receiveMessageOnPort` function are used to ensure synchronous behavior. + options.importer = function (url, prev) { + Atomics.store(importerSignal, 0, 0); + const { fromImport } = this; + workerImporterPort.postMessage({ id, url, prev, fromImport }); + Atomics.wait(importerSignal, 0, 0); + + return receiveMessageOnPort(workerImporterPort)?.message as ImporterResult; + }; + } + + // The synchronous Sass render function can be up to two times faster than the async variant + const result = renderSync(options); + + parentPort?.postMessage({ id, result }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + // Needed because V8 will only serialize the message and stack properties of an Error instance. + const { formatted, file, line, column, message, stack } = error; + parentPort?.postMessage({ id, error: { formatted, file, line, column, message, stack } }); + } +}); diff --git a/packages/angular_devkit/build_angular/src/sass/worker.ts b/packages/angular_devkit/build_angular/src/sass/worker.ts new file mode 100644 index 000000000000..160ccf3f89b0 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/sass/worker.ts @@ -0,0 +1,235 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import mergeSourceMaps, { RawSourceMap } from '@ampproject/remapping'; +import { Dirent } from 'node:fs'; +import { dirname } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { MessagePort, parentPort, receiveMessageOnPort, workerData } from 'node:worker_threads'; +import { + Exception, + FileImporter, + SourceSpan, + StringOptionsWithImporter, + compileString, +} from 'sass'; +import { + LoadPathsUrlRebasingImporter, + ModuleUrlRebasingImporter, + RelativeUrlRebasingImporter, + sassBindWorkaround, +} from './rebasing-importer'; + +/** + * A request to render a Sass stylesheet using the supplied options. + */ +interface RenderRequestMessage { + /** + * The unique request identifier that links the render action with a callback and optional + * importer on the main thread. + */ + id: number; + /** + * The contents to compile. + */ + source: string; + /** + * The Sass options to provide to the `dart-sass` compile function. + */ + options: Omit, 'url'> & { url: string }; + /** + * Indicates the request has a custom importer function on the main thread. + */ + hasImporter: boolean; + /** + * Indicates the request has a custom logger for warning messages. + */ + hasLogger: boolean; + /** + * Indicates paths within url() CSS functions should be rebased. + */ + rebase: boolean; +} + +if (!parentPort || !workerData) { + throw new Error('Sass worker must be executed as a Worker.'); +} + +// The importer variables are used to proxy import requests to the main thread +const { workerImporterPort, importerSignal } = workerData as { + workerImporterPort: MessagePort; + importerSignal: Int32Array; +}; + +parentPort.on('message', (message: RenderRequestMessage) => { + if (!parentPort) { + throw new Error('"parentPort" is not defined. Sass worker must be executed as a Worker.'); + } + + const { id, hasImporter, hasLogger, source, options, rebase } = message; + const entryDirectory = dirname(options.url); + let warnings: + | { + message: string; + deprecation: boolean; + stack?: string; + span?: Omit & { url?: string }; + }[] + | undefined; + try { + const directoryCache = new Map(); + const rebaseSourceMaps = options.sourceMap ? new Map() : undefined; + if (hasImporter) { + // When a custom importer function is present, the importer request must be proxied + // back to the main thread where it can be executed. + // This process must be synchronous from the perspective of dart-sass. The `Atomics` + // functions combined with the shared memory `importSignal` and the Node.js + // `receiveMessageOnPort` function are used to ensure synchronous behavior. + const proxyImporter: FileImporter<'sync'> = { + findFileUrl: (url, options) => { + Atomics.store(importerSignal, 0, 0); + workerImporterPort.postMessage({ id, url, options }); + Atomics.wait(importerSignal, 0, 0); + + const result = receiveMessageOnPort(workerImporterPort)?.message as string | null; + + return result ? pathToFileURL(result) : null; + }, + }; + options.importers = [ + rebase + ? sassBindWorkaround( + new ModuleUrlRebasingImporter( + entryDirectory, + directoryCache, + rebaseSourceMaps, + proxyImporter.findFileUrl, + ), + ) + : proxyImporter, + ]; + } + + if (rebase && options.loadPaths?.length) { + options.importers ??= []; + options.importers.push( + sassBindWorkaround( + new LoadPathsUrlRebasingImporter( + entryDirectory, + directoryCache, + rebaseSourceMaps, + options.loadPaths, + ), + ), + ); + options.loadPaths = undefined; + } + + let relativeImporter; + if (rebase) { + relativeImporter = sassBindWorkaround( + new RelativeUrlRebasingImporter(entryDirectory, directoryCache, rebaseSourceMaps), + ); + } + + // The synchronous Sass render function can be up to two times faster than the async variant + const result = compileString(source, { + ...options, + // URL is not serializable so to convert to string in the parent and back to URL here. + url: pathToFileURL(options.url), + // The `importer` option (singular) handles relative imports + importer: relativeImporter, + logger: hasLogger + ? { + warn(message, { deprecation, span, stack }) { + warnings ??= []; + warnings.push({ + message, + deprecation, + stack, + span: span && convertSourceSpan(span), + }); + }, + } + : undefined, + }); + + if (result.sourceMap && rebaseSourceMaps?.size) { + // Merge the intermediate rebasing source maps into the final Sass generated source map. + // Casting is required due to small but compatible differences in typings between the packages. + result.sourceMap = mergeSourceMaps( + result.sourceMap as unknown as RawSourceMap, + // To prevent an infinite lookup loop, skip getting the source when the rebasing source map + // is referencing its original self. + (file, context) => (file !== context.importer ? rebaseSourceMaps.get(file) : null), + ) as unknown as typeof result.sourceMap; + } + + parentPort.postMessage({ + id, + warnings, + result: { + ...result, + // URL is not serializable so to convert to string here and back to URL in the parent. + loadedUrls: result.loadedUrls.map((p) => fileURLToPath(p)), + }, + }); + } catch (error) { + // Needed because V8 will only serialize the message and stack properties of an Error instance. + if (error instanceof Exception) { + const { span, message, stack, sassMessage, sassStack } = error; + parentPort.postMessage({ + id, + warnings, + error: { + span: convertSourceSpan(span), + message, + stack, + sassMessage, + sassStack, + }, + }); + } else if (error instanceof Error) { + const { message, stack } = error; + parentPort.postMessage({ id, warnings, error: { message, stack } }); + } else { + parentPort.postMessage({ + id, + warnings, + error: { message: 'An unknown error has occurred.' }, + }); + } + } +}); + +/** + * Converts a Sass SourceSpan object into a serializable form. + * The SourceSpan object contains a URL property which must be converted into a string. + * Also, most of the interface's properties are get accessors and are not automatically + * serialized when sent back from the worker. + * + * @param span The Sass SourceSpan object to convert. + * @returns A serializable form of the SourceSpan object. + */ +function convertSourceSpan(span: SourceSpan): Omit & { url?: string } { + return { + text: span.text, + context: span.context, + end: { + column: span.end.column, + offset: span.end.offset, + line: span.end.line, + }, + start: { + column: span.start.column, + offset: span.start.offset, + line: span.start.line, + }, + url: span.url ? fileURLToPath(span.url) : undefined, + }; +} diff --git a/packages/angular_devkit/build_angular/src/server/base_spec.ts b/packages/angular_devkit/build_angular/src/server/base_spec.ts deleted file mode 100644 index 913356bb77f8..000000000000 --- a/packages/angular_devkit/build_angular/src/server/base_spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { ServerBuilderOutput } from '@angular-devkit/build-angular'; -import { getSystemPath, join, normalize, virtualFs } from '@angular-devkit/core'; -import { take, tap } from 'rxjs/operators'; -import { createArchitect, host } from '../test-utils'; - - -describe('Server Builder', () => { - const target = { project: 'app', target: 'server' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - const outputPath = normalize('dist-server'); - - it('works (base)', async () => { - const run = await architect.scheduleTarget(target); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - const fileName = join(outputPath, 'main.js'); - const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); - expect(content).toMatch(/AppServerModule\.ɵmod/); - await run.stop(); - }); - - it('should not emit polyfills', async () => { - const run = await architect.scheduleTarget(target); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - expect(host.fileMatchExists(getSystemPath(outputPath), /polyfills/)).not.toBeDefined(); - expect(host.fileMatchExists(getSystemPath(outputPath), /main/)).toBeDefined(); - - await run.stop(); - }); - - it('should not emit polyfills when ES5 support is needed', async () => { - // the below is needed because of different code paths - // for polyfills if differential loading is needed - host.writeMultipleFiles({ - '.browserslistrc': 'IE 10', - }); - - const run = await architect.scheduleTarget(target); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - expect(host.fileMatchExists(getSystemPath(outputPath), /polyfills/)).not.toBeDefined(); - expect(host.fileMatchExists(getSystemPath(outputPath), /main/)).toBeDefined(); - - await run.stop(); - }); - - it('supports sourcemaps', async () => { - const overrides = { sourceMap: true }; - const run = await architect.scheduleTarget(target, overrides); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBeTruthy(); - await run.stop(); - }); - - it('supports scripts only sourcemaps', async () => { - host.writeMultipleFiles({ - 'src/app/app.component.css': `p { color: red; }`, - }); - - const run = await architect.scheduleTarget(target, { - bundleDependencies: false, - sourceMap: { - styles: false, - scripts: true, - }, - }); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); - - const scriptContent = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'main.js')), - ); - expect(scriptContent).toContain('sourceMappingURL=main.js.map'); - expect(scriptContent).not.toContain('sourceMappingURL=data:application/json'); - - await run.stop(); - }); - - it('supports component styles sourcemaps', async () => { - const overrides = { - bundleDependencies: false, - sourceMap: { - styles: true, - scripts: true, - }, - }; - - host.writeMultipleFiles({ - 'src/app/app.component.css': `p { color: red; }`, - }); - - const run = await architect.scheduleTarget(target, overrides); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true); - - const scriptContent = virtualFs.fileBufferToString( - host.scopedSync().read(join(outputPath, 'main.js')), - ); - expect(scriptContent).toContain('sourceMappingURL=main.js.map'); - expect(scriptContent).toContain('sourceMappingURL=data:application/json'); - - await run.stop(); - }); - - it('runs watch mode', async () => { - const overrides = { watch: true }; - - const run = await architect.scheduleTarget(target, overrides); - - await run.output.pipe( - tap((buildEvent) => { - expect(buildEvent.success).toBe(true); - - const fileName = join(outputPath, 'main.js'); - const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); - expect(content).toMatch(/AppServerModule\.ɵmod/); - }), - take(1), - ).toPromise(); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/server/external_dependencies_spec.ts b/packages/angular_devkit/build_angular/src/server/external_dependencies_spec.ts deleted file mode 100644 index e8ddba737bfa..000000000000 --- a/packages/angular_devkit/build_angular/src/server/external_dependencies_spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Architect } from '@angular-devkit/architect'; -import { ServerBuilderOutput } from '@angular-devkit/build-angular'; -import { join, normalize, virtualFs } from '@angular-devkit/core'; -import { createArchitect, host } from '../test-utils'; - - -describe('Server Builder external dependencies', () => { - const target = { project: 'app', target: 'server' }; - let architect: Architect; - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - const outputPath = normalize('dist-server'); - - it('should not bundle an given external dependency', async () => { - const overrides = { - bundleDependencies: true, - externalDependencies: [ - '@angular/core', - ], - }; - - const run = await architect.scheduleTarget(target, overrides); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - const fileName = join(outputPath, 'main.js'); - const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); - expect(content).toContain('require("@angular/core")'); - expect(content).not.toContain('require("@angular/common")'); - - await run.stop(); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/server/index.ts b/packages/angular_devkit/build_angular/src/server/index.ts deleted file mode 100644 index 6c0934943164..000000000000 --- a/packages/angular_devkit/build_angular/src/server/index.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; -import { runWebpack } from '@angular-devkit/build-webpack'; -import { json, tags } from '@angular-devkit/core'; -import * as path from 'path'; -import { Observable, from } from 'rxjs'; -import { concatMap, map } from 'rxjs/operators'; -import { ScriptTarget } from 'typescript'; -import * as webpack from 'webpack'; -import { ExecutionTransformer } from '../transforms'; -import { NormalizedBrowserBuilderSchema, deleteOutputDir } from '../utils'; -import { i18nInlineEmittedFiles } from '../utils/i18n-inlining'; -import { I18nOptions } from '../utils/i18n-options'; -import { ensureOutputPaths } from '../utils/output-paths'; -import { readTsconfig } from '../utils/read-tsconfig'; -import { assertCompatibleAngularVersion } from '../utils/version'; -import { generateI18nBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config'; -import { - getAotConfig, - getCommonConfig, - getServerConfig, - getStatsConfig, - getStylesConfig, -} from '../webpack/configs'; -import { JsonCompilationStats, webpackStatsLogger } from '../webpack/utils/stats'; -import { Schema as ServerBuilderOptions } from './schema'; - -/** - * @experimental Direct usage of this type is considered experimental. - */ -export type ServerBuilderOutput = json.JsonObject & BuilderOutput & { - baseOutputPath: string; - outputPaths: string[]; - /** - * @deprecated in version 9. Use 'outputPaths' instead. - */ - outputPath: string; -}; - -export { ServerBuilderOptions }; - -/** - * @experimental Direct usage of this function is considered experimental. - */ -export function execute( - options: ServerBuilderOptions, - context: BuilderContext, - transforms: { - webpackConfiguration?: ExecutionTransformer; - } = {}, -): Observable { - const root = context.workspaceRoot; - - // Check Angular version. - assertCompatibleAngularVersion(root, context.logger); - - const tsConfig = readTsconfig(options.tsConfig, root); - const target = tsConfig.options.target || ScriptTarget.ES5; - const baseOutputPath = path.resolve(root, options.outputPath); - let outputPaths: undefined | Map; - - if (typeof options.bundleDependencies === 'string') { - options.bundleDependencies = options.bundleDependencies === 'all'; - context.logger.warn(`Option 'bundleDependencies' string value is deprecated since version 9. Use a boolean value instead.`); - } - - if (!options.bundleDependencies && tsConfig.options.enableIvy) { - // tslint:disable-next-line: no-implicit-dependencies - const { __processed_by_ivy_ngcc__, main = '' } = require('@angular/core/package.json'); - if ( - !__processed_by_ivy_ngcc__ || - !__processed_by_ivy_ngcc__.main || - (main as string).includes('__ivy_ngcc__') - ) { - context.logger.warn(tags.stripIndent` - Warning: Turning off 'bundleDependencies' with Ivy may result in undefined behaviour - unless 'node_modules' are transformed using the standalone Angular compatibility compiler (NGCC). - See: https://angular.io/guide/ivy#ivy-and-universal-app-shell - `); - } - } - - return from(initialize(options, context, transforms.webpackConfiguration)).pipe( - concatMap(({ config, i18n }) => { - return runWebpack(config, context, { - webpackFactory: require('webpack') as typeof webpack, - logging: (stats, config) => { - if (options.verbose) { - context.logger.info(stats.toString(config.stats)); - } - }, - }).pipe( - concatMap(async output => { - const { emittedFiles = [], outputPath, webpackStats } = output; - if (!webpackStats) { - throw new Error('Webpack stats build result is required.'); - } - - let success = output.success; - if (success && i18n.shouldInline) { - outputPaths = ensureOutputPaths(baseOutputPath, i18n); - - success = await i18nInlineEmittedFiles( - context, - emittedFiles, - i18n, - baseOutputPath, - Array.from(outputPaths.values()), - [], - outputPath, - target <= ScriptTarget.ES5, - options.i18nMissingTranslation, - ); - } - - webpackStatsLogger(context.logger, webpackStats as JsonCompilationStats, config); - - return { ...output, success }; - }), - ); - }), - map(output => { - if (!output.success) { - return output as ServerBuilderOutput; - } - - return { - ...output, - baseOutputPath, - outputPath: baseOutputPath, - outputPaths: outputPaths || [baseOutputPath], - } as ServerBuilderOutput; - }), - ); -} - -export default createBuilder( - execute, -); - -async function initialize( - options: ServerBuilderOptions, - context: BuilderContext, - webpackConfigurationTransform?: ExecutionTransformer, -): Promise<{ - config: webpack.Configuration; - i18n: I18nOptions; -}> { - const originalOutputPath = options.outputPath; - const { config, i18n } = await generateI18nBrowserWebpackConfigFromContext( - { - ...options, - buildOptimizer: false, - aot: true, - platform: 'server', - } as NormalizedBrowserBuilderSchema, - context, - wco => [ - getCommonConfig(wco), - getServerConfig(wco), - getStylesConfig(wco), - getStatsConfig(wco), - getAotConfig(wco), - ], - ); - - let transformedConfig; - if (webpackConfigurationTransform) { - transformedConfig = await webpackConfigurationTransform(config); - } - - if (options.deleteOutputPath) { - deleteOutputDir( - context.workspaceRoot, - originalOutputPath, - ); - } - - return { config: transformedConfig || config, i18n }; -} diff --git a/packages/angular_devkit/build_angular/src/server/resources-output-path_spec.ts b/packages/angular_devkit/build_angular/src/server/resources-output-path_spec.ts deleted file mode 100644 index 643f1b272cc9..000000000000 --- a/packages/angular_devkit/build_angular/src/server/resources-output-path_spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -import { Architect } from '@angular-devkit/architect'; -import { ServerBuilderOutput } from '@angular-devkit/build-angular'; -import { join, normalize, virtualFs } from '@angular-devkit/core'; -import { createArchitect, host } from '../test-utils'; - - -describe('Server Builder styles resources output path (No emit assets)', () => { - function writeFiles() { - host.copyFile('src/spectrum.png', './src/assets/component-img-relative.png'); - host.copyFile('src/spectrum.png', './src/assets/component-img-absolute.png'); - host.writeMultipleFiles({ - 'src/app/app.component.css': ` - h3 { background: url('https://github.com/assets/component-img-absolute.png'); } - h4 { background: url('https://github.com/assets/component-img-relative.png'); } - `, - }); - } - - const target = { project: 'app', target: 'server' }; - let architect: Architect; - - const outputPath = normalize('dist-server'); - - beforeEach(async () => { - await host.initialize().toPromise(); - architect = (await createArchitect(host.root())).architect; - }); - afterEach(async () => host.restore().toPromise()); - - it(`supports resourcesOutputPath in resource urls`, async () => { - writeFiles(); - const overrides = { - resourcesOutputPath: 'out-assets', - }; - - const run = await architect.scheduleTarget(target, overrides); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - const fileName = join(outputPath, 'main.js'); - - const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); - - expect(content).toContain(`url('https://github.com/assets/component-img-absolute.png')`); - expect(content).toContain(`url('https://github.com/out-assets/component-img-relative.png')`); - - expect(host.scopedSync().exists(normalize(`${outputPath}/out-assets/component-img-relative.png`))) - .toBe(false); - }); - - it(`supports blank resourcesOutputPath`, async () => { - writeFiles(); - - const run = await architect.scheduleTarget(target); - const output = await run.result as ServerBuilderOutput; - expect(output.success).toBe(true); - - const fileName = join(outputPath, 'main.js'); - const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); - - expect(content).toContain(`url('https://github.com/assets/component-img-absolute.png')`); - expect(content).toContain(`url('https://github.com/component-img-relative.png')`); - - expect(host.scopedSync().exists(normalize(`${outputPath}/component-img-relative.png`))) - .toBe(false); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/server/schema.json b/packages/angular_devkit/build_angular/src/server/schema.json deleted file mode 100644 index aa4c01521026..000000000000 --- a/packages/angular_devkit/build_angular/src/server/schema.json +++ /dev/null @@ -1,286 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "BuildAngularWebpackServerSchema", - "title": "Universal Target", - "type": "object", - "properties": { - "main": { - "type": "string", - "description": "The name of the main entry-point file." - }, - "tsConfig": { - "type": "string", - "default": "tsconfig.app.json", - "description": "The name of the TypeScript configuration file." - }, - "inlineStyleLanguage": { - "description": "The stylesheet language to use for the application's inline component styles.", - "type": "string", - "default": "css", - "enum": [ - "css", - "less", - "sass", - "scss" - ] - }, - "stylePreprocessorOptions": { - "description": "Options to pass to style preprocessors", - "type": "object", - "properties": { - "includePaths": { - "description": "Paths to include. Paths will be resolved to project root.", - "type": "array", - "items": { - "type": "string" - }, - "default": [] - } - }, - "additionalProperties": false - }, - "optimization": { - "description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking and dead-code elimination. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.", - "x-user-analytics": 16, - "default": false, - "oneOf": [ - { - "type": "object", - "properties": { - "scripts": { - "type": "boolean", - "description": "Enables optimization of the scripts output.", - "default": true - }, - "styles": { - "type": "boolean", - "description": "Enables optimization of the styles output.", - "default": true - } - }, - "additionalProperties": false - }, - { - "type": "boolean" - } - ] - }, - "fileReplacements": { - "description": "Replace compilation source files with other compilation source files in the build.", - "type": "array", - "items": { - "$ref": "#/definitions/fileReplacement" - }, - "default": [] - }, - "outputPath": { - "type": "string", - "description": "Path where output will be placed." - }, - "resourcesOutputPath": { - "type": "string", - "description": "The path where style resources will be placed, relative to outputPath.", - "default": "" - }, - "sourceMap": { - "description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.", - "default": true, - "oneOf": [ - { - "type": "object", - "properties": { - "scripts": { - "type": "boolean", - "description": "Output source maps for all scripts.", - "default": true - }, - "styles": { - "type": "boolean", - "description": "Output source maps for all styles.", - "default": true - }, - "hidden": { - "type": "boolean", - "description": "Output source maps used for error reporting tools.", - "default": false - }, - "vendor": { - "type": "boolean", - "description": "Resolve vendor packages source maps.", - "default": false - } - }, - "additionalProperties": false - }, - { - "type": "boolean" - } - ] - }, - "deployUrl": { - "type": "string", - "description": "URL where files will be deployed." - }, - "verbose": { - "type": "boolean", - "description": "Adds more details to output logging.", - "default": false - }, - "progress": { - "type": "boolean", - "description": "Log progress to the console while building.", - "default": true - }, - "i18nMissingTranslation": { - "type": "string", - "description": "How to handle missing translations for i18n.", - "enum": ["warning", "error", "ignore"], - "default": "warning" - }, - "localize": { - "description": "Translate the bundles in one or more locales.", - "oneOf": [ - { - "type": "boolean", - "description": "Translate all locales." - }, - { - "type": "array", - "description": "List of locales ID's to translate.", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$" - } - } - ] - }, - "outputHashing": { - "type": "string", - "description": "Define the output filename cache-busting hashing mode.", - "default": "none", - "enum": [ - "none", - "all", - "media", - "bundles" - ] - }, - "deleteOutputPath": { - "type": "boolean", - "description": "Delete the output path before building.", - "default": true - }, - "preserveSymlinks": { - "type": "boolean", - "description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set." - }, - "extractLicenses": { - "type": "boolean", - "description": "Extract all licenses in a separate file, in the case of production builds only.", - "default": true - }, - "showCircularDependencies": { - "type": "boolean", - "description": "Show circular dependency warnings on builds.", - "default": false, - "x-deprecated": "The recommended method to detect circular dependencies in project code is to use a either a lint rule or other external tooling." - }, - "namedChunks": { - "type": "boolean", - "description": "Use file name for lazy loaded chunks.", - "default": true - }, - "bundleDependencies": { - "description": "Which external dependencies to bundle into the bundle. By default, all of node_modules will be bundled.", - "default": true, - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "none", - "all" - ] - } - ] - }, - "externalDependencies": { - "description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.", - "type": "array", - "items": { - "type": "string" - }, - "default": [] - }, - "statsJson": { - "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", - "default": false - }, - "forkTypeChecker": { - "type": "boolean", - "description": "Run the TypeScript type checker in a forked process.", - "default": true - }, - "watch": { - "type": "boolean", - "description": "Run build when files change.", - "default": false - }, - "poll": { - "type": "number", - "description": "Enable and define the file watching poll time period in milliseconds." - } - }, - "additionalProperties": false, - "required": [ - "outputPath", - "main", - "tsConfig" - ], - "definitions": { - "fileReplacement": { - "oneOf": [ - { - "type": "object", - "properties": { - "src": { - "type": "string", - "pattern": "\\.(([cm]?j|t)sx?|json)$" - }, - "replaceWith": { - "type": "string", - "pattern": "\\.(([cm]?j|t)sx?|json)$" - } - }, - "additionalProperties": false, - "required": [ - "src", - "replaceWith" - ] - }, - { - "type": "object", - "properties": { - "replace": { - "type": "string", - "pattern": "\\.(([cm]?j|t)sx?|json)$" - }, - "with": { - "type": "string", - "pattern": "\\.(([cm]?j|t)sx?|json)$" - } - }, - "additionalProperties": false, - "required": [ - "replace", - "with" - ] - } - ] - } - } -} diff --git a/packages/angular_devkit/build_angular/src/testing/builder-harness.ts b/packages/angular_devkit/build_angular/src/testing/builder-harness.ts index ce3782ec7111..3bfbb686244e 100644 --- a/packages/angular_devkit/build_angular/src/testing/builder-harness.ts +++ b/packages/angular_devkit/build_angular/src/testing/builder-harness.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderContext, BuilderHandlerFn, @@ -20,7 +21,7 @@ import { } from '@angular-devkit/architect'; import { WorkspaceHost } from '@angular-devkit/architect/node'; import { TestProjectHost } from '@angular-devkit/architect/testing'; -import { analytics, getSystemPath, join, json, logging, normalize } from '@angular-devkit/core'; +import { getSystemPath, join, json, logging, normalize } from '@angular-devkit/core'; import { Observable, Subject, from as observableFrom, of as observableOf } from 'rxjs'; import { catchError, finalize, first, map, mergeMap, shareReplay } from 'rxjs/operators'; import { BuilderWatcherFactory, WatcherNotifier } from './file-watching'; @@ -38,16 +39,32 @@ export interface BuilderHarnessExecutionOptions { useNativeFileWatching: boolean; } +/** + * The default set of fields provided to all builders executed via the BuilderHarness. + * `root` and `sourceRoot` are required for most Angular builders to function. + * `cli.cache.enabled` set to false provides improved test isolation guarantees by disabling + * the Webpack caching. + */ +const DEFAULT_PROJECT_METADATA = { + root: '.', + sourceRoot: 'src', + cli: { + cache: { + enabled: false, + }, + }, +}; + export class BuilderHarness { private readonly builderInfo: BuilderInfo; private schemaRegistry = new json.schema.CoreSchemaRegistry(); private projectName = 'test'; - private projectMetadata: Record = { root: '.', sourceRoot: 'src' }; + private projectMetadata: Record = DEFAULT_PROJECT_METADATA; private targetName?: string; private options = new Map(); private builderTargets = new Map< string, - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any { handler: BuilderHandlerFn; info: BuilderInfo; options: json.JsonObject } >(); private watcherNotifier?: WatcherNotifier; @@ -96,7 +113,7 @@ export class BuilderHarness { return this; } - withBuilderTarget( + withBuilderTarget( target: string, handler: BuilderHandlerFn, options?: O, @@ -161,12 +178,12 @@ export class BuilderHarness { getOptions: async (project, target, configuration) => { this.validateProjectName(project); if (target === this.targetName) { - return this.options.get(configuration ?? null) ?? {}; + return (this.options.get(configuration ?? null) ?? {}) as json.JsonObject; } else if (configuration !== undefined) { // Harness builder targets currently do not support configurations return {}; } else { - return (this.builderTargets.get(target)?.options as json.JsonObject) || {}; + return (this.builderTargets.get(target)?.options || {}) as json.JsonObject; } }, hasTarget: async (project, target) => { @@ -222,9 +239,9 @@ export class BuilderHarness { map((buildResult) => ({ result: buildResult, error: undefined })), catchError((error) => { if (outputLogsOnException) { - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.error(logs.map((entry) => entry.message).join('\n')); - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.error(error); } @@ -232,7 +249,7 @@ export class BuilderHarness { }), map(({ result, error }) => { if (outputLogsOnFailure && result?.success === false && logs.length > 0) { - // tslint:disable-next-line: no-console + // eslint-disable-next-line no-console console.error(logs.map((entry) => entry.message).join('\n')); } @@ -246,6 +263,7 @@ export class BuilderHarness { this.watcherNotifier = undefined; for (const teardown of context.teardowns) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises teardown(); } }), @@ -316,9 +334,10 @@ export class BuilderHarness { } hasFileMatch(directory: string, pattern: RegExp): boolean { - return this.host.scopedSync() + return this.host + .scopedSync() .list(normalize(directory)) - .some(name => pattern.test(name)); + .some((name) => pattern.test(name)); } readFile(path: string): string { @@ -360,11 +379,6 @@ class HarnessBuilderContext implements BuilderContext { this.workspaceRoot = this.currentDirectory = basePath; } - get analytics(): analytics.Analytics { - // Can be undefined even though interface does not allow it - return (undefined as unknown) as analytics.Analytics; - } - addTeardown(teardown: () => Promise | void): void { this.teardowns.push(teardown); } @@ -444,7 +458,7 @@ class HarnessBuilderContext implements BuilderContext { options: json.JsonObject, builderName: string, ): Promise { - return (this.contextHost.validate(options, builderName) as unknown) as T; + return this.contextHost.validate(options, builderName) as unknown as T; } // Unused report methods diff --git a/packages/angular_devkit/build_angular/src/testing/builder-harness_spec.ts b/packages/angular_devkit/build_angular/src/testing/builder-harness_spec.ts index e82402f02d59..86d22e1808a7 100644 --- a/packages/angular_devkit/build_angular/src/testing/builder-harness_spec.ts +++ b/packages/angular_devkit/build_angular/src/testing/builder-harness_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { TestProjectHost } from '@angular-devkit/architect/testing'; import { BuilderHarness } from './builder-harness'; @@ -116,7 +117,7 @@ describe('BuilderHarness', () => { const mockHandler = jasmine.createSpy().and.returnValue({ success: true }); const harness = new BuilderHarness(async (_, context) => { - const run = await context.scheduleTarget({project: 'test', target: 'another' }); + const run = await context.scheduleTarget({ project: 'test', target: 'another' }); expect(await run.result).toEqual(jasmine.objectContaining({ success: true })); await run.stop(); diff --git a/packages/angular_devkit/build_angular/src/testing/file-watching.ts b/packages/angular_devkit/build_angular/src/testing/file-watching.ts index e47e6a19d5ff..a09c38dc8645 100644 --- a/packages/angular_devkit/build_angular/src/testing/file-watching.ts +++ b/packages/angular_devkit/build_angular/src/testing/file-watching.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderWatcherCallback, BuilderWatcherFactory, diff --git a/packages/angular_devkit/build_angular/src/testing/index.ts b/packages/angular_devkit/build_angular/src/testing/index.ts index 3300346cf5d4..51bcba582bbc 100644 --- a/packages/angular_devkit/build_angular/src/testing/index.ts +++ b/packages/angular_devkit/build_angular/src/testing/index.ts @@ -1,9 +1,10 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export { BuilderHarnessExecutionOptions, BuilderHarnessExecutionResult } from './builder-harness'; export { HarnessFileMatchers, describeBuilder } from './jasmine-helpers'; diff --git a/packages/angular_devkit/build_angular/src/testing/jasmine-helpers.ts b/packages/angular_devkit/build_angular/src/testing/jasmine-helpers.ts index e393fa756719..68d589c846dd 100644 --- a/packages/angular_devkit/build_angular/src/testing/jasmine-helpers.ts +++ b/packages/angular_devkit/build_angular/src/testing/jasmine-helpers.ts @@ -1,15 +1,16 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderHandlerFn } from '@angular-devkit/architect'; import { json } from '@angular-devkit/core'; import { readFileSync } from 'fs'; -import { host } from '../test-utils'; import { BuilderHarness } from './builder-harness'; +import { host } from './test-utils'; const optionSchemaCache = new Map(); @@ -94,7 +95,7 @@ export function expectFile(path: string, harness: BuilderHarness): Harness try { return expect(harness.readFile(path)).withContext(`With file content for '${path}'`); } catch (e) { - if (e.code !== 'ENOENT') { + if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { throw e; } @@ -111,7 +112,7 @@ export function expectFile(path: string, harness: BuilderHarness): Harness `With file size for '${path}'`, ); } catch (e) { - if (e.code !== 'ENOENT') { + if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { throw e; } diff --git a/packages/angular_devkit/build_angular/src/test-utils.ts b/packages/angular_devkit/build_angular/src/testing/test-utils.ts similarity index 93% rename from packages/angular_devkit/build_angular/src/test-utils.ts rename to packages/angular_devkit/build_angular/src/testing/test-utils.ts index 282053d339e5..c83d76f8d9c3 100644 --- a/packages/angular_devkit/build_angular/src/test-utils.ts +++ b/packages/angular_devkit/build_angular/src/testing/test-utils.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect, BuilderOutput, ScheduleOptions, Target } from '@angular-devkit/architect'; import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing'; @@ -20,11 +21,10 @@ import { workspaces, } from '@angular-devkit/core'; - // Default timeout for large specs is 2.5 minutes. jasmine.DEFAULT_TIMEOUT_INTERVAL = 150000; -export const workspaceRoot = join(normalize(__dirname), `../test/hello-world-app/`); +export const workspaceRoot = join(normalize(__dirname), `../../test/hello-world-app/`); export const host = new TestProjectHost(workspaceRoot); export const outputPath: Path = normalize('dist'); @@ -83,8 +83,9 @@ export async function browserBuild( }; } - expect(output.outputPaths[0]).not.toBeUndefined(); - const outputPath = normalize(output.outputPaths[0]); + const [{ path }] = output.outputs; + expect(path).toBeTruthy(); + const outputPath = normalize(path); const fileNames = await host.list(outputPath).toPromise(); const files = fileNames.reduce((acc: { [name: string]: Promise }, path) => { @@ -102,7 +103,7 @@ export async function browserBuild( cache = host .read(join(outputPath, path)) .toPromise() - .then(content => virtualFs.fileBufferToString(content)); + .then((content) => virtualFs.fileBufferToString(content)); return cache; }, diff --git a/packages/angular_devkit/build_angular/src/transforms.ts b/packages/angular_devkit/build_angular/src/transforms.ts index c724aedab646..bb500c173c25 100644 --- a/packages/angular_devkit/build_angular/src/transforms.ts +++ b/packages/angular_devkit/build_angular/src/transforms.ts @@ -1,8 +1,9 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export type ExecutionTransformer = (input: T) => T | Promise; diff --git a/packages/angular_devkit/build_angular/src/tslint/index.ts b/packages/angular_devkit/build_angular/src/tslint/index.ts deleted file mode 100644 index 3f5f4fe9df3b..000000000000 --- a/packages/angular_devkit/build_angular/src/tslint/index.ts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; -import { json } from '@angular-devkit/core'; -import { readFileSync } from 'fs'; -import * as glob from 'glob'; -import { Minimatch } from 'minimatch'; -import * as path from 'path'; -import * as tslint from 'tslint'; -import { Program } from 'typescript'; -import { stripBom } from '../utils/strip-bom'; -import { Schema as RealTslintBuilderOptions } from './schema'; - - -type TslintBuilderOptions = RealTslintBuilderOptions & json.JsonObject; -interface LintResult extends tslint.LintResult { - fileNames: string[]; -} - -async function _run( - options: TslintBuilderOptions, - context: BuilderContext, -): Promise { - context.logger.warn( - `TSLint's support is discontinued and we're deprecating its support in Angular CLI.\n` + - 'To opt-in using the community driven ESLint builder, see: https://github.com/angular-eslint/angular-eslint#migrating-an-angular-cli-project-from-codelyzer-and-tslint.', - ); - - const systemRoot = context.workspaceRoot; - process.chdir(context.currentDirectory); - const projectName = (context.target && context.target.project) || ''; - - // Print formatter output only for non human-readable formats. - const printInfo = - ['prose', 'verbose', 'stylish'].includes(options.format || '') && !options.silent; - - context.reportStatus(`Linting ${JSON.stringify(projectName)}...`); - if (printInfo) { - context.logger.info(`Linting ${JSON.stringify(projectName)}...`); - } - - if (!options.tsConfig && options.typeCheck) { - throw new Error('A "project" must be specified to enable type checking.'); - } - - let tslint; - try { - tslint = await import('tslint'); - } catch { - throw new Error('Unable to find TSLint. Ensure TSLint is installed.'); - } - - const tslintConfigPath = options.tslintConfig - ? path.resolve(systemRoot, options.tslintConfig) - : null; - const Linter = tslint.Linter; - - let result: undefined | LintResult = undefined; - if (options.tsConfig) { - const tsConfigs = Array.isArray(options.tsConfig) ? options.tsConfig : [options.tsConfig]; - context.reportProgress(0, tsConfigs.length); - const allPrograms = tsConfigs.map(tsConfig => { - return Linter.createProgram(path.resolve(systemRoot, tsConfig)); - }); - - let i = 0; - for (const program of allPrograms) { - const partial = await _lint( - tslint, - systemRoot, - tslintConfigPath, - options, - program, - allPrograms, - ); - if (result === undefined) { - result = partial; - } else { - result.failures = result.failures - .filter(curr => { - return !partial.failures.some(prev => curr.equals(prev)); - }) - .concat(partial.failures); - - // we are not doing much with 'errorCount' and 'warningCount' - // apart from checking if they are greater than 0 thus no need to dedupe these. - result.errorCount += partial.errorCount; - result.warningCount += partial.warningCount; - result.fileNames = [...new Set([...result.fileNames, ...partial.fileNames])]; - - if (partial.fixes) { - result.fixes = result.fixes ? result.fixes.concat(partial.fixes) : partial.fixes; - } - } - - context.reportProgress(++i, allPrograms.length); - } - } else { - result = await _lint(tslint, systemRoot, tslintConfigPath, options); - } - - if (result == undefined) { - throw new Error('Invalid lint configuration. Nothing to lint.'); - } - - if (!options.silent) { - const Formatter = tslint.findFormatter(options.format || ''); - if (!Formatter) { - throw new Error(`Invalid lint format "${options.format}".`); - } - const formatter = new Formatter(); - - const output = formatter.format(result.failures, result.fixes, result.fileNames); - if (output.trim()) { - context.logger.info(output); - } - } - - if (result.warningCount > 0 && printInfo) { - context.logger.warn('Lint warnings found in the listed files.'); - } - - if (result.errorCount > 0 && printInfo) { - context.logger.error('Lint errors found in the listed files.'); - } - - if (result.warningCount === 0 && result.errorCount === 0 && printInfo) { - context.logger.info('All files pass linting.'); - } - - return { - success: options.force || result.errorCount === 0, - }; -} - - -/** @deprecated since version 11 as part of the TSLint deprecation. */ -export default createBuilder(_run); - - -async function _lint( - projectTslint: typeof tslint, - systemRoot: string, - tslintConfigPath: string | null, - options: TslintBuilderOptions, - program?: Program, - allPrograms?: Program[], -): Promise { - const Linter = projectTslint.Linter; - const Configuration = projectTslint.Configuration; - - const files = getFilesToLint(systemRoot, options, Linter, program); - const lintOptions = { - fix: !!options.fix, - formatter: options.format, - }; - - const linter = new Linter(lintOptions, program); - - let lastDirectory: string | undefined = undefined; - let configLoad; - const lintedFiles: string[] = []; - for (const file of files) { - if (program && allPrograms) { - // If it cannot be found in ANY program, then this is an error. - if (allPrograms.every(p => p.getSourceFile(file) === undefined)) { - throw new Error( - `File ${JSON.stringify(file)} is not part of a TypeScript project '${options.tsConfig}'.`, - ); - } else if (program.getSourceFile(file) === undefined) { - // The file exists in some other programs. We will lint it later (or earlier) in the loop. - continue; - } - } - - const contents = getFileContents(file); - - // Only check for a new tslint config if the path changes. - const currentDirectory = path.dirname(file); - if (currentDirectory !== lastDirectory) { - configLoad = Configuration.findConfiguration(tslintConfigPath, file); - lastDirectory = currentDirectory; - } - - if (configLoad) { - // Give some breathing space to other promises that might be waiting. - await Promise.resolve(); - linter.lint(file, contents, configLoad.results); - lintedFiles.push(file); - } - } - - return { - ...linter.getResult(), - fileNames: lintedFiles, - }; -} - -function getFilesToLint( - root: string, - options: TslintBuilderOptions, - linter: typeof tslint.Linter, - program?: Program, -): string[] { - const ignore = options.exclude; - const files = options.files || []; - - if (files.length > 0) { - return files - .map(file => glob.sync(file, { cwd: root, ignore, nodir: true })) - .reduce((prev, curr) => prev.concat(curr), []) - .map(file => path.join(root, file)); - } - - if (!program) { - return []; - } - - let programFiles = linter.getFileNames(program); - - if (ignore && ignore.length > 0) { - // normalize to support ./ paths - const ignoreMatchers = ignore - .map(pattern => new Minimatch(path.normalize(pattern), { dot: true })); - - programFiles = programFiles - .filter(file => !ignoreMatchers.some(matcher => matcher.match(path.relative(root, file)))); - } - - return programFiles; -} - -function getFileContents(file: string): string { - // NOTE: The tslint CLI checks for and excludes MPEG transport streams; this does not. - try { - return stripBom(readFileSync(file, 'utf-8')); - } catch { - throw new Error(`Could not read file '${file}'.`); - } -} diff --git a/packages/angular_devkit/build_angular/src/tslint/schema.json b/packages/angular_devkit/build_angular/src/tslint/schema.json deleted file mode 100644 index 64fa97adcf52..000000000000 --- a/packages/angular_devkit/build_angular/src/tslint/schema.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "title": "TSlint Target", - "description": "TSlint target options for Build Facade.", - "type": "object", - "properties": { - "tslintConfig": { - "type": "string", - "description": "The name of the TSLint configuration file." - }, - "tsConfig": { - "description": "The name of the TypeScript configuration file.", - "oneOf": [ - { "type": "string" }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "fix": { - "type": "boolean", - "description": "Fixes linting errors (may overwrite linted files).", - "default": false - }, - "typeCheck": { - "type": "boolean", - "description": "Controls the type check for linting.", - "default": false - }, - "force": { - "type": "boolean", - "description": "Succeeds even if there was linting errors.", - "default": false - }, - "silent": { - "type": "boolean", - "description": "Show output text.", - "default": false - }, - "format": { - "type": "string", - "description": "Output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist).", - "default": "stylish", - "anyOf": [ - { - "enum": [ - "checkstyle", - "codeFrame", - "filesList", - "json", - "junit", - "msbuild", - "pmd", - "prose", - "stylish", - "tap", - "verbose", - "vso" - ] - }, - { "minLength": 1 } - ] - }, - "exclude": { - "type": "array", - "description": "Files to exclude from linting.", - "default": [], - "items": { - "type": "string" - } - }, - "files": { - "type": "array", - "description": "Files to include in linting.", - "default": [], - "items": { - "type": "string" - } - } - }, - "additionalProperties": false, - "required": [] -} \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/src/tslint/works_spec.ts b/packages/angular_devkit/build_angular/src/tslint/works_spec.ts deleted file mode 100644 index bd4ec4829772..000000000000 --- a/packages/angular_devkit/build_angular/src/tslint/works_spec.ts +++ /dev/null @@ -1,280 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Architect, Target } from '@angular-devkit/architect'; -import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; -import { TestingArchitectHost } from '@angular-devkit/architect/testing'; -import { - logging, - normalize, - schema, - workspaces, -} from '@angular-devkit/core'; -import { NodeJsSyncHost } from '@angular-devkit/core/node'; -import { workspaceRoot } from '../test-utils'; - - -const lintTarget: Target = { project: 'app', target: 'lint' }; - -// tslint:disable-next-line:no-big-function -describe('Tslint Target', () => { - // const filesWithErrors = { 'src/foo.ts': 'const foo = "";\n' }; - let testArchitectHost: TestingArchitectHost; - let architect: Architect; - - beforeEach(async () => { - const registry = new schema.CoreSchemaRegistry(); - registry.addPostTransform(schema.transforms.addUndefinedDefaults); - - const { workspace } = await workspaces.readWorkspace( - normalize(workspaceRoot), - workspaces.createWorkspaceHost(new NodeJsSyncHost()), - ); - - testArchitectHost = new TestingArchitectHost( - workspaceRoot, - workspaceRoot, - new WorkspaceNodeModulesArchitectHost(workspace, workspaceRoot), - ); - architect = new Architect(testArchitectHost, registry); - }); - - it('works', async () => { - const run = await architect.scheduleTarget({ project: 'app', target: 'lint' }); - const output = await run.result; - expect(output.success).toBe(true); - await run.stop(); - }); - - it(`should show project name as status and in the logs`, async () => { - // Check logs. - const logger = new logging.Logger('lint-info'); - const allLogs: string[] = []; - logger.subscribe(entry => allLogs.push(entry.message)); - - const run = await architect.scheduleTarget(lintTarget, {}, { logger }); - - // Check status updates. - const allStatus: string[] = []; - run.progress.subscribe(progress => { - if (progress.status !== undefined) { - allStatus.push(progress.status); - } - }); - - const output = await run.result; - expect(output.success).toBe(true); - expect(allStatus).toContain(jasmine.stringMatching(/linting.*"app".*/i)); - expect(allLogs).toContain(jasmine.stringMatching(/linting.*"app".*/i)); - await run.stop(); - }); - - it(`should not show project name when formatter is non human readable`, async () => { - const overrides = { - format: 'checkstyle', - }; - - // Check logs. - const logger = new logging.Logger('lint-info'); - const allLogs: string[] = []; - logger.subscribe(entry => allLogs.push(entry.message)); - - const run = await architect.scheduleTarget(lintTarget, overrides, { logger }); - - // Check status updates. - const allStatus: string[] = []; - run.progress.subscribe(progress => { - if (progress.status !== undefined) { - allStatus.push(progress.status); - } - }); - - const output = await run.result; - expect(output.success).toBe(true); - expect(allLogs).toContain(jasmine.stringMatching(/file name=.*app.module.ts/i)); - expect(allStatus).toContain(jasmine.stringMatching(/linting.*"app".*/i)); - expect(allLogs).not.toContain(jasmine.stringMatching(/linting.*"app".*/i)); - await run.stop(); - }); - - // it('should report lint error once', (done) => { - // host.writeMultipleFiles({'src/app/app.component.ts': 'const foo = "";\n' }); - // const logger = new TestLogger('lint-error'); - // - // runTargetSpec(host, tslintTargetSpec, undefined, DefaultTimeout, logger).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(false)), - // tap(() => { - // // this is to make sure there are no duplicates - // expect(logger.includes(`" should be \'\nERROR`)).toBe(false); - // - // expect(logger.includes(`" should be '`)).toBe(true); - // expect(logger.includes(`Lint errors found in the listed files`)).toBe(true); - // }), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports exclude with glob', (done) => { - // host.writeMultipleFiles(filesWithErrors); - // const overrides: Partial = { exclude: ['**/foo.ts'] }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports exclude with relative paths', (done) => { - // host.writeMultipleFiles(filesWithErrors); - // const overrides: Partial = { exclude: ['src/foo.ts'] }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it(`supports exclude with paths starting with './'`, (done) => { - // host.writeMultipleFiles(filesWithErrors); - // const overrides: Partial = { exclude: ['./src/foo.ts'] }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports fix', (done) => { - // host.writeMultipleFiles(filesWithErrors); - // const overrides: Partial = { fix: true }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // tap(() => { - // const fileName = normalize('src/foo.ts'); - // const content = virtualFs.fileBufferToString(host.scopedSync().read(fileName)); - // expect(content).toContain(`const foo = '';`); - // }), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports force', (done) => { - // host.writeMultipleFiles(filesWithErrors); - // const logger = new TestLogger('lint-force'); - // const overrides: Partial = { force: true }; - // - // runTargetSpec(host, tslintTargetSpec, overrides, DefaultTimeout, logger).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // tap(() => { - // expect(logger.includes(`" should be '`)).toBe(true); - // expect(logger.includes(`Lint errors found in the listed files`)).toBe(true); - // }), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports format', (done) => { - // host.writeMultipleFiles(filesWithErrors); - // const logger = new TestLogger('lint-format'); - // const overrides: Partial = { format: 'stylish' }; - // - // runTargetSpec(host, tslintTargetSpec, overrides, DefaultTimeout, logger).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(false)), - // tap(() => { - // expect(logger.includes(`quotemark`)).toBe(true); - // }), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports finding configs', (done) => { - // host.writeMultipleFiles({ - // 'src/app/foo/foo.ts': `const foo = '';\n`, - // 'src/app/foo/tslint.json': ` - // { - // "rules": { - // "quotemark": [ - // true, - // "double" - // ] - // } - // } - // `, - // }); - // const overrides: Partial = { tslintConfig: undefined }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(false)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports overriding configs', (done) => { - // host.writeMultipleFiles({ - // 'src/app/foo/foo.ts': `const foo = '';\n`, - // 'src/app/foo/tslint.json': ` - // { - // "rules": { - // "quotemark": [ - // true, - // "double" - // ] - // } - // } - // `, - // }); - // const overrides: Partial = { tslintConfig: 'tslint.json' }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports using files with no project', (done) => { - // const overrides: Partial = { - // tsConfig: undefined, - // files: ['src/app/**/*.ts'], - // }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports using one project as a string', (done) => { - // const overrides: Partial = { - // tsConfig: 'src/tsconfig.app.json', - // }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports using one project as an array', (done) => { - // const overrides: Partial = { - // tsConfig: ['src/tsconfig.app.json'], - // }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('supports using two projects', (done) => { - // const overrides: Partial = { - // tsConfig: ['src/tsconfig.app.json', 'src/tsconfig.spec.json'], - // }; - // - // runTargetSpec(host, tslintTargetSpec, overrides).pipe( - // tap((buildEvent) => expect(buildEvent.success).toBe(true)), - // ).toPromise().then(done, done.fail); - // }, 30000); - // - // it('errors when type checking is used without a project', (done) => { - // const overrides: Partial = { - // tsConfig: undefined, - // typeCheck: true, - // }; - // - // runTargetSpec(host, tslintTargetSpec, overrides) - // .subscribe(undefined, () => done(), done.fail); - // }, 30000); -}); diff --git a/packages/angular_devkit/build_angular/src/typings.d.ts b/packages/angular_devkit/build_angular/src/typings.d.ts index bdf566191697..204fa0d207d0 100644 --- a/packages/angular_devkit/build_angular/src/typings.d.ts +++ b/packages/angular_devkit/build_angular/src/typings.d.ts @@ -1,10 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -declare module '@discoveryjs/json-ext' { - export function stringifyStream(value: unknown): import('stream').Readable; + +declare module '@babel/helper-annotate-as-pure' { + export default function annotateAsPure( + pathOrNode: import('@babel/types').Node | { node: import('@babel/types').Node }, + ): void; } diff --git a/packages/angular_devkit/build_angular/src/utils/action-cache.ts b/packages/angular_devkit/build_angular/src/utils/action-cache.ts deleted file mode 100644 index 6aa1b4c9afa6..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/action-cache.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as cacache from 'cacache'; -import { createHash } from 'crypto'; -import * as fs from 'fs'; -import { copyFile } from './copy-file'; -import { allowMangle } from './environment-options'; -import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from './process-bundle'; - -const packageVersion = require('../../package.json').version; - -export interface CacheEntry { - path: string; - size: number; - integrity?: string; -} - -export class BundleActionCache { - constructor(private readonly cachePath: string, private readonly integrityAlgorithm?: string) {} - - static copyEntryContent(entry: CacheEntry | string, dest: fs.PathLike): void { - copyFile(typeof entry === 'string' ? entry : entry.path, dest); - - if (process.platform !== 'win32') { - // The cache writes entries as readonly and when using copyFile the permissions will also be copied. - // See: https://github.com/npm/cacache/blob/073fbe1a9f789ba42d9a41de7b8429c93cf61579/lib/util/move-file.js#L36 - fs.chmodSync(dest, 0o644); - } - } - - generateIntegrityValue(content: string): string { - const algorithm = this.integrityAlgorithm || 'sha1'; - const codeHash = createHash(algorithm) - .update(content) - .digest('base64'); - - return `${algorithm}-${codeHash}`; - } - - generateBaseCacheKey(content: string): string { - // Create base cache key with elements: - // * package version - different build-angular versions cause different final outputs - // * code length/hash - ensure cached version matches the same input code - const integrity = this.generateIntegrityValue(content); - let baseCacheKey = `${packageVersion}|${content.length}|${integrity}`; - if (!allowMangle) { - baseCacheKey += '|MD'; - } - - return baseCacheKey; - } - - generateCacheKeys(action: ProcessBundleOptions): string[] { - // Postfix added to sourcemap cache keys when vendor, hidden sourcemaps are present - // Allows non-destructive caching of both variants - const sourceMapVendorPostfix = action.sourceMaps && action.vendorSourceMaps ? '|vendor' : ''; - - // sourceMappingURL is added at the very end which causes the code to be the same when sourcemaps are enabled/disabled - // When using hiddenSourceMaps we can omit the postfix since sourceMappingURL will not be added. - // When having sourcemaps a hashed file and non hashed file can have the same content. But the sourceMappingURL will differ. - const sourceMapPostFix = action.sourceMaps && !action.hiddenSourceMaps ? `|sourcemap|${action.filename}` : ''; - - const baseCacheKey = this.generateBaseCacheKey(action.code); - - // Determine cache entries required based on build settings - const cacheKeys: string[] = []; - - // If optimizing and the original is not ignored, add original as required - if (!action.ignoreOriginal) { - cacheKeys[CacheKey.OriginalCode] = baseCacheKey + sourceMapPostFix + '|orig'; - - // If sourcemaps are enabled, add original sourcemap as required - if (action.sourceMaps) { - cacheKeys[CacheKey.OriginalMap] = baseCacheKey + sourceMapVendorPostfix + '|orig-map'; - } - } - - // If not only optimizing, add downlevel as required - if (!action.optimizeOnly) { - cacheKeys[CacheKey.DownlevelCode] = baseCacheKey + sourceMapPostFix + '|dl'; - - // If sourcemaps are enabled, add downlevel sourcemap as required - if (action.sourceMaps) { - cacheKeys[CacheKey.DownlevelMap] = baseCacheKey + sourceMapVendorPostfix + '|dl-map'; - } - } - - return cacheKeys; - } - - async getCacheEntries(cacheKeys: (string | undefined)[]): Promise<(CacheEntry | null)[] | false> { - // Attempt to get required cache entries - const cacheEntries = []; - for (const key of cacheKeys) { - if (key) { - const entry = await cacache.get.info(this.cachePath, key); - if (!entry) { - return false; - } - cacheEntries.push({ - path: entry.path, - // tslint:disable-next-line: no-any - size: (entry as any).size, - integrity: entry.metadata && entry.metadata.integrity, - }); - } else { - cacheEntries.push(null); - } - } - - return cacheEntries; - } - - async getCachedBundleResult(action: ProcessBundleOptions): Promise { - const entries = action.cacheKeys && await this.getCacheEntries(action.cacheKeys); - if (!entries) { - return null; - } - - const result: ProcessBundleResult = { - name: action.name, - integrity: this.generateIntegrityValue(action.code), - }; - - let cacheEntry = entries[CacheKey.OriginalCode]; - if (cacheEntry) { - result.original = { - filename: action.filename, - size: cacheEntry.size, - integrity: cacheEntry.integrity, - }; - - BundleActionCache.copyEntryContent(cacheEntry, result.original.filename); - - cacheEntry = entries[CacheKey.OriginalMap]; - if (cacheEntry) { - result.original.map = { - filename: action.filename + '.map', - size: cacheEntry.size, - }; - - BundleActionCache.copyEntryContent(cacheEntry, result.original.filename + '.map'); - } - } else if (!action.ignoreOriginal) { - // If the original wasn't processed (and therefore not cached), add info - result.original = { - filename: action.filename, - size: Buffer.byteLength(action.code, 'utf8'), - map: - action.map === undefined - ? undefined - : { - filename: action.filename + '.map', - size: Buffer.byteLength(action.map, 'utf8'), - }, - }; - } - - cacheEntry = entries[CacheKey.DownlevelCode]; - if (cacheEntry) { - result.downlevel = { - filename: action.filename.replace(/\-(es20\d{2}|esnext)/, '-es5'), - size: cacheEntry.size, - integrity: cacheEntry.integrity, - }; - - BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename); - - cacheEntry = entries[CacheKey.DownlevelMap]; - if (cacheEntry) { - result.downlevel.map = { - filename: action.filename.replace(/\-(es20\d{2}|esnext)/, '-es5') + '.map', - size: cacheEntry.size, - }; - - BundleActionCache.copyEntryContent(cacheEntry, result.downlevel.filename + '.map'); - } - } - - return result; - } -} diff --git a/packages/angular_devkit/build_angular/src/utils/action-executor.ts b/packages/angular_devkit/build_angular/src/utils/action-executor.ts index a57d59e28d20..8ec0be4e971f 100644 --- a/packages/angular_devkit/build_angular/src/utils/action-executor.ts +++ b/packages/angular_devkit/build_angular/src/utils/action-executor.ts @@ -1,159 +1,69 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import JestWorker from 'jest-worker'; -import * as os from 'os'; -import * as path from 'path'; -import * as v8 from 'v8'; -import { BundleActionCache } from './action-cache'; -import { I18nOptions } from './i18n-options'; -import { InlineOptions, ProcessBundleOptions, ProcessBundleResult } from './process-bundle'; -import { maxWorkers } from './workers'; - -const hasThreadSupport = (() => { - try { - require('worker_threads'); - - return true; - } catch { - return false; - } -})(); -// This is used to normalize serialization messaging across threads and processes -// Threads use the structured clone algorithm which handles more types -// Processes use JSON which is much more limited -const serialize = ((v8 as unknown) as { serialize(value: unknown): Buffer }).serialize; +import Piscina from 'piscina'; +import { maxWorkers } from './environment-options'; +import { I18nOptions } from './i18n-options'; +import { InlineOptions } from './process-bundle'; -let workerFile = require.resolve('./process-bundle'); -workerFile = - path.extname(workerFile) === '.ts' - ? require.resolve('./process-bundle-bootstrap') - : workerFile; +const workerFile = require.resolve('./process-bundle'); export class BundleActionExecutor { - private largeWorker?: JestWorker; - private smallWorker?: JestWorker; - private cache?: BundleActionCache; + private workerPool?: Piscina; - constructor( - private workerOptions: { cachePath?: string; i18n: I18nOptions }, - integrityAlgorithm?: string, - private readonly sizeThreshold = 32 * 1024, - ) { - if (workerOptions.cachePath) { - this.cache = new BundleActionCache(workerOptions.cachePath, integrityAlgorithm); - } - } + constructor(private workerOptions: { i18n: I18nOptions }) {} - private static executeMethod(worker: JestWorker, method: string, input: unknown): Promise { - return ((worker as unknown) as Record Promise>)[method](input); - } - - private ensureLarge(): JestWorker { - if (this.largeWorker) { - return this.largeWorker; + private ensureWorkerPool(): Piscina { + if (this.workerPool) { + return this.workerPool; } - // larger files are processed in a separate process to limit memory usage in the main process - return (this.largeWorker = new JestWorker(workerFile, { - exposedMethods: ['process', 'inlineLocales'], - setupArgs: [[...serialize(this.workerOptions)]], - numWorkers: maxWorkers, - })); - } - - private ensureSmall(): JestWorker { - if (this.smallWorker) { - return this.smallWorker; - } + this.workerPool = new Piscina({ + filename: workerFile, + name: 'inlineLocales', + workerData: this.workerOptions, + maxThreads: maxWorkers, + }); - // small files are processed in a limited number of threads to improve speed - // The limited number also prevents a large increase in memory usage for an otherwise short operation - return (this.smallWorker = new JestWorker(workerFile, { - exposedMethods: ['process', 'inlineLocales'], - setupArgs: hasThreadSupport ? [this.workerOptions] : [[...serialize(this.workerOptions)]], - numWorkers: os.cpus().length < 2 ? 1 : 2, - enableWorkerThreads: hasThreadSupport, - })); - } - - private executeAction(method: string, action: { code: string }): Promise { - // code.length is not an exact byte count but close enough for this - if (action.code.length > this.sizeThreshold) { - return BundleActionExecutor.executeMethod(this.ensureLarge(), method, action); - } else { - return BundleActionExecutor.executeMethod(this.ensureSmall(), method, action); - } - } - - async process(action: ProcessBundleOptions): Promise { - if (this.cache) { - const cacheKeys = this.cache.generateCacheKeys(action); - action.cacheKeys = cacheKeys; - - // Try to get cached data, if it fails fallback to processing - try { - const cachedResult = await this.cache.getCachedBundleResult(action); - if (cachedResult) { - return cachedResult; - } - } catch {} - } - - return this.executeAction('process', action); - } - - processAll(actions: Iterable): AsyncIterable { - return BundleActionExecutor.executeAll(actions, action => this.process(action)); + return this.workerPool; } async inline( action: InlineOptions, ): Promise<{ file: string; diagnostics: { type: string; message: string }[]; count: number }> { - return this.executeAction('inlineLocales', action); + return this.ensureWorkerPool().run(action, { name: 'inlineLocales' }); } inlineAll(actions: Iterable) { - return BundleActionExecutor.executeAll(actions, action => this.inline(action)); + return BundleActionExecutor.executeAll(actions, (action) => this.inline(action)); } private static async *executeAll( actions: Iterable, executor: (action: I) => Promise, ): AsyncIterable { - const executions = new Map, Promise>(); + const executions = new Map, Promise<[Promise, O]>>(); for (const action of actions) { const execution = executor(action); executions.set( execution, - execution.then(result => { - executions.delete(execution); - - return result; - }), + execution.then((result) => [execution, result]), ); } while (executions.size > 0) { - yield Promise.race(executions.values()); + const [execution, result] = await Promise.race(executions.values()); + executions.delete(execution); + yield result; } } stop(): void { - // Floating promises are intentional here - // https://github.com/facebook/jest/tree/56079a5aceacf32333089cea50c64385885fee26/packages/jest-worker#end - if (this.largeWorker) { - // tslint:disable-next-line: no-floating-promises - this.largeWorker.end(); - } - if (this.smallWorker) { - // tslint:disable-next-line: no-floating-promises - this.smallWorker.end(); - } + void this.workerPool?.destroy(); } } diff --git a/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts b/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts deleted file mode 100644 index f5662b60b7b0..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/build-browser-features.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import * as browserslist from 'browserslist'; -import { feature, features } from 'caniuse-lite'; -import * as ts from 'typescript'; - -export class BuildBrowserFeatures { - readonly supportedBrowsers: string[]; - - constructor( - private projectRoot: string, - ) { - this.supportedBrowsers = browserslist(undefined, { path: this.projectRoot }); - } - - /** - * True, when one or more browsers requires ES5 - * support and the script target is ES2015 or greater. - */ - isDifferentialLoadingNeeded(scriptTarget: ts.ScriptTarget): boolean { - const es6TargetOrLater = scriptTarget > ts.ScriptTarget.ES5; - - return es6TargetOrLater && this.isEs5SupportNeeded(); - } - - /** - * True, when one or more browsers requires ES5 support - */ - isEs5SupportNeeded(): boolean { - return !this.isFeatureSupported('es6-module'); - } - - /** - * True, when a browser feature is supported partially or fully. - */ - isFeatureSupported(featureId: string): boolean { - // y: feature is fully available - // n: feature is unavailable - // a: feature is partially supported - // x: feature is prefixed - const criteria = [ - 'y', - 'a', - ]; - - const data = feature(features[featureId]); - - return !this.supportedBrowsers - .some(browser => { - const [agentId, version] = browser.split(' '); - - const browserData = data.stats[agentId]; - const featureStatus = (browserData && browserData[version]) as string | undefined; - - // We are only interested in the first character - // Ex: when 'a #4 #5', we only need to check for 'a' - // as for such cases we should polyfill these features as needed - return !featureStatus || !criteria.includes(featureStatus.charAt(0)); - }); - } -} diff --git a/packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts b/packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts deleted file mode 100644 index c50e885b4f5e..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/build-browser-features_spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - - -import { TestProjectHost } from '@angular-devkit/architect/testing'; -import { getSystemPath, join, normalize } from '@angular-devkit/core'; -import { ScriptTarget } from 'typescript'; -import { BuildBrowserFeatures } from './build-browser-features'; - -const workspaceRoot = join(normalize(__dirname), '../../test/build-browser-features/'); -const host = new TestProjectHost(workspaceRoot); - -describe('BuildBrowserFeatures', () => { - let workspaceRootSysPath = ''; - beforeEach(async () => { - await host.initialize().toPromise(); - workspaceRootSysPath = getSystemPath(host.root()); - }); - - afterEach(async () => host.restore().toPromise()); - - describe('isDifferentialLoadingNeeded', () => { - it('should be true for IE 9-11 and ES2017', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'IE 9-11', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isDifferentialLoadingNeeded(ScriptTarget.ES2017)).toBe(true); - }); - - it('should be false for Chrome and ES2017', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'last 1 chrome version', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isDifferentialLoadingNeeded(ScriptTarget.ES2017)).toBe(false); - }); - - it('detects no need for differential loading for target is ES5', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'last 1 chrome version', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isDifferentialLoadingNeeded(ScriptTarget.ES5)).toBe(false); - }); - - it('should be false for Safari 10.1 when target is ES2017', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'Safari 10.1', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isDifferentialLoadingNeeded(ScriptTarget.ES2017)).toBe(false); - }); - }); - - describe('isFeatureSupported', () => { - it('should be true for es6-module and Safari 10.1', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'Safari 10.1', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); - }); - - it('should be false for es6-module and IE9', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'IE 9', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(false); - }); - - it('should be true for es6-module and last 1 chrome version', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'last 1 chrome version', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); - }); - - it('should be true for es6-module and Edge 18', () => { - host.writeMultipleFiles({ - '.browserslistrc': 'Edge 18', - }); - - const buildBrowserFeatures = new BuildBrowserFeatures(workspaceRootSysPath); - expect(buildBrowserFeatures.isFeatureSupported('es6-module')).toBe(true); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/utils/build-options.ts b/packages/angular_devkit/build_angular/src/utils/build-options.ts index 433e194d1040..cacfd5746da2 100644 --- a/packages/angular_devkit/build_angular/src/utils/build-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/build-options.ts @@ -1,25 +1,28 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { logging } from '@angular-devkit/core'; -import { ParsedConfiguration } from '@angular/compiler-cli'; +import type { ParsedConfiguration } from '@angular/compiler-cli'; import { AssetPatternClass, Budget, CrossOrigin, - ExtraEntryPoint, - I18NMissingTranslation, + I18NTranslation, IndexUnion, InlineStyleLanguage, Localize, + OutputHashing, + ScriptElement, SourceMapClass, -} from '../browser/schema'; -import { Schema as DevServerSchema } from '../dev-server/schema'; + StyleElement, +} from '../builders/browser/schema'; +import { Schema as DevServerSchema } from '../builders/dev-server/schema'; +import { NormalizedCachedOptions } from './normalize-cache'; import { NormalizedFileReplacement } from './normalize-file-replacements'; import { NormalizedOptimizationOptions } from './normalize-optimization'; @@ -37,19 +40,15 @@ export interface BuildOptions { verbose?: boolean; progress?: boolean; localize?: Localize; - i18nMissingTranslation?: I18NMissingTranslation; - /** @deprecated since version 11.0. No longer required to disable CSS extraction for HMR.*/ - extractCss?: boolean; - bundleDependencies?: boolean; + i18nMissingTranslation?: I18NTranslation; externalDependencies?: string[]; watch?: boolean; - outputHashing?: string; + outputHashing?: OutputHashing; poll?: number; index?: IndexUnion; deleteOutputPath?: boolean; preserveSymlinks?: boolean; extractLicenses?: boolean; - showCircularDependencies?: boolean; buildOptimizer?: boolean; namedChunks?: boolean; crossOrigin?: CrossOrigin; @@ -57,30 +56,27 @@ export interface BuildOptions { serviceWorker?: boolean; webWorkerTsConfig?: string; statsJson: boolean; - forkTypeChecker: boolean; hmr?: boolean; main: string; - polyfills?: string; + polyfills: string[]; budgets: Budget[]; assets: AssetPatternClass[]; - scripts: ExtraEntryPoint[]; - styles: ExtraEntryPoint[]; + scripts: ScriptElement[]; + styles: StyleElement[]; stylePreprocessorOptions?: { includePaths: string[] }; platform?: 'browser' | 'server'; fileReplacements: NormalizedFileReplacement[]; inlineStyleLanguage?: InlineStyleLanguage; - allowedCommonJsDependencies?: string[]; - - differentialLoadingNeeded?: boolean; -} - -export interface WebpackTestOptions extends BuildOptions { + cache: NormalizedCachedOptions; codeCoverage?: boolean; codeCoverageExclude?: string[]; + supportedBrowsers?: string[]; } -export interface WebpackDevServerOptions extends BuildOptions, Omit { } +export interface WebpackDevServerOptions + extends BuildOptions, + Omit {} export interface WebpackConfigOptions { root: string; @@ -90,5 +86,5 @@ export interface WebpackConfigOptions { buildOptions: T; tsConfig: ParsedConfiguration; tsConfigPath: string; - scriptTarget: import('typescript').ScriptTarget; + projectName: string; } diff --git a/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts index 31dee86cf99c..56662afd01b3 100644 --- a/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts +++ b/packages/angular_devkit/build_angular/src/utils/bundle-calculator.ts @@ -1,19 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { basename } from 'path'; -import { Budget, Type } from '../browser/schema'; -import { ProcessBundleFile, ProcessBundleResult } from '../utils/process-bundle'; -import { - JsonAssetStats, - JsonChunkStats, - JsonCompilationStats, - formatSize, -} from '../webpack/utils/stats'; + +import { StatsAsset, StatsChunk, StatsCompilation } from 'webpack'; +import { Budget, Type } from '../builders/browser/schema'; +import { formatSize } from '../webpack/utils/stats'; interface Size { size: number; @@ -36,9 +31,10 @@ export enum ThresholdSeverity { Error = 'error', } -enum DifferentialBuildType { - ORIGINAL = 'original', - DOWNLEVEL = 'downlevel', +export interface BudgetCalculatorResult { + severity: ThresholdSeverity; + message: string; + label?: string; } export function* calculateThresholds(budget: Budget): IterableIterator { @@ -106,25 +102,18 @@ export function* calculateThresholds(budget: Budget): IterableIterator; type CalculatorTypes = { - new ( - budget: Budget, - chunks: JsonChunkStats[], - assets: JsonAssetStats[], - processResults: ProcessBundleResult[], - ): Calculator; + new (budget: Budget, chunks: StatsChunk[], assets: StatsAsset[]): Calculator; }; const calculatorMap: Record = { all: AllCalculator, @@ -136,7 +125,7 @@ function calculateSizes( }; const ctor = calculatorMap[budget.type]; - const {chunks, assets} = stats; + const { chunks, assets } = stats; if (!chunks) { throw new Error('Webpack stats output did not include chunk information.'); } @@ -144,61 +133,41 @@ function calculateSizes( throw new Error('Webpack stats output did not include asset information.'); } - const calculator = new ctor(budget, chunks, assets, processResults); + const calculator = new ctor(budget, chunks, assets); return calculator.calculate(); } abstract class Calculator { - constructor ( + constructor( protected budget: Budget, - protected chunks: JsonChunkStats[], - protected assets: JsonAssetStats[], - protected processResults: ProcessBundleResult[], + protected chunks: StatsChunk[], + protected assets: StatsAsset[], ) {} abstract calculate(): Size[]; /** Calculates the size of the given chunk for the provided build type. */ - protected calculateChunkSize( - chunk: JsonChunkStats, - buildType: DifferentialBuildType, - ): number { - // Look for a process result containing different builds for this chunk. - const processResult = this.processResults - .find((processResult) => processResult.name === chunk.id.toString()); - - if (processResult) { - // Found a differential build, use the correct size information. - const processResultFile = getDifferentialBuildResult( - processResult, buildType); - - return processResultFile && processResultFile.size || 0; - } else { - // No differential builds, get the chunk size by summing its assets. - return chunk.files - .filter(file => !file.endsWith('.map')) - .map(file => { - const asset = this.assets.find((asset) => asset.name === file); - if (!asset) { - throw new Error(`Could not find asset for file: ${file}`); - } - - return asset.size; - }) - .reduce((l, r) => l + r, 0); + protected calculateChunkSize(chunk: StatsChunk): number { + // No differential builds, get the chunk size by summing its assets. + if (!chunk.files) { + return 0; } - } - protected getAssetSize(asset: JsonAssetStats): number { - if (asset.name.endsWith('.js')) { - const processResult = this.processResults - .find((processResult) => processResult.original && basename(processResult.original.filename) === asset.name); - if (processResult?.original) { - return processResult.original.size; - } - } + return chunk.files + .filter((file) => !file.endsWith('.map')) + .map((file) => { + const asset = this.assets.find((asset) => asset.name === file); + if (!asset) { + throw new Error(`Could not find asset for file: ${file}`); + } + + return asset.size; + }) + .reduce((l, r) => l + r, 0); + } + protected getAssetSize(asset: StatsAsset): number { return asset.size; } } @@ -213,26 +182,12 @@ class BundleCalculator extends Calculator { return []; } - const buildTypeLabels = getBuildTypeLabels(this.processResults); - - // The chunk may or may not have differential builds. Compute the size for - // each then check afterwards if they are all the same. - const buildSizes = Object.values(DifferentialBuildType).map((buildType) => { - const size = this.chunks - .filter(chunk => chunk.names.includes(budgetName)) - .map(chunk => this.calculateChunkSize(chunk, buildType)) - .reduce((l, r) => l + r, 0); - - return { size, label: `bundle ${this.budget.name}-${buildTypeLabels[buildType]}` }; - }); - - // If this bundle was not actually generated by a differential build, then - // merge the results into a single value. - if (allEquivalent(buildSizes.map((buildSize) => buildSize.size))) { - return mergeDifferentialBuildSizes(buildSizes, budgetName); - } else { - return buildSizes; - } + const size = this.chunks + .filter((chunk) => chunk?.names?.includes(budgetName)) + .map((chunk) => this.calculateChunkSize(chunk)) + .reduce((l, r) => l + r, 0); + + return [{ size, label: this.budget.name }]; } } @@ -241,24 +196,15 @@ class BundleCalculator extends Calculator { */ class InitialCalculator extends Calculator { calculate() { - const buildTypeLabels = getBuildTypeLabels(this.processResults); - const buildSizes = Object.values(DifferentialBuildType).map((buildType) => { - return { - label: `bundle initial-${buildTypeLabels[buildType]}`, + return [ + { + label: `bundle initial`, size: this.chunks - .filter(chunk => chunk.initial) - .map(chunk => this.calculateChunkSize(chunk, buildType)) + .filter((chunk) => chunk.initial) + .map((chunk) => this.calculateChunkSize(chunk)) .reduce((l, r) => l + r, 0), - }; - }); - - // If this bundle was not actually generated by a differential build, then - // merge the results into a single value. - if (allEquivalent(buildSizes.map((buildSize) => buildSize.size))) { - return mergeDifferentialBuildSizes(buildSizes, 'initial'); - } else { - return buildSizes; - } + }, + ]; } } @@ -268,11 +214,11 @@ class InitialCalculator extends Calculator { class AllScriptCalculator extends Calculator { calculate() { const size = this.assets - .filter(asset => asset.name.endsWith('.js')) - .map(asset => this.getAssetSize(asset)) + .filter((asset) => asset.name.endsWith('.js')) + .map((asset) => this.getAssetSize(asset)) .reduce((total: number, size: number) => total + size, 0); - return [{size, label: 'total scripts'}]; + return [{ size, label: 'total scripts' }]; } } @@ -282,11 +228,11 @@ class AllScriptCalculator extends Calculator { class AllCalculator extends Calculator { calculate() { const size = this.assets - .filter(asset => !asset.name.endsWith('.map')) - .map(asset => this.getAssetSize(asset)) + .filter((asset) => !asset.name.endsWith('.map')) + .map((asset) => this.getAssetSize(asset)) .reduce((total: number, size: number) => total + size, 0); - return [{size, label: 'total'}]; + return [{ size, label: 'total' }]; } } @@ -296,8 +242,8 @@ class AllCalculator extends Calculator { class AnyScriptCalculator extends Calculator { calculate() { return this.assets - .filter(asset => asset.name.endsWith('.js')) - .map(asset => ({ + .filter((asset) => asset.name.endsWith('.js')) + .map((asset) => ({ size: this.getAssetSize(asset), label: asset.name, })); @@ -310,8 +256,8 @@ class AnyScriptCalculator extends Calculator { class AnyCalculator extends Calculator { calculate() { return this.assets - .filter(asset => !asset.name.endsWith('.map')) - .map(asset => ({ + .filter((asset) => !asset.name.endsWith('.map')) + .map((asset) => ({ size: this.getAssetSize(asset), label: asset.name, })); @@ -321,22 +267,18 @@ class AnyCalculator extends Calculator { /** * Calculate the bytes given a string value. */ -function calculateBytes( - input: string, - baseline?: string, - factor: 1 | -1 = 1, -): number { +function calculateBytes(input: string, baseline?: string, factor: 1 | -1 = 1): number { const matches = input.match(/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/); if (!matches) { return NaN; } - const baselineBytes = baseline && calculateBytes(baseline) || 0; + const baselineBytes = (baseline && calculateBytes(baseline)) || 0; let value = Number(matches[1]); switch (matches[2] && matches[2].toLowerCase()) { case '%': - value = baselineBytes * value / 100; + value = (baselineBytes * value) / 100; break; case 'kb': value *= 1024; @@ -358,22 +300,24 @@ function calculateBytes( export function* checkBudgets( budgets: Budget[], - webpackStats: JsonCompilationStats, - processResults: ProcessBundleResult[], -): IterableIterator<{ severity: ThresholdSeverity, message: string }> { + webpackStats: StatsCompilation, +): IterableIterator { // Ignore AnyComponentStyle budgets as these are handled in `AnyComponentStyleBudgetChecker`. const computableBudgets = budgets.filter((budget) => budget.type !== Type.AnyComponentStyle); for (const budget of computableBudgets) { - const sizes = calculateSizes(budget, webpackStats, processResults); + const sizes = calculateSizes(budget, webpackStats); for (const { size, label } of sizes) { yield* checkThresholds(calculateThresholds(budget), size, label); } } } -export function* checkThresholds(thresholds: IterableIterator, size: number, label?: string): - IterableIterator<{ severity: ThresholdSeverity, message: string }> { +export function* checkThresholds( + thresholds: IterableIterator, + size: number, + label?: string, +): IterableIterator { for (const threshold of thresholds) { switch (threshold.type) { case ThresholdType.Max: { @@ -384,9 +328,10 @@ export function* checkThresholds(thresholds: IterableIterator, size: const sizeDifference = formatSize(size - threshold.limit); yield { severity: threshold.severity, - message: `${label} exceeded maximum budget. Budget ${ - formatSize(threshold.limit)} was not met by ${ - sizeDifference} with a total of ${formatSize(size)}.`, + label, + message: `${label} exceeded maximum budget. Budget ${formatSize( + threshold.limit, + )} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`, }; break; } @@ -398,63 +343,16 @@ export function* checkThresholds(thresholds: IterableIterator, size: const sizeDifference = formatSize(threshold.limit - size); yield { severity: threshold.severity, - message: `${label} failed to meet minimum budget. Budget ${ - formatSize(threshold.limit)} was not met by ${ - sizeDifference} with a total of ${formatSize(size)}.`, + label, + message: `${label} failed to meet minimum budget. Budget ${formatSize( + threshold.limit, + )} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`, }; break; - } default: { + } + default: { throw new Error(`Unexpected threshold type: ${ThresholdType[threshold.type]}`); } } } } - -/** Returns the {@link ProcessBundleFile} for the given {@link DifferentialBuildType}. */ -function getDifferentialBuildResult( - processResult: ProcessBundleResult, buildType: DifferentialBuildType): - ProcessBundleFile|null { - switch (buildType) { - case DifferentialBuildType.ORIGINAL: return processResult.original || null; - case DifferentialBuildType.DOWNLEVEL: return processResult.downlevel || null; - } -} - -/** - * Merges the given differential builds into a single, non-differential value. - * - * Preconditions: All the sizes should be equivalent, or else they represent - * differential builds. - */ -function mergeDifferentialBuildSizes(buildSizes: Size[], mergeLabel: string): Size[] { - if (buildSizes.length === 0) { - return []; - } - - // Only one size. - return [{ - label: mergeLabel, - size: buildSizes[0].size, - }]; -} - -/** Returns whether or not all items in the list are equivalent to each other. */ -function allEquivalent(items: Iterable): boolean { - return new Set(items).size < 2; -} - -function getBuildTypeLabels(processResults: ProcessBundleResult[]): Record { - const fileNameSuffixRegExp = /\-(es20\d{2}|esnext)\./; - const originalFileName = processResults - .find(({ original }) => original?.filename && fileNameSuffixRegExp.test(original.filename))?.original?.filename; - - let originalSuffix: string | undefined; - if (originalFileName) { - originalSuffix = fileNameSuffixRegExp.exec(originalFileName)?.[1]; - } - - return { - [DifferentialBuildType.DOWNLEVEL]: 'es5', - [DifferentialBuildType.ORIGINAL]: originalSuffix || 'es2015', - }; -} diff --git a/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts b/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts index a9fe37c633dc..a27ed4406fc8 100644 --- a/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts +++ b/packages/angular_devkit/build_angular/src/utils/bundle-calculator_spec.ts @@ -1,25 +1,26 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Budget, Type } from '../browser/schema'; + +import { StatsCompilation } from 'webpack'; +import { Budget, Type } from '../builders/browser/schema'; import { ThresholdSeverity, checkBudgets } from './bundle-calculator'; -import { ProcessBundleResult } from './process-bundle'; const KB = 1024; -// tslint:disable-next-line: no-big-function describe('bundle-calculator', () => { - // tslint:disable-next-line: no-big-function describe('checkBudgets()', () => { it('yields maximum budgets exceeded', () => { - const budgets: Budget[] = [{ - type: Type.Any, - maximumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.Any, + maximumError: '1kb', + }, + ]; const stats = { chunks: [], assets: [ @@ -32,22 +33,25 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'foo.js', message: jasmine.stringMatching('foo.js exceeded maximum budget.'), }); }); it('yields minimum budgets exceeded', () => { - const budgets: Budget[] = [{ - type: Type.Any, - minimumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.Any, + minimumError: '1kb', + }, + ]; const stats = { chunks: [], assets: [ @@ -60,29 +64,32 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'bar.js', message: jasmine.stringMatching('bar.js failed to meet minimum budget.'), }); }); it('yields exceeded bundle budgets', () => { - const budgets: Budget[] = [{ - type: Type.Bundle, - name: 'foo', - maximumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.Bundle, + name: 'foo', + maximumError: '1kb', + }, + ]; const stats = { chunks: [ { id: 0, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], + names: ['foo'], + files: ['foo.js', 'bar.js'], }, ], assets: [ @@ -95,110 +102,32 @@ describe('bundle-calculator', () => { size: 0.75 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'foo', message: jasmine.stringMatching('foo exceeded maximum budget.'), }); }); - it('yields exceeded differential bundle budgets', () => { - const budgets: Budget[] = [{ - type: Type.Bundle, - name: 'foo', - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], - }, - ], - assets: [], - }; - const processResults: ProcessBundleResult[] = [ - { - name: '0', - original: { - filename: 'foo-es2015.js', - size: 1.25 * KB, - }, - downlevel: { - filename: 'foo-es5.js', - size: 1.75 * KB, - }, - }, - ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - expect(failures.length).toBe(2); - expect(failures).toContain({ - severity: ThresholdSeverity.Error, - message: jasmine.stringMatching('bundle foo-es2015 exceeded maximum budget.'), - }); - expect(failures).toContain({ - severity: ThresholdSeverity.Error, - message: jasmine.stringMatching('bundle foo-es5 exceeded maximum budget.'), - }); - }); - - it('does *not* yield a combined differential bundle budget', () => { - const budgets: Budget[] = [{ - type: Type.Bundle, - name: 'foo', - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], - }, - ], - assets: [], - }; - const processResults: ProcessBundleResult[] = [ + it('yields exceeded initial budget', () => { + const budgets: Budget[] = [ { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: 'foo-es2015.js', - size: 0.5 * KB, - }, - downlevel: { - filename: 'foo-es5.js', - size: 0.75 * KB, - }, + type: Type.Initial, + maximumError: '1kb', }, ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - // Because individual builds are under budget, they are acceptable. Should - // **not** yield a combined build which is over budget. - expect(failures.length).toBe(0); - }); - - it('yields exceeded initial budget', () => { - const budgets: Budget[] = [{ - type: Type.Initial, - maximumError: '1kb', - }]; const stats = { chunks: [ { id: 0, initial: true, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], + names: ['foo'], + files: ['foo.js', 'bar.js'], }, ], assets: [ @@ -211,111 +140,32 @@ describe('bundle-calculator', () => { size: 0.75 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'bundle initial', message: jasmine.stringMatching('initial exceeded maximum budget.'), }); }); - it('yields exceeded differential initial budget', () => { - const budgets: Budget[] = [{ - type: Type.Initial, - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - initial: true, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], - }, - ], - assets: [], - }; - const processResults: ProcessBundleResult[] = [ - { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: 'initial-es2017.js', - size: 1.25 * KB, - }, - downlevel: { - filename: 'initial-es5.js', - size: 1.75 * KB, - }, - }, - ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - expect(failures.length).toBe(2); - expect(failures).toContain({ - severity: ThresholdSeverity.Error, - message: jasmine.stringMatching('bundle initial-es2017 exceeded maximum budget.'), - }); - expect(failures).toContain({ - severity: ThresholdSeverity.Error, - message: jasmine.stringMatching('bundle initial-es5 exceeded maximum budget.'), - }); - }); - - it('does *not* yield a combined differential initial budget', () => { - const budgets: Budget[] = [{ - type: Type.Initial, - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - initial: true, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], - }, - ], - assets: [], - }; - const processResults: ProcessBundleResult[] = [ + it('yields exceeded total scripts budget', () => { + const budgets: Budget[] = [ { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: 'initial-es2015.js', - size: 0.5 * KB, - }, - downlevel: { - filename: 'initial-es5.js', - size: 0.75 * KB, - }, + type: Type.AllScript, + maximumError: '1kb', }, ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - // Because individual builds are under budget, they are acceptable. Should - // **not** yield a combined build which is over budget. - expect(failures.length).toBe(0); - }); - - it('yields exceeded total scripts budget', () => { - const budgets: Budget[] = [{ - type: Type.AllScript, - maximumError: '1kb', - }]; const stats = { chunks: [ { id: 0, initial: true, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], + names: ['foo'], + files: ['foo.js', 'bar.js'], }, ], assets: [ @@ -332,29 +182,32 @@ describe('bundle-calculator', () => { size: 1.5 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'total scripts', message: jasmine.stringMatching('total scripts exceeded maximum budget.'), }); }); it('yields exceeded total budget', () => { - const budgets: Budget[] = [{ - type: Type.All, - maximumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.All, + maximumError: '1kb', + }, + ]; const stats = { chunks: [ { id: 0, initial: true, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.css' ], + names: ['foo'], + files: ['foo.js', 'bar.css'], }, ], assets: [ @@ -367,29 +220,32 @@ describe('bundle-calculator', () => { size: 0.75 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'total', message: jasmine.stringMatching('total exceeded maximum budget.'), }); }); it('skips component style budgets', () => { - const budgets: Budget[] = [{ - type: Type.AnyComponentStyle, - maximumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.AnyComponentStyle, + maximumError: '1kb', + }, + ]; const stats = { chunks: [ { id: 0, initial: true, - names: [ 'foo' ], - files: [ 'foo.css', 'bar.js' ], + names: ['foo'], + files: ['foo.css', 'bar.js'], }, ], assets: [ @@ -402,25 +258,27 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(0); }); it('yields exceeded individual script budget', () => { - const budgets: Budget[] = [{ - type: Type.AnyScript, - maximumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.AnyScript, + maximumError: '1kb', + }, + ]; const stats = { chunks: [ { id: 0, initial: true, - names: [ 'foo' ], - files: [ 'foo.js', 'bar.js' ], + names: ['foo'], + files: ['foo.js', 'bar.js'], }, ], assets: [ @@ -433,29 +291,32 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'foo.js', message: jasmine.stringMatching('foo.js exceeded maximum budget.'), }); }); it('yields exceeded individual file budget', () => { - const budgets: Budget[] = [{ - type: Type.Any, - maximumError: '1kb', - }]; + const budgets: Budget[] = [ + { + type: Type.Any, + maximumError: '1kb', + }, + ]; const stats = { chunks: [ { id: 0, initial: true, - names: [ 'foo' ], - files: [ 'foo.ext', 'bar.ext' ], + names: ['foo'], + files: ['foo.ext', 'bar.ext'], }, ], assets: [ @@ -468,187 +329,16 @@ describe('bundle-calculator', () => { size: 0.5 * KB, }, ], - }; + } as unknown as StatsCompilation; - const failures = Array.from(checkBudgets(budgets, stats, [] /* processResults */)); + const failures = Array.from(checkBudgets(budgets, stats)); expect(failures.length).toBe(1); expect(failures).toContain({ severity: ThresholdSeverity.Error, + label: 'foo.ext', message: jasmine.stringMatching('foo.ext exceeded maximum budget.'), }); }); - - it('does *not* yield a combined differential bundle budget for any script', () => { - const budgets: Budget[] = [{ - type: Type.AnyScript, - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - initial: true, - names: [ 'foo' ], - files: [ 'foo.js' ], - }, - ], - assets: [ - { - name: 'main-es2015.js', - size: 1.25 * KB, - }, - ], - }; - const processResults: ProcessBundleResult[] = [ - { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: '/home/main-es2015.js', - size: 0.5 * KB, - }, - downlevel: { - filename: '/home/main-es5.js', - size: 0.75 * KB, - }, - }, - ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - // Because individual builds are under budget, they are acceptable. Should - // **not** yield a combined build which is over budget. - expect(failures.length).toBe(0); - }); - - it('does *not* yield a combined differential bundle budget for all script', () => { - const budgets: Budget[] = [{ - type: Type.AllScript, - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - initial: true, - names: [ 'foo' ], - files: [ 'foo.js' ], - }, - ], - assets: [ - { - name: 'main-es2015.js', - size: 1.25 * KB, - }, - ], - }; - const processResults: ProcessBundleResult[] = [ - { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: '/home/main-es2015.js', - size: 0.5 * KB, - }, - downlevel: { - filename: '/home/main-es5.js', - size: 0.75 * KB, - }, - }, - ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - // Because individual builds are under budget, they are acceptable. Should - // **not** yield a combined build which is over budget. - expect(failures.length).toBe(0); - }); - - it('does *not* yield a combined differential bundle budget for total budget', () => { - const budgets: Budget[] = [{ - type: Type.All, - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - initial: true, - names: [ 'foo' ], - files: [ 'foo.js' ], - }, - ], - assets: [ - { - name: 'main-es2015.js', - size: 1.25 * KB, - }, - ], - }; - const processResults: ProcessBundleResult[] = [ - { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: '/home/main-es2015.js', - size: 0.5 * KB, - }, - downlevel: { - filename: '/home/main-es5.js', - size: 0.75 * KB, - }, - }, - ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - // Because individual builds are under budget, they are acceptable. Should - // **not** yield a combined build which is over budget. - expect(failures.length).toBe(0); - }); - - it('does *not* yield a combined differential bundle budget for individual file budget', () => { - const budgets: Budget[] = [{ - type: Type.Any, - maximumError: '1kb', - }]; - const stats = { - chunks: [ - { - id: 0, - initial: true, - names: [ 'foo' ], - files: [ 'foo.js' ], - }, - ], - assets: [ - { - name: 'main-es2015.js', - size: 1.25 * KB, - }, - ], - }; - const processResults: ProcessBundleResult[] = [ - { - name: '0', - // Individual builds are under budget, but combined they are over. - original: { - filename: '/home/main-es2015.js', - size: 0.5 * KB, - }, - downlevel: { - filename: '/home/main-es5.js', - size: 0.75 * KB, - }, - }, - ]; - - const failures = Array.from(checkBudgets(budgets, stats, processResults)); - - // Because individual builds are under budget, they are acceptable. Should - // **not** yield a combined build which is over budget. - expect(failures.length).toBe(0); - }); }); }); diff --git a/packages/angular_devkit/build_angular/src/utils/cache-path.ts b/packages/angular_devkit/build_angular/src/utils/cache-path.ts deleted file mode 100644 index a6eeffdd7495..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/cache-path.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as findCacheDirectory from 'find-cache-dir'; -import { tmpdir } from 'os'; -import { resolve } from 'path'; -import { cachingBasePath } from './environment-options'; - -export function findCachePath(name: string): string { - if (cachingBasePath) { - return resolve(cachingBasePath, name); - } - - return findCacheDirectory({ name }) || tmpdir(); -} diff --git a/packages/angular_devkit/build_angular/src/utils/check-port.ts b/packages/angular_devkit/build_angular/src/utils/check-port.ts index 1a21cfa61fb0..ebbc839b26e8 100644 --- a/packages/angular_devkit/build_angular/src/utils/check-port.ts +++ b/packages/angular_devkit/build_angular/src/utils/check-port.ts @@ -1,11 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { prompt } from 'inquirer'; + import * as net from 'net'; import { isTTY } from './tty'; @@ -35,15 +35,19 @@ export async function checkPort(port: number, host: string): Promise { return; } - prompt({ - type: 'confirm', - name: 'useDifferent', - message: `Port ${port} is already in use.\nWould you like to use a different port?`, - default: true, - }).then( - (answers) => answers.useDifferent ? resolve(0) : reject(createInUseError(port)), - () => reject(createInUseError(port)), - ); + import('inquirer') + .then(({ prompt }) => + prompt({ + type: 'confirm', + name: 'useDifferent', + message: `Port ${port} is already in use.\nWould you like to use a different port?`, + default: true, + }), + ) + .then( + (answers) => (answers.useDifferent ? resolve(0) : reject(createInUseError(port))), + () => reject(createInUseError(port)), + ); }) .once('listening', () => { server.close(); diff --git a/packages/angular_devkit/build_angular/src/utils/color.ts b/packages/angular_devkit/build_angular/src/utils/color.ts index ad77d8230fc7..ff201f3e157a 100644 --- a/packages/angular_devkit/build_angular/src/utils/color.ts +++ b/packages/angular_devkit/build_angular/src/utils/color.ts @@ -1,15 +1,40 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import * as ansiColors from 'ansi-colors'; import { WriteStream } from 'tty'; -type AnsiColors = typeof ansiColors; -const supportsColor = process.stdout instanceof WriteStream && process.stdout.getColorDepth() > 1; +function supportColor(): boolean { + if (process.env.FORCE_COLOR !== undefined) { + // 2 colors: FORCE_COLOR = 0 (Disables colors), depth 1 + // 16 colors: FORCE_COLOR = 1, depth 4 + // 256 colors: FORCE_COLOR = 2, depth 8 + // 16,777,216 colors: FORCE_COLOR = 3, depth 16 + // See: https://nodejs.org/dist/latest-v12.x/docs/api/tty.html#tty_writestream_getcolordepth_env + // and https://github.com/nodejs/node/blob/b9f36062d7b5c5039498e98d2f2c180dca2a7065/lib/internal/tty.js#L106; + switch (process.env.FORCE_COLOR) { + case '': + case 'true': + case '1': + case '2': + case '3': + return true; + default: + return false; + } + } + + if (process.stdout instanceof WriteStream) { + return process.stdout.getColorDepth() > 1; + } + + return false; +} export function removeColor(text: string): string { // This has been created because when colors.enabled is false unstyle doesn't work @@ -18,8 +43,7 @@ export function removeColor(text: string): string { } // Create a separate instance to prevent unintended global changes to the color configuration -// Create function is not defined in the typings. See: https://github.com/doowb/ansi-colors/pull/44 -const colors = (ansiColors as AnsiColors & { create: () => AnsiColors }).create(); -colors.enabled = supportsColor; +const colors = ansiColors.create(); +colors.enabled = supportColor(); export { colors }; diff --git a/packages/angular_devkit/build_angular/src/utils/copy-assets.ts b/packages/angular_devkit/build_angular/src/utils/copy-assets.ts index 976796ce4cdd..be8ab1799459 100644 --- a/packages/angular_devkit/build_angular/src/utils/copy-assets.ts +++ b/packages/angular_devkit/build_angular/src/utils/copy-assets.ts @@ -1,20 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import * as fs from 'fs'; -import * as glob from 'glob'; +import glob from 'glob'; import * as path from 'path'; -import { copyFile } from './copy-file'; +import { promisify } from 'util'; -function globAsync(pattern: string, options: glob.IOptions) { - return new Promise((resolve, reject) => - glob(pattern, options, (e, m) => (e ? reject(e) : resolve(m))), - ); -} +const globPromise = promisify(glob); export async function copyAssets( entries: { @@ -33,10 +30,12 @@ export async function copyAssets( for (const entry of entries) { const cwd = path.resolve(root, entry.input); - const files = await globAsync(entry.glob, { + const files = await globPromise(entry.glob, { cwd, dot: true, nodir: true, + root: cwd, + nomount: true, ignore: entry.ignore ? defaultIgnore.concat(entry.ignore) : defaultIgnore, follow: entry.followSymlinks, }); @@ -59,7 +58,7 @@ export async function copyAssets( } directoryExists.add(dir); } - copyFile(src, dest); + fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE); } } } diff --git a/packages/angular_devkit/build_angular/src/utils/copy-file.ts b/packages/angular_devkit/build_angular/src/utils/copy-file.ts deleted file mode 100644 index f935599fe95f..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/copy-file.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as fs from 'fs'; - -// Workaround Node.js issue prior to 10.16 with copyFile on macOS -// https://github.com/angular/angular-cli/issues/15544 & https://github.com/nodejs/node/pull/27241 -let copyFileWorkaround = false; -if (process.platform === 'darwin') { - const version = process.versions.node.split('.').map(part => Number(part)); - if (version[0] < 10 || version[0] === 11 || (version[0] === 10 && version[1] < 16)) { - copyFileWorkaround = true; - } -} - -export function copyFile(src: fs.PathLike, dest: fs.PathLike): void { - if (copyFileWorkaround) { - try { - fs.unlinkSync(dest); - } catch {} - } - - fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE); -} diff --git a/packages/angular_devkit/build_angular/src/utils/default-progress.ts b/packages/angular_devkit/build_angular/src/utils/default-progress.ts index 258412b460f1..ce78668341a0 100644 --- a/packages/angular_devkit/build_angular/src/utils/default-progress.ts +++ b/packages/angular_devkit/build_angular/src/utils/default-progress.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license diff --git a/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts b/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts index bb8c648a1220..d1e770a8f5be 100644 --- a/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts +++ b/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts @@ -1,21 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import * as fs from 'fs'; import { resolve } from 'path'; -import * as rimraf from 'rimraf'; /** * Delete an output directory, but error out if it's the root of the project. */ -export function deleteOutputDir(root: string, outputPath: string) { +export function deleteOutputDir(root: string, outputPath: string): void { const resolvedOutputPath = resolve(root, outputPath); if (resolvedOutputPath === root) { throw new Error('Output path MUST not be project root directory!'); } - rimraf.sync(resolvedOutputPath); + fs.rmSync(resolvedOutputPath, { force: true, recursive: true, maxRetries: 3 }); } diff --git a/packages/angular_devkit/build_angular/src/utils/empty.js b/packages/angular_devkit/build_angular/src/utils/empty.js deleted file mode 100644 index 8b137891791f..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/empty.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/angular_devkit/build_angular/src/utils/environment-options.ts b/packages/angular_devkit/build_angular/src/utils/environment-options.ts index 2d896edb9389..ff82810d74da 100644 --- a/packages/angular_devkit/build_angular/src/utils/environment-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/environment-options.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as path from 'path'; + +import { colors } from './color'; function isDisabled(variable: string): boolean { return variable === '0' || variable.toLowerCase() === 'false'; @@ -65,20 +66,33 @@ export const allowMangle = isPresent(mangleVariable) export const shouldBeautify = debugOptimize.beautify; export const allowMinify = debugOptimize.minify; -// Build cache -const cacheVariable = process.env['NG_BUILD_CACHE']; -export const cachingDisabled = isPresent(cacheVariable) && isDisabled(cacheVariable); -export const cachingBasePath = (() => { - if (cachingDisabled || !isPresent(cacheVariable) || isEnabled(cacheVariable)) { - return null; - } - if (!path.isAbsolute(cacheVariable)) { - throw new Error('NG_BUILD_CACHE path value must be absolute.'); +/** + * Some environments, like CircleCI which use Docker report a number of CPUs by the host and not the count of available. + * This cause `Error: Call retries were exceeded` errors when trying to use them. + * + * @see https://github.com/nodejs/node/issues/28762 + * @see https://github.com/webpack-contrib/terser-webpack-plugin/issues/143 + * @see https://ithub.com/angular/angular-cli/issues/16860#issuecomment-588828079 + * + */ +const maxWorkersVariable = process.env['NG_BUILD_MAX_WORKERS']; +export const maxWorkers = isPresent(maxWorkersVariable) ? +maxWorkersVariable : 4; + +const legacySassVariable = process.env['NG_BUILD_LEGACY_SASS']; +export const useLegacySass: boolean = (() => { + if (!isPresent(legacySassVariable)) { + return false; } - return cacheVariable; + // eslint-disable-next-line no-console + console.warn( + colors.yellow( + `Warning: 'NG_BUILD_LEGACY_SASS' environment variable support will be removed in version 16.`, + ), + ); + + return isEnabled(legacySassVariable); })(); -// Build profiling -const profilingVariable = process.env['NG_BUILD_PROFILING']; -export const profilingEnabled = isPresent(profilingVariable) && isEnabled(profilingVariable); +const debugPerfVariable = process.env['NG_BUILD_DEBUG_PERF']; +export const debugPerformance = isPresent(debugPerfVariable) && isEnabled(debugPerfVariable); diff --git a/packages/angular_devkit/build_angular/src/utils/error.ts b/packages/angular_devkit/build_angular/src/utils/error.ts new file mode 100644 index 000000000000..3b37aafc9dc3 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/error.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import assert from 'assert'; + +export function assertIsError(value: unknown): asserts value is Error & { code?: string } { + const isError = + value instanceof Error || + // The following is needing to identify errors coming from RxJs. + (typeof value === 'object' && value && 'name' in value && 'message' in value); + assert(isError, 'catch clause variable is not an Error instance'); +} diff --git a/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts b/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts new file mode 100644 index 000000000000..1147738e93a9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * Transform browserlists result to esbuild target. + * @see https://esbuild.github.io/api/#target + */ +export function transformSupportedBrowsersToTargets(supportedBrowsers: string[]): string[] { + const transformed: string[] = []; + + // https://esbuild.github.io/api/#target + const esBuildSupportedBrowsers = new Set([ + 'chrome', + 'edge', + 'firefox', + 'ie', + 'ios', + 'node', + 'opera', + 'safari', + ]); + + for (const browser of supportedBrowsers) { + let [browserName, version] = browser.toLowerCase().split(' '); + + // browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios` + if (browserName === 'ios_saf') { + browserName = 'ios'; + } + + // browserslist uses ranges `15.2-15.3` versions but only the lowest is required + // to perform minimum supported feature checks. esbuild also expects a single version. + [version] = version.split('-'); + + if (esBuildSupportedBrowsers.has(browserName)) { + if (browserName === 'safari' && version === 'TP') { + // esbuild only supports numeric versions so `TP` is converted to a high number (999) since + // a Technology Preview (TP) of Safari is assumed to support all currently known features. + version = '999'; + } else if (!version.includes('.')) { + // A lone major version is considered by esbuild to include all minor versions. However, + // browserslist does not and is also inconsistent in its `.0` version naming. For example, + // Safari 15.0 is named `safari 15` but Safari 16.0 is named `safari 16.0`. + version += '.0'; + } + + transformed.push(browserName + version); + } + } + + return transformed; +} diff --git a/packages/angular_devkit/build_angular/src/utils/find-up.ts b/packages/angular_devkit/build_angular/src/utils/find-up.ts deleted file mode 100644 index d99687c5fdf9..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/find-up.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { existsSync } from 'fs'; -import * as path from 'path'; -import { isDirectory } from './is-directory'; - -export function findAllNodeModules(from: string, root?: string): string[] { - const nodeModules: string[] = []; - - let current = from; - while (current && current !== root) { - const potential = path.join(current, 'node_modules'); - if (existsSync(potential) && isDirectory(potential)) { - nodeModules.push(potential); - } - - const next = path.dirname(current); - if (next === current) { - break; - } - current = next; - } - - return nodeModules; -} diff --git a/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts b/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts index ee1fb9e9e49d..4c32203e67f3 100644 --- a/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts +++ b/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts @@ -1,16 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderContext } from '@angular-devkit/architect'; import { EmittedFiles } from '@angular-devkit/build-webpack'; import * as fs from 'fs'; import * as path from 'path'; import { BundleActionExecutor } from './action-executor'; import { copyAssets } from './copy-assets'; +import { assertIsError } from './error'; import { I18nOptions } from './i18n-options'; import { InlineOptions } from './process-bundle'; import { Spinner } from './spinner'; @@ -20,9 +22,9 @@ function emittedFilesToInlineOptions( scriptsEntryPointName: string[], emittedPath: string, outputPath: string, - es5: boolean, missingTranslation: 'error' | 'warning' | 'ignore' | undefined, -): { options: InlineOptions[]; originalFiles: string[] } { + context: BuilderContext, +): { options: InlineOptions[]; originalFiles: string[] } { const options: InlineOptions[] = []; const originalFiles: string[] = []; for (const emittedFile of emittedFiles) { @@ -38,10 +40,9 @@ function emittedFilesToInlineOptions( const action: InlineOptions = { filename: emittedFile.file, code: fs.readFileSync(originalPath, 'utf8'), - es5, outputPath, missingTranslation, - setLocale: emittedFile.name === 'main' || emittedFile.name === 'vendor', + setLocale: emittedFile.name === 'main', }; originalFiles.push(originalPath); @@ -50,11 +51,14 @@ function emittedFilesToInlineOptions( action.map = fs.readFileSync(originalMapPath, 'utf8'); originalFiles.push(originalMapPath); } catch (err) { + assertIsError(err); if (err.code !== 'ENOENT') { throw err; } } + context.logger.debug(`i18n file queued for processing: ${action.filename}`); + options.push(action); } @@ -69,7 +73,6 @@ export async function i18nInlineEmittedFiles( outputPaths: string[], scriptsEntryPointName: string[], emittedPath: string, - es5: boolean, missingTranslation: 'error' | 'warning' | 'ignore' | undefined, ): Promise { const executor = new BundleActionExecutor({ i18n }); @@ -83,11 +86,13 @@ export async function i18nInlineEmittedFiles( scriptsEntryPointName, emittedPath, baseOutputPath, - es5, missingTranslation, + context, ); for await (const result of executor.inlineAll(options)) { + context.logger.debug(`i18n file processed: ${result.file}`); + for (const diagnostic of result.diagnostics) { spinner.stop(); if (diagnostic.type === 'error') { @@ -107,13 +112,14 @@ export async function i18nInlineEmittedFiles( glob: '**/*', input: emittedPath, output: '', - ignore: [...processedFiles].map(f => path.relative(emittedPath, f)), + ignore: [...processedFiles].map((f) => path.relative(emittedPath, f)), }, ], outputPaths, '', ); } catch (err) { + assertIsError(err); spinner.fail('Localized bundle generation failed: ' + err.message); return false; diff --git a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts b/packages/angular_devkit/build_angular/src/utils/i18n-options.ts index 2a803f8fdc66..326cd6bb1800 100644 --- a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/i18n-options.ts @@ -1,33 +1,42 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderContext } from '@angular-devkit/architect'; import { json } from '@angular-devkit/core'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import { Schema as BrowserBuilderSchema } from '../browser/schema'; -import { Schema as ServerBuilderSchema } from '../server/schema'; +import fs from 'fs'; +import module from 'module'; +import os from 'os'; +import path from 'path'; +import { Schema as BrowserBuilderSchema, I18NTranslation } from '../builders/browser/schema'; +import { Schema as ServerBuilderSchema } from '../builders/server/schema'; import { readTsconfig } from '../utils/read-tsconfig'; -import { createTranslationLoader } from './load-translations'; +import { TranslationLoader, createTranslationLoader } from './load-translations'; + +/** + * The base module location used to search for locale specific data. + */ +const LOCALE_DATA_BASE_MODULE = '@angular/common/locales/global'; + +export interface LocaleDescription { + files: { + path: string; + integrity?: string; + format?: string; + }[]; + translation?: Record; + dataPath?: string; + baseHref?: string; +} export interface I18nOptions { inlineLocales: Set; sourceLocale: string; - locales: Record< - string, - { - files: { path: string; integrity?: string; format?: string }[]; - translation?: Record; - dataPath?: string; - baseHref?: string; - } - >; + locales: Record; flatOutput?: boolean; readonly shouldInline: boolean; hasDefinedSourceLocale?: boolean; @@ -79,7 +88,10 @@ export function createI18nOptions( let rawSourceLocaleBaseHref; if (json.isJsonObject(metadata.sourceLocale)) { rawSourceLocale = metadata.sourceLocale.code; - if (metadata.sourceLocale.baseHref !== undefined && typeof metadata.sourceLocale.baseHref !== 'string') { + if ( + metadata.sourceLocale.baseHref !== undefined && + typeof metadata.sourceLocale.baseHref !== 'string' + ) { throw new Error('Project i18n sourceLocale baseHref field is malformed. Expected a string.'); } rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref; @@ -132,7 +144,7 @@ export function createI18nOptions( if (inline === true) { i18n.inlineLocales.add(i18n.sourceLocale); - Object.keys(i18n.locales).forEach(locale => i18n.inlineLocales.add(locale)); + Object.keys(i18n.locales).forEach((locale) => i18n.inlineLocales.add(locale)); } else if (inline) { for (const locale of inline) { if (!i18n.locales[locale] && i18n.sourceLocale !== locale) { @@ -158,7 +170,7 @@ export async function configureI18nBuild + projectRequire.resolve(path.join(LOCALE_DATA_BASE_MODULE, locale)); - // Load locale data and translations (if present) + // Load locale data and translations (if present) let loader; const usedFormats = new Set(); for (const [locale, desc] of Object.entries(i18n.locales)) { @@ -183,21 +193,21 @@ export async function configureI18nBuild 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) { - // This limitation is only for legacy message id support (defaults to true as of 9.0) - throw new Error( - 'Localization currently only supports using one type of translation file format for the entire application.', - ); - } - - file.format = loadResult.format; - file.integrity = loadResult.integrity; + loader ??= await createTranslationLoader(); + + loadTranslations( + locale, + desc, + context.workspaceRoot, + loader, + { + warn(message) { + context.logger.warn(message); + }, + error(message) { + throw new Error(message); + }, + }, + usedFormats, + buildOptions.i18nDuplicateTranslation, + ); - if (desc.translation) { - // Merge translations - for (const [id, message] of Object.entries(loadResult.translations)) { - if (desc.translation[id] !== undefined) { - context.logger.warn( - `WARNING [${file.path}]: Duplicate translations for message '${id}' when merging`, - ); - } - desc.translation[id] = message; - } - } else { - // First or only translation file - desc.translation = loadResult.translations; - } + if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) { + // This limitation is only for legacy message id support (defaults to true as of 9.0) + throw new Error( + 'Localization currently only supports using one type of translation file format for the entire application.', + ); } } // If inlining store the output in a temporary location to facilitate post-processing if (i18n.shouldInline) { + // TODO: we should likely save these in the .angular directory in the next major version. + // We'd need to do a migration to add the temp directory to gitignore. const tempPath = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'angular-cli-i18n-')); buildOptions.outputPath = tempPath; - // Remove temporary directory used for i18n processing process.on('exit', () => { try { - rimraf.sync(tempPath); + fs.rmSync(tempPath, { force: true, recursive: true, maxRetries: 3 }); } catch {} }); } @@ -274,37 +261,72 @@ export async function configureI18nBuild string): string | null { + // Remove private use subtags + const scrubbedLocale = locale.replace(/-x(-[a-zA-Z0-9]{1,8})+$/, ''); - return localesPath; + try { + return resolver(scrubbedLocale); } catch { - return null; + // fallback to known existing en-US locale data as of 14.0 + return scrubbedLocale === 'en-US' ? findLocaleDataPath('en', resolver) : null; } } -function findLocaleDataPath(locale: string, basePath: string): string | null { - // Remove private use subtags - const scrubbedLocale = locale.replace(/-x(-[a-zA-Z0-9]{1,8})+$/, ''); - - const localeDataPath = path.join(basePath, scrubbedLocale + '.js'); +export function loadTranslations( + locale: string, + desc: LocaleDescription, + workspaceRoot: string, + loader: TranslationLoader, + logger: { warn: (message: string) => void; error: (message: string) => void }, + usedFormats?: Set, + duplicateTranslation?: I18NTranslation, +) { + let translations: Record | undefined = undefined; + for (const file of desc.files) { + const loadResult = loader(path.join(workspaceRoot, file.path)); + + for (const diagnostics of loadResult.diagnostics.messages) { + if (diagnostics.type === 'error') { + logger.error(`Error parsing translation file '${file.path}': ${diagnostics.message}`); + } else { + logger.warn(`WARNING [${file.path}]: ${diagnostics.message}`); + } + } - if (!fs.existsSync(localeDataPath)) { - if (scrubbedLocale === 'en-US') { - // fallback to known existing en-US locale data as of 9.0 - return findLocaleDataPath('en-US-POSIX', basePath); + if (loadResult.locale !== undefined && loadResult.locale !== locale) { + logger.warn( + `WARNING [${file.path}]: File target locale ('${loadResult.locale}') does not match configured locale ('${locale}')`, + ); } - return null; + usedFormats?.add(loadResult.format); + file.format = loadResult.format; + file.integrity = loadResult.integrity; + + if (translations) { + // Merge translations + for (const [id, message] of Object.entries(loadResult.translations)) { + if (translations[id] !== undefined) { + const duplicateTranslationMessage = `[${file.path}]: Duplicate translations for message '${id}' when merging.`; + switch (duplicateTranslation) { + case I18NTranslation.Ignore: + break; + case I18NTranslation.Error: + logger.error(`ERROR ${duplicateTranslationMessage}`); + break; + case I18NTranslation.Warning: + default: + logger.warn(`WARNING ${duplicateTranslationMessage}`); + break; + } + } + translations[id] = message; + } + } else { + // First or only translation file + translations = loadResult.translations; + } } - - return localeDataPath; + desc.translation = translations; } diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts b/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts index d27516c8c3af..09602760744d 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts @@ -1,18 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { createHash } from 'crypto'; +import { loadEsmModule } from '../load-esm'; import { htmlRewritingStream } from './html-rewriting-stream'; export type LoadOutputFileFunctionType = (file: string) => Promise; export type CrossOriginValue = 'none' | 'anonymous' | 'use-credentials'; +export type Entrypoint = [name: string, isModule: boolean]; + export interface AugmentIndexHtmlOptions { /* Input contents */ html: string; @@ -23,13 +26,8 @@ export interface AugmentIndexHtmlOptions { crossOrigin?: CrossOriginValue; /* * Files emitted by the build. - * Js files will be added without 'nomodule' nor 'module'. */ files: FileInfo[]; - /** Files that should be added using 'nomodule'. */ - noModuleFiles?: FileInfo[]; - /** Files that should be added using 'module'. */ - moduleFiles?: FileInfo[]; /* * Function that loads a file used. * This allows us to use different routines within the IndexHtmlWebpackPlugin and @@ -37,7 +35,7 @@ export interface AugmentIndexHtmlOptions { */ loadOutputFile: LoadOutputFileFunctionType; /** Used to sort the inseration of files in the HTML file */ - entrypoints: string[]; + entrypoints: Entrypoint[]; /** Used to set the document default locale */ lang?: string; } @@ -47,18 +45,19 @@ export interface FileInfo { name: string; extension: string; } - /* * Helper function used by the IndexHtmlWebpackPlugin. * Can also be directly used by builder, e. g. in order to generate an index.html * after processing several configurations in order to build different sets of * bundles for differential serving. */ -export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise { - const { - loadOutputFile, files, noModuleFiles = [], moduleFiles = [], entrypoints, - sri, deployUrl = '', lang, baseHref, html, - } = params; +export async function augmentIndexHtml( + params: AugmentIndexHtmlOptions, +): Promise<{ content: string; warnings: string[]; errors: string[] }> { + const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params; + + const warnings: string[] = []; + const errors: string[] = []; let { crossOrigin = 'none' } = params; if (sri && crossOrigin === 'none') { @@ -66,19 +65,19 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise } const stylesheets = new Set(); - const scripts = new Set(); + const scripts = new Map(); - // Sort files in the order we want to insert them by entrypoint and dedupes duplicates - const mergedFiles = [...moduleFiles, ...noModuleFiles, ...files]; - for (const entrypoint of entrypoints) { - for (const { extension, file, name } of mergedFiles) { - if (name !== entrypoint) { + // Sort files in the order we want to insert them by entrypoint + for (const [entrypoint, isModule] of entrypoints) { + for (const { extension, file, name } of files) { + if (name !== entrypoint || scripts.has(file) || stylesheets.has(file)) { continue; } switch (extension) { case '.js': - scripts.add(file); + // Also, non entrypoints need to be loaded as no module as they can contain problematic code. + scripts.set(file, isModule); break; case '.css': stylesheets.add(file); @@ -88,36 +87,22 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise } let scriptTags: string[] = []; - for (const script of scripts) { - const attrs = [`src="https://github.com/$%7BdeployUrl%7D$%7Bscript%7D"`]; - - if (crossOrigin !== 'none') { - attrs.push(`crossorigin="${crossOrigin}"`); - } + for (const [src, isModule] of scripts) { + const attrs = [`src="https://github.com/$%7BdeployUrl%7D$%7Bsrc%7D"`]; - // We want to include nomodule or module when a file is not common amongs all - // such as runtime.js - const scriptPredictor = ({ file }: FileInfo): boolean => file === script; - if (!files.some(scriptPredictor)) { - // in some cases for differential loading file with the same name is available in both - // nomodule and module such as scripts.js - // we shall not add these attributes if that's the case - const isNoModuleType = noModuleFiles.some(scriptPredictor); - const isModuleType = moduleFiles.some(scriptPredictor); - - if (isNoModuleType && !isModuleType) { - attrs.push('nomodule', 'defer'); - } else if (isModuleType && !isNoModuleType) { - attrs.push('type="module"'); - } else { - attrs.push('defer'); - } + // This is also need for non entry-points as they may contain problematic code. + if (isModule) { + attrs.push('type="module"'); } else { attrs.push('defer'); } + if (crossOrigin !== 'none') { + attrs.push(`crossorigin="${crossOrigin}"`); + } + if (sri) { - const content = await loadOutputFile(script); + const content = await loadOutputFile(src); attrs.push(generateSriAttributes(content)); } @@ -125,35 +110,37 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise } let linkTags: string[] = []; - for (const stylesheet of stylesheets) { - const attrs = [ - `rel="stylesheet"`, - `href="https://github.com/$%7BdeployUrl%7D$%7Bstylesheet%7D"`, - ]; + for (const src of stylesheets) { + const attrs = [`rel="stylesheet"`, `href="https://github.com/$%7BdeployUrl%7D$%7Bsrc%7D"`]; if (crossOrigin !== 'none') { attrs.push(`crossorigin="${crossOrigin}"`); } if (sri) { - const content = await loadOutputFile(stylesheet); + const content = await loadOutputFile(src); attrs.push(generateSriAttributes(content)); } linkTags.push(``); } + const dir = lang ? await getLanguageDirection(lang, warnings) : undefined; const { rewriter, transformedContent } = await htmlRewritingStream(html); const baseTagExists = html.includes(' { + .on('startTag', (tag) => { switch (tag.tagName) { case 'html': // Adjust document locale if specified if (isString(lang)) { updateAttribute(tag, 'lang', lang); } + + if (dir) { + updateAttribute(tag, 'dir', dir); + } break; case 'head': // Base href should be added before any link, meta tags @@ -174,7 +161,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise rewriter.emitStartTag(tag); }) - .on('endTag', tag => { + .on('endTag', (tag) => { switch (tag.tagName) { case 'head': for (const linkTag of linkTags) { @@ -196,27 +183,32 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise rewriter.emitEndTag(tag); }); - const content = await transformedContent; - - if (linkTags.length || scriptTags.length) { - // In case no body/head tags are not present (dotnet partial templates) - return linkTags.join('') + scriptTags.join('') + content; - } - - return content; + const content = await transformedContent(); + + return { + content: + linkTags.length || scriptTags.length + ? // In case no body/head tags are not present (dotnet partial templates) + linkTags.join('') + scriptTags.join('') + content + : content, + warnings, + errors, + }; } function generateSriAttributes(content: string): string { const algo = 'sha384'; - const hash = createHash(algo) - .update(content, 'utf8') - .digest('base64'); + const hash = createHash(algo).update(content, 'utf8').digest('base64'); return `integrity="${algo}-${hash}"`; } -function updateAttribute(tag: { attrs: { name: string, value: string }[] }, name: string, value: string): void { - const index = tag.attrs.findIndex(a => a.name === name); +function updateAttribute( + tag: { attrs: { name: string; value: string }[] }, + name: string, + value: string, +): void { + const index = tag.attrs.findIndex((a) => a.name === name); const newValue = { name, value }; if (index === -1) { @@ -229,3 +221,41 @@ function updateAttribute(tag: { attrs: { name: string, value: string }[] }, name function isString(value: unknown): value is string { return typeof value === 'string'; } + +async function getLanguageDirection( + locale: string, + warnings: string[], +): Promise { + const dir = await getLanguageDirectionFromLocales(locale); + + if (!dir) { + warnings.push( + `Locale data for '${locale}' cannot be found. 'dir' attribute will not be set for this locale.`, + ); + } + + return dir; +} + +async function getLanguageDirectionFromLocales(locale: string): Promise { + try { + const localeData = ( + await loadEsmModule( + `@angular/common/locales/${locale}`, + ) + ).default; + + const dir = localeData[localeData.length - 2]; + + return isString(dir) ? dir : undefined; + } catch { + // In some cases certain locales might map to files which are named only with language id. + // Example: `en-US` -> `en`. + const [languageId] = locale.split('-', 1); + if (languageId !== locale) { + return getLanguageDirectionFromLocales(languageId); + } + } + + return undefined; +} diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html_spec.ts b/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html_spec.ts index 0324d56f8e2e..a9fd02bc3560 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html_spec.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html_spec.ts @@ -1,12 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { tags } from '@angular-devkit/core'; -import { AugmentIndexHtmlOptions, FileInfo, augmentIndexHtml } from './augment-index-html'; +import { AugmentIndexHtmlOptions, augmentIndexHtml } from './augment-index-html'; describe('augment-index-html', () => { const indexGeneratorOptions: AugmentIndexHtmlOptions = { @@ -15,14 +16,19 @@ describe('augment-index-html', () => { sri: false, files: [], loadOutputFile: async (_fileName: string) => '', - entrypoints: ['scripts', 'polyfills', 'main', 'styles'], + entrypoints: [ + ['scripts', false], + ['polyfills', true], + ['main', true], + ['styles', false], + ], }; const oneLineHtml = (html: TemplateStringsArray) => - tags.stripIndents`${html}`.replace(/(\>\s+)/g, '>'); + tags.stripIndents`${html}`.replace(/(>\s+)/g, '>'); it('can generate index.html', async () => { - const source = augmentIndexHtml({ + const { content } = await augmentIndexHtml({ ...indexGeneratorOptions, files: [ { file: 'styles.css', extension: '.css', name: 'styles' }, @@ -33,30 +39,28 @@ describe('augment-index-html', () => { ], }); - const html = await source; - expect(html).toEqual(oneLineHtml` + expect(content).toEqual(oneLineHtml` - - - + + + `); }); it('should replace base href value', async () => { - const source = augmentIndexHtml({ + const { content } = await augmentIndexHtml({ ...indexGeneratorOptions, html: '', baseHref: '/Apps/', }); - const html = await source; - expect(html).toEqual(oneLineHtml` + expect(content).toEqual(oneLineHtml` @@ -66,98 +70,69 @@ describe('augment-index-html', () => { `); }); - it(`should emit correct script tags when having 'module' and 'non-module' js`, async () => { - const es2017JsFiles: FileInfo[] = [ - { file: 'runtime-es2017.js', extension: '.js', name: 'main' }, - { file: 'main-es2017.js', extension: '.js', name: 'main' }, - { file: 'runtime-es2017.js', extension: '.js', name: 'polyfills' }, - { file: 'polyfills-es2017.js', extension: '.js', name: 'polyfills' }, - ]; - - const es5JsFiles: FileInfo[] = [ - { file: 'runtime-es5.js', extension: '.js', name: 'main' }, - { file: 'main-es5.js', extension: '.js', name: 'main' }, - { file: 'runtime-es5.js', extension: '.js', name: 'polyfills' }, - { file: 'polyfills-es5.js', extension: '.js', name: 'polyfills' }, - ]; - - const source = augmentIndexHtml({ + it('should add lang and dir LTR attribute for French (fr)', async () => { + const { content } = await augmentIndexHtml({ ...indexGeneratorOptions, - files: [ - { file: 'styles.css', extension: '.css', name: 'styles' }, - { file: 'styles.css', extension: '.css', name: 'styles' }, - ], - moduleFiles: es2017JsFiles, - noModuleFiles: es5JsFiles, + lang: 'fr', }); - const html = await source; - expect(html).toEqual(oneLineHtml` - - - - - - - - - - - - - - - `); + expect(content).toEqual(oneLineHtml` + + + + + + + + `); }); - it( - `should not add 'module' and 'non-module' attr to js files which are in both module formats`, - async () => { - const es2017JsFiles: FileInfo[] = [ - { file: 'scripts.js', extension: '.js', name: 'scripts' }, - { file: 'main-es2017.js', extension: '.js', name: 'main' }, - ]; - - const es5JsFiles: FileInfo[] = [ - { file: 'scripts.js', extension: '.js', name: 'scripts' }, - { file: 'main-es5.js', extension: '.js', name: 'main' }, - ]; - - const source = augmentIndexHtml({ - ...indexGeneratorOptions, - files: [ - { file: 'styles.css', extension: '.css', name: 'styles' }, - { file: 'styles.css', extension: '.css', name: 'styles' }, - ], - moduleFiles: es2017JsFiles, - noModuleFiles: es5JsFiles, - }); - - const html = await source; - expect(html).toEqual(oneLineHtml` - - - - - - - - - - - - `); + it('should add lang and dir RTL attribute for Pashto (ps)', async () => { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + lang: 'ps', }); - it('should add lang attribute', async () => { - const source = augmentIndexHtml({ + expect(content).toEqual(oneLineHtml` + + + + + + + + `); + }); + + it(`should fallback to use language ID to set the dir attribute (en-US)`, async () => { + const { content, warnings } = await augmentIndexHtml({ ...indexGeneratorOptions, - lang: 'fr', + lang: 'en-US', + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + `); + }); + + it(`should work when lang (locale) is not provided by '@angular/common'`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + lang: 'xx-XX', }); - const html = await source; - expect(html).toEqual(oneLineHtml` - + expect(warnings).toEqual([ + `Locale data for 'xx-XX' cannot be found. 'dir' attribute will not be set for this locale.`, + ]); + expect(content).toEqual(oneLineHtml` + @@ -168,7 +143,7 @@ describe('augment-index-html', () => { }); it(`should add script and link tags even when body and head element doesn't exist`, async () => { - const source = augmentIndexHtml({ + const { content } = await augmentIndexHtml({ ...indexGeneratorOptions, html: ``, files: [ @@ -180,12 +155,11 @@ describe('augment-index-html', () => { ], }); - const html = await source; - expect(html).toEqual(oneLineHtml` + expect(content).toEqual(oneLineHtml` - - - + + + `); }); diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/html-rewriting-stream.ts b/packages/angular_devkit/build_angular/src/utils/index-file/html-rewriting-stream.ts index e9c910019ef8..70375c0daef0 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/html-rewriting-stream.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/html-rewriting-stream.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -9,33 +9,45 @@ import { Readable, Writable } from 'stream'; export async function htmlRewritingStream(content: string): Promise<{ - rewriter: import('parse5-html-rewriting-stream'), - transformedContent: Promise, + rewriter: import('parse5-html-rewriting-stream'); + transformedContent: () => Promise; }> { const chunks: Buffer[] = []; - const rewriter = new (await import('parse5-html-rewriting-stream'))(); + const rewriter = new (await import('parse5-html-rewriting-stream')).default(); return { rewriter, - transformedContent: new Promise(resolve => { - new Readable({ - encoding: 'utf8', - read(): void { - this.push(Buffer.from(content)); - this.push(null); - }, - }) - .pipe(rewriter) - .pipe(new Writable({ - write(chunk: string | Buffer, encoding: string | undefined, callback: Function): void { - chunks.push(typeof chunk === 'string' ? Buffer.from(chunk, encoding as BufferEncoding) : chunk); - callback(); - }, - final(callback: (error?: Error) => void): void { - callback(); - resolve(Buffer.concat(chunks).toString()); - }, - })); - }), + transformedContent: () => { + return new Promise((resolve) => { + new Readable({ + encoding: 'utf8', + read(): void { + this.push(Buffer.from(content)); + this.push(null); + }, + }) + .pipe(rewriter) + .pipe( + new Writable({ + write( + chunk: string | Buffer, + encoding: string | undefined, + callback: Function, + ): void { + chunks.push( + typeof chunk === 'string' + ? Buffer.from(chunk, encoding as BufferEncoding) + : chunk, + ); + callback(); + }, + final(callback: (error?: Error) => void): void { + callback(); + resolve(Buffer.concat(chunks).toString()); + }, + }), + ); + }); + }, }; } diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts b/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts index 299789721bbb..a49651df6127 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -8,32 +8,34 @@ import * as fs from 'fs'; import { join } from 'path'; +import { NormalizedCachedOptions } from '../normalize-cache'; import { NormalizedOptimizationOptions } from '../normalize-optimization'; import { stripBom } from '../strip-bom'; -import { CrossOriginValue, FileInfo, augmentIndexHtml } from './augment-index-html'; +import { CrossOriginValue, Entrypoint, FileInfo, augmentIndexHtml } from './augment-index-html'; import { InlineCriticalCssProcessor } from './inline-critical-css'; import { InlineFontsProcessor } from './inline-fonts'; -type IndexHtmlGeneratorPlugin = (html: string, options: IndexHtmlGeneratorProcessOptions) => Promise; +type IndexHtmlGeneratorPlugin = ( + html: string, + options: IndexHtmlGeneratorProcessOptions, +) => Promise; export interface IndexHtmlGeneratorProcessOptions { lang: string | undefined; baseHref: string | undefined; outputPath: string; files: FileInfo[]; - noModuleFiles: FileInfo[]; - moduleFiles: FileInfo[]; } export interface IndexHtmlGeneratorOptions { indexPath: string; deployUrl?: string; sri?: boolean; - entrypoints: string[]; + entrypoints: Entrypoint[]; postTransform?: IndexHtmlTransform; crossOrigin?: CrossOriginValue; optimization?: NormalizedOptimizationOptions; - WOFFSupportNeeded: boolean; + cache?: NormalizedCachedOptions; } export type IndexHtmlTransform = (content: string) => Promise; @@ -57,11 +59,7 @@ export class IndexHtmlGenerator { extraPlugins.push(inlineCriticalCssPlugin(this)); } - this.plugins = [ - augmentIndexHtmlPlugin(this), - ...extraPlugins, - postTransformPlugin(this), - ]; + this.plugins = [augmentIndexHtmlPlugin(this), ...extraPlugins, postTransformPlugin(this)]; } async process(options: IndexHtmlGeneratorProcessOptions): Promise { @@ -103,22 +101,10 @@ export class IndexHtmlGenerator { } function augmentIndexHtmlPlugin(generator: IndexHtmlGenerator): IndexHtmlGeneratorPlugin { - const { - deployUrl, - crossOrigin, - sri = false, - entrypoints, - } = generator.options; + const { deployUrl, crossOrigin, sri = false, entrypoints } = generator.options; return async (html, options) => { - const { - lang, - baseHref, - outputPath = '', - noModuleFiles, - files, - moduleFiles, - } = options; + const { lang, baseHref, outputPath = '', files } = options; return augmentIndexHtml({ html, @@ -128,9 +114,7 @@ function augmentIndexHtmlPlugin(generator: IndexHtmlGenerator): IndexHtmlGenerat sri, lang, entrypoints, - loadOutputFile: filePath => generator.readAsset(join(outputPath, filePath)), - noModuleFiles, - moduleFiles, + loadOutputFile: (filePath) => generator.readAsset(join(outputPath, filePath)), files, }); }; @@ -139,23 +123,22 @@ function augmentIndexHtmlPlugin(generator: IndexHtmlGenerator): IndexHtmlGenerat function inlineFontsPlugin({ options }: IndexHtmlGenerator): IndexHtmlGeneratorPlugin { const inlineFontsProcessor = new InlineFontsProcessor({ minify: options.optimization?.styles.minify, - WOFFSupportNeeded: options.WOFFSupportNeeded, }); - return async html => inlineFontsProcessor.process(html); + return async (html) => inlineFontsProcessor.process(html); } - function inlineCriticalCssPlugin(generator: IndexHtmlGenerator): IndexHtmlGeneratorPlugin { const inlineCriticalCssProcessor = new InlineCriticalCssProcessor({ minify: generator.options.optimization?.styles.minify, deployUrl: generator.options.deployUrl, - readAsset: filePath => generator.readAsset(filePath), + readAsset: (filePath) => generator.readAsset(filePath), }); - return async (html, options) => inlineCriticalCssProcessor.process(html, { outputPath: options.outputPath }); + return async (html, options) => + inlineCriticalCssProcessor.process(html, { outputPath: options.outputPath }); } function postTransformPlugin({ options }: IndexHtmlGenerator): IndexHtmlGeneratorPlugin { - return async html => options.postTransform ? options.postTransform(html) : html; + return async (html) => (options.postTransform ? options.postTransform(html) : html); } diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css.ts b/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css.ts index dd835688bc35..e53e951d8d93 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -24,12 +24,15 @@ class CrittersExtended extends Critters { readonly warnings: string[] = []; readonly errors: string[] = []; - constructor(private readonly optionsExtended: InlineCriticalCssProcessorOptions & InlineCriticalCssProcessOptions) { + constructor( + private readonly optionsExtended: InlineCriticalCssProcessorOptions & + InlineCriticalCssProcessOptions, + ) { super({ logger: { warn: (s: string) => this.warnings.push(s), error: (s: string) => this.errors.push(s), - info: () => { }, + info: () => {}, }, logLevel: 'warn', path: optionsExtended.outputPath, @@ -44,7 +47,7 @@ class CrittersExtended extends Critters { }); } - protected readFile(path: string): Promise { + public override readFile(path: string): Promise { const readAsset = this.optionsExtended.readAsset; return readAsset ? readAsset(path) : fs.promises.readFile(path, 'utf-8'); @@ -52,10 +55,12 @@ class CrittersExtended extends Critters { } export class InlineCriticalCssProcessor { - constructor(protected readonly options: InlineCriticalCssProcessorOptions) { } + constructor(protected readonly options: InlineCriticalCssProcessorOptions) {} - async process(html: string, options: InlineCriticalCssProcessOptions) - : Promise<{ content: string, warnings: string[], errors: string[] }> { + async process( + html: string, + options: InlineCriticalCssProcessOptions, + ): Promise<{ content: string; warnings: string[]; errors: string[] }> { const critters = new CrittersExtended({ ...this.options, ...options }); const content = await critters.process(html); diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css_spec.ts b/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css_spec.ts index dd29f5da3019..1af80e8dc847 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css_spec.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/inline-critical-css_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { tags } from '@angular-devkit/core'; import { InlineCriticalCssProcessor } from './inline-critical-css'; @@ -49,18 +50,18 @@ describe('InlineCriticalCssProcessor', () => { outputPath: '/dist/', }); - expect(content).toContain(``); - expect(content).toContain(``); + expect(content).toContain( + ``, + ); + expect(content).toContain( + ``, + ); expect(content).not.toContain('color: blue'); expect(tags.stripIndents`${content}`).toContain(tags.stripIndents` - - `); + `); }); it('should inline critical css when using deployUrl', async () => { @@ -73,17 +74,17 @@ describe('InlineCriticalCssProcessor', () => { outputPath: '/dist/', }); - expect(content).toContain(``); - expect(content).toContain(``); + expect(content).toContain( + ``, + ); + expect(content).toContain( + ``, + ); expect(tags.stripIndents`${content}`).toContain(tags.stripIndents` - - `); + `); }); it('should compress inline critical css when minify is enabled', async () => { @@ -96,8 +97,12 @@ describe('InlineCriticalCssProcessor', () => { outputPath: '/dist/', }); - expect(content).toContain(``); - expect(content).toContain(``); - expect(content).toContain(''); + expect(content).toContain( + ``, + ); + expect(content).toContain( + ``, + ); + expect(content).toContain(''); }); }); diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts b/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts index e927dd2d0322..2f358cbad7ff 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -9,93 +9,162 @@ import * as cacache from 'cacache'; import * as fs from 'fs'; import * as https from 'https'; -import * as proxyAgent from 'https-proxy-agent'; +import proxyAgent from 'https-proxy-agent'; +import { join } from 'path'; import { URL } from 'url'; -import { findCachePath } from '../cache-path'; -import { cachingDisabled } from '../environment-options'; +import { NormalizedCachedOptions } from '../normalize-cache'; +import { VERSION } from '../package-version'; import { htmlRewritingStream } from './html-rewriting-stream'; -const cacheFontsPath: string | undefined = cachingDisabled ? undefined : findCachePath('angular-build-fonts'); -const packageVersion = require('../../../package.json').version; - -const enum UserAgent { - Chrome = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36', - IE = 'Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11. 0) like Gecko', +interface FontProviderDetails { + preconnectUrl: string; } -const SUPPORTED_PROVIDERS = [ - 'fonts.googleapis.com', -]; - export interface InlineFontsOptions { minify?: boolean; - WOFFSupportNeeded: boolean; + cache?: NormalizedCachedOptions; } -export class InlineFontsProcessor { +const SUPPORTED_PROVIDERS: Record = { + 'fonts.googleapis.com': { + preconnectUrl: 'https://fonts.gstatic.com/', + }, + 'use.typekit.net': { + preconnectUrl: 'https://use.typekit.net/', + }, +}; - constructor(private options: InlineFontsOptions) { } +export class InlineFontsProcessor { + private readonly cachePath: string | undefined; + constructor(private options: InlineFontsOptions) { + const { path: cacheDirectory, enabled } = this.options.cache || {}; + if (cacheDirectory && enabled) { + this.cachePath = join(cacheDirectory, 'angular-build-fonts'); + } + } async process(content: string): Promise { const hrefList: string[] = []; + const existingPreconnect = new Set(); // Collector link tags with href - const { rewriter: collectorStream } = await htmlRewritingStream(content); + const { rewriter: collectorStream, transformedContent: initCollectorStream } = + await htmlRewritingStream(content); - collectorStream.on('startTag', tag => { + collectorStream.on('startTag', (tag) => { const { tagName, attrs } = tag; if (tagName !== 'link') { return; } - // name === 'rel' && value === 'stylesheet') - && attrs.find(({ name }) => name === 'href')?.value; + let hrefValue: string | undefined; + let relValue: string | undefined; + for (const { name, value } of attrs) { + switch (name) { + case 'rel': + relValue = value; + break; + + case 'href': + hrefValue = value; + break; + } + + if (hrefValue && relValue) { + switch (relValue) { + case 'stylesheet': + // + hrefList.push(hrefValue); + break; + + case 'preconnect': + // + existingPreconnect.add(hrefValue.replace(/\/$/, '')); + break; + } - if (href) { - hrefList.push(href); + return; + } } }); - await new Promise(resolve => collectorStream.on('finish', resolve)); + initCollectorStream().catch(() => { + // We don't really care about any errors here because it just initializes + // the rewriting stream, as we are waiting for `finish` below. + }); + + await new Promise((resolve) => collectorStream.on('finish', resolve)); // Download stylesheets - const hrefsContent = await this.processHrefs(hrefList); + const hrefsContent = new Map(); + const newPreconnectUrls = new Set(); + + for (const hrefItem of hrefList) { + const url = this.createNormalizedUrl(hrefItem); + if (!url) { + continue; + } + + const content = await this.processHref(url); + if (content === undefined) { + continue; + } + + hrefsContent.set(hrefItem, content); + + // Add preconnect + const preconnectUrl = this.getFontProviderDetails(url)?.preconnectUrl; + if (preconnectUrl && !existingPreconnect.has(preconnectUrl)) { + newPreconnectUrls.add(preconnectUrl); + } + } + if (hrefsContent.size === 0) { return content; } // Replace link with style tag. const { rewriter, transformedContent } = await htmlRewritingStream(content); - rewriter.on('startTag', tag => { + rewriter.on('startTag', (tag) => { const { tagName, attrs } = tag; - if (tagName !== 'link') { - rewriter.emitStartTag(tag); + switch (tagName) { + case 'head': + rewriter.emitStartTag(tag); + for (const url of newPreconnectUrls) { + rewriter.emitRaw(``); + } + break; + + case 'link': + const hrefAttr = + attrs.some(({ name, value }) => name === 'rel' && value === 'stylesheet') && + attrs.find(({ name, value }) => name === 'href' && hrefsContent.has(value)); + if (hrefAttr) { + const href = hrefAttr.value; + const cssContent = hrefsContent.get(href); + rewriter.emitRaw(``); + } else { + rewriter.emitStartTag(tag); + } + break; - return; - } + default: + rewriter.emitStartTag(tag); - const hrefAttr = attrs.some(({ name, value }) => name === 'rel' && value === 'stylesheet') - && attrs.find(({ name, value }) => name === 'href' && hrefsContent.has(value)); - if (hrefAttr) { - const href = hrefAttr.value; - const cssContent = hrefsContent.get(href); - rewriter.emitRaw(``); - } else { - rewriter.emitStartTag(tag); + break; } }); - return transformedContent; + return transformedContent(); } - private async getResponse(url: URL, userAgent: UserAgent): Promise { - const key = `${packageVersion}|${url}|${userAgent}`; + private async getResponse(url: URL): Promise { + const key = `${VERSION}|${url}`; - if (cacheFontsPath) { - const entry = await cacache.get.info(cacheFontsPath, key); + if (this.cachePath) { + const entry = await cacache.get.info(this.cachePath, key); if (entry) { return fs.promises.readFile(entry.path, 'utf8'); } @@ -110,82 +179,86 @@ export class InlineFontsProcessor { const data = await new Promise((resolve, reject) => { let rawResponse = ''; - https.get( - url, - { - agent, - rejectUnauthorized: false, - headers: { - 'user-agent': userAgent, + https + .get( + url, + { + agent, + rejectUnauthorized: false, + headers: { + 'user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36', + }, }, - }, - res => { - if (res.statusCode !== 200) { - reject(new Error(`Inlining of fonts failed. ${url} returned status code: ${res.statusCode}.`)); - - return; - } - - res - .on('data', chunk => rawResponse += chunk) - .on('end', () => resolve(rawResponse)); - }, - ) - .on('error', e => - reject(new Error( - `Inlining of fonts failed. An error has occurred while retrieving ${url} over the internet.\n` + - e.message, - ))); + (res) => { + if (res.statusCode !== 200) { + reject( + new Error( + `Inlining of fonts failed. ${url} returned status code: ${res.statusCode}.`, + ), + ); + + return; + } + + res.on('data', (chunk) => (rawResponse += chunk)).on('end', () => resolve(rawResponse)); + }, + ) + .on('error', (e) => + reject( + new Error( + `Inlining of fonts failed. An error has occurred while retrieving ${url} over the internet.\n` + + e.message, + ), + ), + ); }); - if (cacheFontsPath) { - await cacache.put(cacheFontsPath, key, data); + if (this.cachePath) { + await cacache.put(this.cachePath, key, data); } return data; } - private async processHrefs(hrefList: string[]): Promise> { - const hrefsContent = new Map(); + private async processHref(url: URL): Promise { + const provider = this.getFontProviderDetails(url); + if (!provider) { + return undefined; + } - for (const hrefPath of hrefList) { - // Need to convert '//' to 'https://' because the URL parser will fail with '//'. - const normalizedHref = hrefPath.startsWith('//') ? `https:${hrefPath}` : hrefPath; - if (!normalizedHref.startsWith('http')) { - // Non valid URL. - // Example: relative path styles.css. - continue; - } + let cssContent = await this.getResponse(url); - const url = new URL(normalizedHref); - // Force HTTPS protocol - url.protocol = 'https:'; + if (this.options.minify) { + cssContent = cssContent + // Comments. + .replace(/\/\*([\s\S]*?)\*\//g, '') + // New lines. + .replace(/\n/g, '') + // Safe spaces. + .replace(/\s?[{:;]\s+/g, (s) => s.trim()); + } - if (!SUPPORTED_PROVIDERS.includes(url.hostname)) { - // Provider not supported. - continue; - } + return cssContent; + } - // The order IE -> Chrome is important as otherwise Chrome will load woff1. - let cssContent = ''; - if (this.options.WOFFSupportNeeded) { - cssContent += await this.getResponse(url, UserAgent.IE); - } - cssContent += await this.getResponse(url, UserAgent.Chrome); - - if (this.options.minify) { - cssContent = cssContent - // Comments. - .replace(/\/\*([\s\S]*?)\*\//g, '') - // New lines. - .replace(/\n/g, '') - // Safe spaces. - .replace(/\s?[\{\:\;]\s+/g, s => s.trim()); - } + private getFontProviderDetails(url: URL): FontProviderDetails | undefined { + return SUPPORTED_PROVIDERS[url.hostname]; + } - hrefsContent.set(hrefPath, cssContent); + private createNormalizedUrl(value: string): URL | undefined { + // Need to convert '//' to 'https://' because the URL parser will fail with '//'. + const normalizedHref = value.startsWith('//') ? `https:${value}` : value; + if (!normalizedHref.startsWith('http')) { + // Non valid URL. + // Example: relative path styles.css. + return undefined; } - return hrefsContent; + const url = new URL(normalizedHref); + // Force HTTPS protocol + url.protocol = 'https:'; + + return url; } } diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts_spec.ts b/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts_spec.ts index 506c69fbc229..b3400899bfea 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts_spec.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts_spec.ts @@ -1,14 +1,16 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { InlineFontsProcessor } from './inline-fonts'; describe('InlineFontsProcessor', () => { - const content = ` + describe('Google fonts', () => { + const content = ` @@ -16,13 +18,21 @@ describe('InlineFontsProcessor', () => { `; - it('should inline supported fonts and icons in HTML', async () => { - const inlineFontsProcessor = new InlineFontsProcessor({ - minify: false, - WOFFSupportNeeded: false, + it('works with // protocol', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: false, + }); + + const html = await inlineFontsProcessor.process(content.replace('https://', '//')); + expect(html).toContain(`format('woff2');`); }); - const html = await inlineFontsProcessor.process(` + it('should inline supported fonts and icons in HTML', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: false, + }); + + const html = await inlineFontsProcessor.process(` @@ -32,20 +42,21 @@ describe('InlineFontsProcessor', () => { `); - expect(html).not.toContain('href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"'); - expect(html).not.toContain('href="https://fonts.googleapis.com/icon?family=Material+Icons"'); - expect(html).toContain('href="https://github.com/theme.css"'); - expect(html).toContain(`font-family: 'Roboto'`); - expect(html).toContain(`font-family: 'Material Icons'`); - }); - - it('should inline multiple fonts from a single request with minification enabled', async () => { - const inlineFontsProcessor = new InlineFontsProcessor({ - minify: true, - WOFFSupportNeeded: false, + expect(html).not.toContain( + 'href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"', + ); + expect(html).not.toContain('href="https://fonts.googleapis.com/icon?family=Material+Icons"'); + expect(html).toContain('href="https://github.com/theme.css"'); + expect(html).toContain(`font-family: 'Roboto'`); + expect(html).toContain(`font-family: 'Material Icons'`); }); - const html = await inlineFontsProcessor.process(` + it('should inline multiple fonts from a single request with minification enabled', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: true, + }); + + const html = await inlineFontsProcessor.process(` @@ -54,53 +65,59 @@ describe('InlineFontsProcessor', () => { `); - expect(html).toContain(`'Google Sans'`); - expect(html).toContain(`'Roboto'`); - expect(html).toContain(`'Roboto Mono'`); - expect(html).toContain(`'Material Icons'`); - }); - - it('works with http protocol', async () => { - const inlineFontsProcessor = new InlineFontsProcessor({ - WOFFSupportNeeded: false, - minify: false, + expect(html).toContain(`'Google Sans'`); + expect(html).toContain(`'Roboto'`); + expect(html).toContain(`'Roboto Mono'`); + expect(html).toContain(`'Material Icons'`); }); - const html = await inlineFontsProcessor - .process(content.replace('https://', 'http://')); - expect(html).toContain(`format('woff2');`); - }); + it('works with http protocol', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: false, + }); - it('works with // protocol', async () => { - const inlineFontsProcessor = new InlineFontsProcessor({ - WOFFSupportNeeded: false, - minify: false, + const html = await inlineFontsProcessor.process(content.replace('https://', 'http://')); + expect(html).toContain(`format('woff2');`); }); - const html = await inlineFontsProcessor - .process(content.replace('https://', '//')); - expect(html).toContain(`format('woff2');`); - }); + it('should remove comments and line breaks when `minifyInlinedCSS` is true', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: true, + }); - it('should include WOFF1 definitions when `WOFF1SupportNeeded` is true', async () => { - const inlineFontsProcessor = new InlineFontsProcessor({ - WOFFSupportNeeded: true, - minify: false, + const html = await inlineFontsProcessor.process(content); + expect(html).not.toContain('/*'); + expect(html).toContain(';font-style:normal;'); }); - const html = await inlineFontsProcessor.process(content); - expect(html).toContain(`format('woff2');`); - expect(html).toContain(`format('woff');`); - }); + it('should add preconnect hint', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: false, + }); - it('should remove comments and line breaks when `minifyInlinedCSS` is true', async () => { - const inlineFontsProcessor = new InlineFontsProcessor({ - WOFFSupportNeeded: false, - minify: true, + const html = await inlineFontsProcessor.process(content); + expect(html).toContain( + ``, + ); }); + }); + + describe('Adobe Typekit fonts', () => { + const content = ` + + + + + + `; - const html = await inlineFontsProcessor.process(content); - expect(html).not.toContain('/*'); - expect(html).toContain(';font-style:normal;'); + it('should add preconnect hint', async () => { + const inlineFontsProcessor = new InlineFontsProcessor({ + minify: false, + }); + + const html = await inlineFontsProcessor.process(content); + expect(html).toContain(``); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/utils/index.ts b/packages/angular_devkit/build_angular/src/utils/index.ts index 47bdbc914ce9..477ee188984b 100644 --- a/packages/angular_devkit/build_angular/src/utils/index.ts +++ b/packages/angular_devkit/build_angular/src/utils/index.ts @@ -1,12 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -export * from './build-browser-features'; export * from './default-progress'; export * from './delete-output-dir'; export * from './run-module-as-observable-fork'; @@ -16,4 +15,3 @@ export * from './normalize-source-maps'; export * from './normalize-optimization'; export * from './normalize-builder-schema'; export * from './url'; -export * from './workers'; diff --git a/packages/angular_devkit/build_angular/src/utils/is-directory.ts b/packages/angular_devkit/build_angular/src/utils/is-directory.ts deleted file mode 100644 index f4636a272531..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/is-directory.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable -// TODO: cleanup this file, it's copied as is from Angular CLI. - -import * as fs from 'fs'; - -export function isDirectory(path: string) { - try { - return fs.statSync(path).isDirectory(); - } catch (_) { - return false; - } -} diff --git a/packages/angular_devkit/build_angular/src/utils/load-esm.ts b/packages/angular_devkit/build_angular/src/utils/load-esm.ts new file mode 100644 index 000000000000..a9d4e5cf87ae --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/load-esm.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { URL } from 'url'; + +/** + * This uses a dynamic import to load a module which may be ESM. + * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript + * will currently, unconditionally downlevel dynamic import into a require call. + * require calls cannot load ESM code and will result in a runtime error. To workaround + * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. + * Once TypeScript provides support for keeping the dynamic import this workaround can + * be dropped. + * + * @param modulePath The path of the module to load. + * @returns A Promise that resolves to the dynamically imported module. + */ +export function loadEsmModule(modulePath: string | URL): Promise { + return new Function('modulePath', `return import(modulePath);`)(modulePath) as Promise; +} diff --git a/packages/angular_devkit/build_angular/src/utils/load-translations.ts b/packages/angular_devkit/build_angular/src/utils/load-translations.ts index ac78333e0c27..d481e6aa83ae 100644 --- a/packages/angular_devkit/build_angular/src/utils/load-translations.ts +++ b/packages/angular_devkit/build_angular/src/utils/load-translations.ts @@ -1,20 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import type { Diagnostics } from '@angular/localize/tools'; import { createHash } from 'crypto'; import * as fs from 'fs'; +import { loadEsmModule } from './load-esm'; -export type TranslationLoader = ( - path: string, -) => { +export type TranslationLoader = (path: string) => { translations: Record; format: string; locale?: string; - diagnostics: import('@angular/localize/src/tools/src/diagnostics').Diagnostics; + diagnostics: Diagnostics; integrity: string; }; @@ -25,9 +26,11 @@ export async function createTranslationLoader(): Promise { const content = fs.readFileSync(path, 'utf8'); const unusedParsers = new Map(); for (const [format, parser] of Object.entries(parsers)) { - const analysis = analyze(parser, path, content); + const analysis = parser.analyze(path, content); if (analysis.canParse) { - const { locale, translations } = parser.parse(path, content, analysis.hint); + // Types don't overlap here so we need to use any. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { locale, translations } = parser.parse(path, content, analysis.hint as any); const integrity = 'sha256-' + createHash('sha256').update(content).digest('base64'); return { format, locale, translations, diagnostics, integrity }; @@ -45,48 +48,30 @@ export async function createTranslationLoader(): Promise { messages.join('\n'), ); }; - - // TODO: `parser.canParse()` is deprecated; remove this polyfill once we are sure all parsers provide the `parser.analyze()` method. - // tslint:disable-next-line: no-any - function analyze(parser: any, path: string, content: string) { - if (parser.analyze !== undefined) { - return parser.analyze(path, content); - } else { - const hint = parser.canParse(path, content); - - return { canParse: hint !== false, hint, diagnostics }; - } - } } async function importParsers() { try { + // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + const { + Diagnostics, + ArbTranslationParser, + SimpleJsonTranslationParser, + Xliff1TranslationParser, + Xliff2TranslationParser, + XtbTranslationParser, + } = await loadEsmModule('@angular/localize/tools'); - const localizeDiag = await import('@angular/localize/src/tools/src/diagnostics'); - const diagnostics = new localizeDiag.Diagnostics(); - + const diagnostics = new Diagnostics(); const parsers = { - arb: new (await import( - // tslint:disable-next-line:trailing-comma - '@angular/localize/src/tools/src/translate/translation_files/translation_parsers/arb_translation_parser' - )).ArbTranslationParser(), - json: new (await import( - // tslint:disable-next-line:trailing-comma - '@angular/localize/src/tools/src/translate/translation_files/translation_parsers/simple_json_translation_parser' - )).SimpleJsonTranslationParser(), - xlf: new (await import( - // tslint:disable-next-line:trailing-comma - '@angular/localize/src/tools/src/translate/translation_files/translation_parsers/xliff1_translation_parser' - )).Xliff1TranslationParser(), - xlf2: new (await import( - // tslint:disable-next-line:trailing-comma - '@angular/localize/src/tools/src/translate/translation_files/translation_parsers/xliff2_translation_parser' - )).Xliff2TranslationParser(), + arb: new ArbTranslationParser(), + json: new SimpleJsonTranslationParser(), + xlf: new Xliff1TranslationParser(), + xlf2: new Xliff2TranslationParser(), // The name ('xmb') needs to match the AOT compiler option - xmb: new (await import( - // tslint:disable-next-line:trailing-comma - '@angular/localize/src/tools/src/translate/translation_files/translation_parsers/xtb_translation_parser' - )).XtbTranslationParser(), + xmb: new XtbTranslationParser(), }; return { parsers, diagnostics }; diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-asset-patterns.ts b/packages/angular_devkit/build_angular/src/utils/normalize-asset-patterns.ts index da17ac8d3568..02a6529d112c 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-asset-patterns.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-asset-patterns.ts @@ -1,24 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - BaseException, - Path, - basename, - dirname, - getSystemPath, - join, - normalize, - relative, - resolve, -} from '@angular-devkit/core'; -import { statSync } from 'fs'; -import { AssetPattern, AssetPatternClass } from '../browser/schema'; +import { BaseException } from '@angular-devkit/core'; +import { statSync } from 'fs'; +import * as path from 'path'; +import { AssetPattern, AssetPatternClass } from '../builders/browser/schema'; export class MissingAssetSourceRootException extends BaseException { constructor(path: String) { @@ -28,59 +19,58 @@ export class MissingAssetSourceRootException extends BaseException { export function normalizeAssetPatterns( assetPatterns: AssetPattern[], - root: Path, - projectRoot: Path, - maybeSourceRoot: Path | undefined, + workspaceRoot: string, + projectRoot: string, + projectSourceRoot: string | undefined, ): AssetPatternClass[] { - // When sourceRoot is not available, we default to ${projectRoot}/src. - const sourceRoot = maybeSourceRoot || join(projectRoot, 'src'); - const resolvedSourceRoot = resolve(root, sourceRoot); - if (assetPatterns.length === 0) { return []; } - return assetPatterns - .map(assetPattern => { - // Normalize string asset patterns to objects. - if (typeof assetPattern === 'string') { - const assetPath = normalize(assetPattern); - const resolvedAssetPath = resolve(root, assetPath); - - // Check if the string asset is within sourceRoot. - if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { - throw new MissingAssetSourceRootException(assetPattern); - } + // When sourceRoot is not available, we default to ${projectRoot}/src. + const sourceRoot = projectSourceRoot || path.join(projectRoot, 'src'); + const resolvedSourceRoot = path.resolve(workspaceRoot, sourceRoot); - let glob: string, input: Path, output: Path; - let isDirectory = false; + return assetPatterns.map((assetPattern) => { + // Normalize string asset patterns to objects. + if (typeof assetPattern === 'string') { + const assetPath = path.normalize(assetPattern); + const resolvedAssetPath = path.resolve(workspaceRoot, assetPath); - try { - isDirectory = statSync(getSystemPath(resolvedAssetPath)).isDirectory(); - } catch { - isDirectory = true; - } + // Check if the string asset is within sourceRoot. + if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { + throw new MissingAssetSourceRootException(assetPattern); + } - if (isDirectory) { - // Folders get a recursive star glob. - glob = '**/*'; - // Input directory is their original path. - input = assetPath; - } else { - // Files are their own glob. - glob = basename(assetPath); - // Input directory is their original dirname. - input = dirname(assetPath); - } + let glob: string, input: string; + let isDirectory = false; - // Output directory for both is the relative path from source root to input. - output = relative(resolvedSourceRoot, resolve(root, input)); + try { + isDirectory = statSync(resolvedAssetPath).isDirectory(); + } catch { + isDirectory = true; + } - // Return the asset pattern in object format. - return { glob, input, output }; + if (isDirectory) { + // Folders get a recursive star glob. + glob = '**/*'; + // Input directory is their original path. + input = assetPath; } else { - // It's already an AssetPatternObject, no need to convert. - return assetPattern; + // Files are their own glob. + glob = path.basename(assetPath); + // Input directory is their original dirname. + input = path.dirname(assetPath); } - }); + + // Output directory for both is the relative path from source root to input. + const output = path.relative(resolvedSourceRoot, path.resolve(workspaceRoot, input)); + + // Return the asset pattern in object format. + return { glob, input, output }; + } else { + // It's already an AssetPatternObject, no need to convert. + return assetPattern; + } + }); } diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts b/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts index 5fe6d10b7e3d..43189d3a370c 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts @@ -1,67 +1,78 @@ - /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - -import { Path } from '@angular-devkit/core'; +import { json, logging } from '@angular-devkit/core'; import { AssetPatternClass, Schema as BrowserBuilderSchema, SourceMapClass, -} from '../browser/schema'; +} from '../builders/browser/schema'; import { BuildOptions } from './build-options'; import { normalizeAssetPatterns } from './normalize-asset-patterns'; +import { normalizeCacheOptions } from './normalize-cache'; import { NormalizedFileReplacement, normalizeFileReplacements, } from './normalize-file-replacements'; import { NormalizedOptimizationOptions, normalizeOptimization } from './normalize-optimization'; +import { normalizePolyfills } from './normalize-polyfills'; import { normalizeSourceMaps } from './normalize-source-maps'; - +import { getSupportedBrowsers } from './supported-browsers'; /** * A normalized browser builder schema. */ -export type NormalizedBrowserBuilderSchema = BrowserBuilderSchema & BuildOptions & { - sourceMap: SourceMapClass; - assets: AssetPatternClass[]; - fileReplacements: NormalizedFileReplacement[]; - optimization: NormalizedOptimizationOptions; -}; +export type NormalizedBrowserBuilderSchema = BrowserBuilderSchema & + BuildOptions & { + sourceMap: SourceMapClass; + assets: AssetPatternClass[]; + fileReplacements: NormalizedFileReplacement[]; + optimization: NormalizedOptimizationOptions; + polyfills: string[]; + }; export function normalizeBrowserSchema( - root: Path, - projectRoot: Path, - sourceRoot: Path | undefined, + workspaceRoot: string, + projectRoot: string, + projectSourceRoot: string | undefined, options: BrowserBuilderSchema, + metadata: json.JsonObject, + logger: logging.LoggerApi, ): NormalizedBrowserBuilderSchema { - const normalizedSourceMapOptions = normalizeSourceMaps(options.sourceMap || false); - return { ...options, - assets: normalizeAssetPatterns(options.assets || [], root, projectRoot, sourceRoot), - fileReplacements: normalizeFileReplacements(options.fileReplacements || [], root), + cache: normalizeCacheOptions(metadata, workspaceRoot), + assets: normalizeAssetPatterns( + options.assets || [], + workspaceRoot, + projectRoot, + projectSourceRoot, + ), + fileReplacements: normalizeFileReplacements(options.fileReplacements || [], workspaceRoot), optimization: normalizeOptimization(options.optimization), - sourceMap: normalizedSourceMapOptions, - preserveSymlinks: options.preserveSymlinks === undefined ? process.execArgv.includes('--preserve-symlinks') : options.preserveSymlinks, + sourceMap: normalizeSourceMaps(options.sourceMap || false), + polyfills: normalizePolyfills(options.polyfills, workspaceRoot), + preserveSymlinks: + options.preserveSymlinks === undefined + ? process.execArgv.includes('--preserve-symlinks') + : options.preserveSymlinks, statsJson: options.statsJson || false, - forkTypeChecker: options.forkTypeChecker || false, budgets: options.budgets || [], scripts: options.scripts || [], styles: options.styles || [], stylePreprocessorOptions: { - includePaths: options.stylePreprocessorOptions - && options.stylePreprocessorOptions.includePaths - || [], + includePaths: + (options.stylePreprocessorOptions && options.stylePreprocessorOptions.includePaths) || [], }, // Using just `--poll` will result in a value of 0 which is very likely not the intention // A value of 0 is falsy and will disable polling rather then enable // 500 ms is a sensible default in this case poll: options.poll === 0 ? 500 : options.poll, + supportedBrowsers: getSupportedBrowsers(projectRoot, logger), }; } diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-cache.ts b/packages/angular_devkit/build_angular/src/utils/normalize-cache.ts new file mode 100644 index 000000000000..7d8c10053b6a --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/normalize-cache.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { json } from '@angular-devkit/core'; +import { join, resolve } from 'path'; +import { VERSION } from './package-version'; + +export interface NormalizedCachedOptions { + /** Whether disk cache is enabled. */ + enabled: boolean; + /** Disk cache path. Example: `/.angular/cache/v12.0.0`. */ + path: string; + /** Disk cache base path. Example: `/.angular/cache`. */ + basePath: string; +} + +interface CacheMetadata { + enabled?: boolean; + environment?: 'local' | 'ci' | 'all'; + path?: string; +} + +export function normalizeCacheOptions( + metadata: json.JsonObject, + worspaceRoot: string, +): NormalizedCachedOptions { + const cacheMetadata: CacheMetadata = + json.isJsonObject(metadata.cli) && json.isJsonObject(metadata.cli.cache) + ? metadata.cli.cache + : {}; + + const { enabled = true, environment = 'local', path = '.angular/cache' } = cacheMetadata; + const isCI = process.env['CI'] === '1' || process.env['CI']?.toLowerCase() === 'true'; + + let cacheEnabled = enabled; + if (cacheEnabled) { + switch (environment) { + case 'ci': + cacheEnabled = isCI; + break; + case 'local': + cacheEnabled = !isCI; + break; + } + } + + const cacheBasePath = resolve(worspaceRoot, path); + + return { + enabled: cacheEnabled, + basePath: cacheBasePath, + path: join(cacheBasePath, VERSION), + }; +} diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-file-replacements.ts b/packages/angular_devkit/build_angular/src/utils/normalize-file-replacements.ts index 27d80d7491aa..741079ffded9 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-file-replacements.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-file-replacements.ts @@ -1,21 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - BaseException, - Path, - getSystemPath, - join, - normalize, -} from '@angular-devkit/core'; +import { BaseException } from '@angular-devkit/core'; import { existsSync } from 'fs'; -import { FileReplacement } from '../browser/schema'; - +import * as path from 'path'; +import { FileReplacement } from '../builders/browser/schema'; export class MissingFileReplacementException extends BaseException { constructor(path: String) { @@ -24,28 +18,29 @@ export class MissingFileReplacementException extends BaseException { } export interface NormalizedFileReplacement { - replace: Path; - with: Path; + replace: string; + with: string; } export function normalizeFileReplacements( fileReplacements: FileReplacement[], - root: Path, + workspaceRoot: string, ): NormalizedFileReplacement[] { if (fileReplacements.length === 0) { return []; } - const normalizedReplacement = fileReplacements - .map(replacement => normalizeFileReplacement(replacement, root)); + const normalizedReplacement = fileReplacements.map((replacement) => + normalizeFileReplacement(replacement, workspaceRoot), + ); for (const { replace, with: replacementWith } of normalizedReplacement) { - if (!existsSync(getSystemPath(replacementWith))) { - throw new MissingFileReplacementException(getSystemPath(replacementWith)); + if (!existsSync(replacementWith)) { + throw new MissingFileReplacementException(replacementWith); } - if (!existsSync(getSystemPath(replace))) { - throw new MissingFileReplacementException(getSystemPath(replace)); + if (!existsSync(replace)) { + throw new MissingFileReplacementException(replace); } } @@ -54,27 +49,22 @@ export function normalizeFileReplacements( function normalizeFileReplacement( fileReplacement: FileReplacement, - root?: Path, + root: string, ): NormalizedFileReplacement { - let replacePath: Path; - let withPath: Path; + let replacePath: string; + let withPath: string; if (fileReplacement.src && fileReplacement.replaceWith) { - replacePath = normalize(fileReplacement.src); - withPath = normalize(fileReplacement.replaceWith); + replacePath = fileReplacement.src; + withPath = fileReplacement.replaceWith; } else if (fileReplacement.replace && fileReplacement.with) { - replacePath = normalize(fileReplacement.replace); - withPath = normalize(fileReplacement.with); + replacePath = fileReplacement.replace; + withPath = fileReplacement.with; } else { throw new Error(`Invalid file replacement: ${JSON.stringify(fileReplacement)}`); } - // TODO: For 7.x should this only happen if not absolute? - if (root) { - replacePath = join(root, replacePath); - } - if (root) { - withPath = join(root, withPath); - } - - return { replace: replacePath, with: withPath }; + return { + replace: path.join(root, replacePath), + with: path.join(root, withPath), + }; } diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-optimization.ts b/packages/angular_devkit/build_angular/src/utils/normalize-optimization.ts index 6946a3039f1a..9bbf455b86f5 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-optimization.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-optimization.ts @@ -1,29 +1,44 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { FontsClass, OptimizationClass, OptimizationUnion, StylesClass } from '../browser/schema'; +import { + FontsClass, + OptimizationClass, + OptimizationUnion, + StylesClass, +} from '../builders/browser/schema'; -export type NormalizedOptimizationOptions = Required> & { - fonts: FontsClass, - styles: StylesClass, +export type NormalizedOptimizationOptions = Required< + Omit +> & { + fonts: FontsClass; + styles: StylesClass; }; -export function normalizeOptimization(optimization: OptimizationUnion = false): NormalizedOptimizationOptions { +export function normalizeOptimization( + optimization: OptimizationUnion = true, +): NormalizedOptimizationOptions { if (typeof optimization === 'object') { return { scripts: !!optimization.scripts, - styles: typeof optimization.styles === 'object' ? optimization.styles : { - minify: !!optimization.styles, - inlineCritical: !!optimization.styles, - }, - fonts: typeof optimization.fonts === 'object' ? optimization.fonts : { - inline: !!optimization.fonts, - }, + styles: + typeof optimization.styles === 'object' + ? optimization.styles + : { + minify: !!optimization.styles, + inlineCritical: !!optimization.styles, + }, + fonts: + typeof optimization.fonts === 'object' + ? optimization.fonts + : { + inline: !!optimization.fonts, + }, }; } diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-polyfills.ts b/packages/angular_devkit/build_angular/src/utils/normalize-polyfills.ts new file mode 100644 index 000000000000..8eb75745b0b9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/normalize-polyfills.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { existsSync } from 'fs'; +import { resolve } from 'path'; + +export function normalizePolyfills( + polyfills: string[] | string | undefined, + root: string, +): string[] { + if (!polyfills) { + return []; + } + + const polyfillsList = Array.isArray(polyfills) ? polyfills : [polyfills]; + + return polyfillsList.map((p) => { + const resolvedPath = resolve(root, p); + + // If file doesn't exist, let the bundle resolve it using node module resolution. + return existsSync(resolvedPath) ? resolvedPath : p; + }); +} diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts b/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts index ca873b59fa95..1ccd09ee4bf8 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-source-maps.ts @@ -1,18 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { SourceMapClass, SourceMapUnion } from '../browser/schema'; +import { SourceMapClass, SourceMapUnion } from '../builders/browser/schema'; export function normalizeSourceMaps(sourceMap: SourceMapUnion): SourceMapClass { const scripts = typeof sourceMap === 'object' ? sourceMap.scripts : sourceMap; const styles = typeof sourceMap === 'object' ? sourceMap.styles : sourceMap; - const hidden = typeof sourceMap === 'object' && sourceMap.hidden || false; - const vendor = typeof sourceMap === 'object' && sourceMap.vendor || false; + const hidden = (typeof sourceMap === 'object' && sourceMap.hidden) || false; + const vendor = (typeof sourceMap === 'object' && sourceMap.vendor) || false; return { vendor, diff --git a/packages/angular_devkit/build_angular/src/utils/output-paths.ts b/packages/angular_devkit/build_angular/src/utils/output-paths.ts index 78e34299c801..b3a6ab4e431d 100644 --- a/packages/angular_devkit/build_angular/src/utils/output-paths.ts +++ b/packages/angular_devkit/build_angular/src/utils/output-paths.ts @@ -1,17 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { existsSync, mkdirSync } from 'fs'; import { join } from 'path'; import { I18nOptions } from './i18n-options'; export function ensureOutputPaths(baseOutputPath: string, i18n: I18nOptions): Map { const outputPaths: [string, string][] = i18n.shouldInline - ? [...i18n.inlineLocales].map(l => [l, i18n.flatOutput ? baseOutputPath : join(baseOutputPath, l)]) + ? [...i18n.inlineLocales].map((l) => [ + l, + i18n.flatOutput ? baseOutputPath : join(baseOutputPath, l), + ]) : [['', baseOutputPath]]; for (const [, outputPath] of outputPaths) { diff --git a/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts b/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts index 982c3f294c54..78ca469f2265 100644 --- a/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts +++ b/packages/angular_devkit/build_angular/src/utils/package-chunk-sort.ts @@ -1,44 +1,46 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { ExtraEntryPoint } from '../browser/schema'; + +import { ScriptElement, StyleElement } from '../builders/browser/schema'; import { normalizeExtraEntryPoints } from '../webpack/utils/helpers'; -export function generateEntryPoints(appConfig: { - styles: ExtraEntryPoint[]; - scripts: ExtraEntryPoint[]; -}) { +export type EntryPointsType = [name: string, isModule: boolean]; + +export function generateEntryPoints(options: { + styles: StyleElement[]; + scripts: ScriptElement[]; + isHMREnabled?: boolean; +}): EntryPointsType[] { // Add all styles/scripts, except lazy-loaded ones. const extraEntryPoints = ( - extraEntryPoints: ExtraEntryPoint[], + extraEntryPoints: (ScriptElement | ScriptElement)[], defaultBundleName: string, - ): string[] => { + ) => { const entryPoints = normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName) - .filter(entry => entry.inject) - .map(entry => entry.bundleName); + .filter((entry) => entry.inject) + .map((entry) => entry.bundleName); // remove duplicates - return [...new Set(entryPoints)]; + return [...new Set(entryPoints)].map((f) => [f, false]); }; - const entryPoints = [ - 'runtime', - 'polyfills-es5', - 'polyfills', - 'sw-register', - ...extraEntryPoints(appConfig.styles, 'styles'), - ...extraEntryPoints(appConfig.scripts, 'scripts'), - 'vendor', - 'main', + const entryPoints: EntryPointsType[] = [ + ['runtime', !options.isHMREnabled], + ['polyfills', true], + ...extraEntryPoints(options.styles, 'styles'), + ...extraEntryPoints(options.scripts, 'scripts'), + ['vendor', true], + ['main', true], ]; - const duplicates = [ - ...new Set(entryPoints.filter(x => entryPoints.indexOf(x) !== entryPoints.lastIndexOf(x))), - ]; + const duplicates = entryPoints.filter( + ([name]) => entryPoints[0].indexOf(name) !== entryPoints[0].lastIndexOf(name), + ); if (duplicates.length > 0) { throw new Error(`Multiple bundles have been named the same: '${duplicates.join(`', '`)}'.`); diff --git a/packages/angular_devkit/build_angular/src/utils/package-version.ts b/packages/angular_devkit/build_angular/src/utils/package-version.ts new file mode 100644 index 000000000000..4634959bd831 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/package-version.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export const VERSION: string = require('../../package.json').version; diff --git a/packages/angular_devkit/build_angular/src/utils/process-bundle-bootstrap.js b/packages/angular_devkit/build_angular/src/utils/process-bundle-bootstrap.js index b2b1965f4c67..7ff6bb28fcdc 100644 --- a/packages/angular_devkit/build_angular/src/utils/process-bundle-bootstrap.js +++ b/packages/angular_devkit/build_angular/src/utils/process-bundle-bootstrap.js @@ -1,9 +1,10 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + require('../../../../../lib/bootstrap-local'); -module.exports = require('./process-bundle.ts'); \ No newline at end of file +module.exports = require('./process-bundle.ts'); diff --git a/packages/angular_devkit/build_angular/src/utils/process-bundle.ts b/packages/angular_devkit/build_angular/src/utils/process-bundle.ts index 88e0cc55ae56..90beec2d0b07 100644 --- a/packages/angular_devkit/build_angular/src/utils/process-bundle.ts +++ b/packages/angular_devkit/build_angular/src/utils/process-bundle.ts @@ -1,14 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import remapping from '@ampproject/remapping'; import { NodePath, ParseResult, - PluginObj, parseSync, transformAsync, transformFromAstSync, @@ -16,535 +17,54 @@ import { types, } from '@babel/core'; import templateBuilder from '@babel/template'; -import * as cacache from 'cacache'; -import { createHash } from 'crypto'; -import * as fs from 'fs'; +import * as fs from 'fs/promises'; import * as path from 'path'; -import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; -import { minify } from 'terser'; -import * as v8 from 'v8'; -import { sources } from 'webpack'; -import { allowMangle, allowMinify, shouldBeautify } from './environment-options'; +import { workerData } from 'worker_threads'; +import { allowMinify, shouldBeautify } from './environment-options'; +import { assertIsError } from './error'; import { I18nOptions } from './i18n-options'; +import { loadEsmModule } from './load-esm'; -const { - ConcatSource, - OriginalSource, - ReplaceSource, - SourceMapSource, -} = sources; - -type LocalizeUtilities = typeof import('@angular/localize/src/tools/src/source_file_utils'); - -// If code size is larger than 500KB, consider lower fidelity but faster sourcemap merge -const FAST_SOURCEMAP_THRESHOLD = 500 * 1024; - -export interface ProcessBundleOptions { - filename: string; - code: string; - map?: string; - name: string; - sourceMaps?: boolean; - hiddenSourceMaps?: boolean; - vendorSourceMaps?: boolean; - runtime?: boolean; - optimize?: boolean; - optimizeOnly?: boolean; - ignoreOriginal?: boolean; - cacheKeys?: (string | undefined)[]; - integrityAlgorithm?: 'sha256' | 'sha384' | 'sha512'; - runtimeData?: ProcessBundleResult[]; - replacements?: [string, string][]; - supportedBrowsers?: string[] | Record; -} - -export interface ProcessBundleResult { - name: string; - integrity?: string; - original?: ProcessBundleFile; - downlevel?: ProcessBundleFile; -} - -export interface ProcessBundleFile { - filename: string; - size: number; - integrity?: string; - map?: { - filename: string; - size: number; - }; -} - -export const enum CacheKey { - OriginalCode = 0, - OriginalMap = 1, - DownlevelCode = 2, - DownlevelMap = 3, -} - -let cachePath: string | undefined; -let i18n: I18nOptions | undefined; - -export function setup(data: number[] | { cachePath: string; i18n: I18nOptions }): void { - const options = Array.isArray(data) - ? (v8.deserialize(Buffer.from(data)) as { cachePath: string; i18n: I18nOptions }) - : data; - cachePath = options.cachePath; - i18n = options.i18n; -} - -async function cachePut(content: string, key: string | undefined, integrity?: string): Promise { - if (cachePath && key) { - await cacache.put(cachePath, key, content, { - metadata: { integrity }, - }); - } -} - -export async function process(options: ProcessBundleOptions): Promise { - if (!options.cacheKeys) { - options.cacheKeys = []; - } - - const result: ProcessBundleResult = { name: options.name }; - if (options.integrityAlgorithm) { - // Store unmodified code integrity value -- used for SRI value replacement - result.integrity = generateIntegrityValue(options.integrityAlgorithm, options.code); - } - - // Runtime chunk requires specialized handling - if (options.runtime) { - return { ...result, ...(await processRuntime(options)) }; - } - - const basePath = path.dirname(options.filename); - const filename = path.basename(options.filename); - const downlevelFilename = filename.replace(/\-(es20\d{2}|esnext)/, '-es5'); - const downlevel = !options.optimizeOnly; - const sourceCode = options.code; - const sourceMap = options.map ? JSON.parse(options.map) : undefined; - - let downlevelCode; - let downlevelMap; - if (downlevel) { - const {supportedBrowsers: targets = []} = options; - - // todo: revisit this in version 10, when we update our defaults browserslist - // Without this workaround bundles will not be downlevelled because Babel doesn't know handle to 'op_mini all' - // See: https://github.com/babel/babel/issues/11155 - if (Array.isArray(targets) && targets.includes('op_mini all')) { - targets.push('ie_mob 11'); - } else if ('op_mini' in targets) { - targets['ie_mob'] = '11'; - } - - // Downlevel the bundle - const transformResult = await transformAsync(sourceCode, { - filename, - // using false ensures that babel will NOT search and process sourcemap comments (large memory usage) - // The types do not include the false option even though it is valid - // tslint:disable-next-line: no-any - inputSourceMap: false as any, - babelrc: false, - configFile: false, - presets: [[ - require.resolve('@babel/preset-env'), - { - // browserslist-compatible query or object of minimum environment versions to support - targets, - // modules aren't needed since the bundles use webpack's custom module loading - modules: false, - // 'transform-typeof-symbol' generates slower code - exclude: ['transform-typeof-symbol'], - }, - ]], - plugins: [ - createIifeWrapperPlugin(), - ...(options.replacements ? [createReplacePlugin(options.replacements)] : []), - ], - minified: allowMinify && !!options.optimize, - compact: !shouldBeautify && !!options.optimize, - sourceMaps: !!sourceMap, - }); - - if (!transformResult || !transformResult.code) { - throw new Error(`Unknown error occurred processing bundle for "${options.filename}".`); - } - downlevelCode = transformResult.code; - - if (sourceMap && transformResult.map) { - // String length is used as an estimate for byte length - const fastSourceMaps = sourceCode.length > FAST_SOURCEMAP_THRESHOLD; - - downlevelMap = await mergeSourceMaps( - sourceCode, - sourceMap, - downlevelCode, - transformResult.map, - filename, - // When not optimizing, the sourcemaps are significantly less complex - // and can use the higher fidelity merge - !!options.optimize && fastSourceMaps, - ); - } - } - - if (downlevelCode) { - result.downlevel = await processBundle({ - ...options, - code: downlevelCode, - map: downlevelMap, - filename: path.join(basePath, downlevelFilename), - isOriginal: false, - }); - } - - if (!result.original && !options.ignoreOriginal) { - result.original = await processBundle({ - ...options, - isOriginal: true, - }); - } - - return result; -} - -async function mergeSourceMaps( - inputCode: string, - inputSourceMap: RawSourceMap, - resultCode: string, - resultSourceMap: RawSourceMap, - filename: string, - fast = false, -): Promise { - // Webpack 5 terser sourcemaps currently fail merging with the high-quality method - if (fast) { - return mergeSourceMapsFast(inputSourceMap, resultSourceMap); - } - - // SourceMapSource produces high-quality sourcemaps - // Final sourcemap will always be available when providing the input sourcemaps - // tslint:disable-next-line: no-non-null-assertion - const finalSourceMap = new SourceMapSource( - resultCode, - filename, - resultSourceMap, - inputCode, - inputSourceMap, - true, - ).map()!; - - return finalSourceMap as RawSourceMap; -} - -async function mergeSourceMapsFast(first: RawSourceMap, second: RawSourceMap) { - const sourceRoot = first.sourceRoot; - const generator = new SourceMapGenerator(); - - // sourcemap package adds the sourceRoot to all position source paths if not removed - delete first.sourceRoot; - - await SourceMapConsumer.with(first, null, originalConsumer => { - return SourceMapConsumer.with(second, null, newConsumer => { - newConsumer.eachMapping(mapping => { - if (mapping.originalLine === null) { - return; - } - const originalPosition = originalConsumer.originalPositionFor({ - line: mapping.originalLine, - column: mapping.originalColumn, - }); - if ( - originalPosition.line === null || - originalPosition.column === null || - originalPosition.source === null - ) { - return; - } - generator.addMapping({ - generated: { - line: mapping.generatedLine, - column: mapping.generatedColumn, - }, - name: originalPosition.name || undefined, - original: { - line: originalPosition.line, - column: originalPosition.column, - }, - source: originalPosition.source, - }); - }); - }); - }); - - const map = generator.toJSON(); - map.file = second.file; - map.sourceRoot = sourceRoot; - - // Add source content if present - if (first.sourcesContent) { - // Source content array is based on index of sources - const sourceContentMap = new Map(); - for (let i = 0; i < first.sources.length; i++) { - // make paths "absolute" so they can be compared (`./a.js` and `a.js` are equivalent) - sourceContentMap.set(path.resolve('/', first.sources[i]), i); - } - map.sourcesContent = []; - for (let i = 0; i < map.sources.length; i++) { - const contentIndex = sourceContentMap.get(path.resolve('/', map.sources[i])); - if (contentIndex === undefined) { - map.sourcesContent.push(''); - } else { - map.sourcesContent.push(first.sourcesContent[contentIndex]); - } - } - } - - // Put the sourceRoot back - if (sourceRoot) { - first.sourceRoot = sourceRoot; - } - - return map; -} - -async function processBundle( - options: Omit & { isOriginal: boolean; map?: string | RawSourceMap }, -): Promise { - const { - optimize, - isOriginal, - code, - map, - filename: filepath, - hiddenSourceMaps, - cacheKeys = [], - integrityAlgorithm, - } = options; - - const rawMap = typeof map === 'string' ? JSON.parse(map) as RawSourceMap : map; - const filename = path.basename(filepath); - - let result: { - code: string, - map: RawSourceMap | undefined, - }; - - if (rawMap) { - rawMap.file = filename; - } - - if (optimize) { - result = await terserMangle(code, { - filename, - map: rawMap, - compress: !isOriginal, // We only compress bundles which are downlevelled. - ecma: isOriginal ? 2015 : 5, - }); - } else { - result = { - map: rawMap, - code, - }; - } - - let mapContent: string | undefined; - if (result.map) { - if (!hiddenSourceMaps) { - result.code += `\n//# sourceMappingURL=${filename}.map`; - } - - mapContent = JSON.stringify(result.map); - - await cachePut( - mapContent, - cacheKeys[isOriginal ? CacheKey.OriginalMap : CacheKey.DownlevelMap], - ); - fs.writeFileSync(filepath + '.map', mapContent); - } - - const fileResult = createFileEntry( - filepath, - result.code, - mapContent, - integrityAlgorithm, - ); - - await cachePut( - result.code, - cacheKeys[isOriginal ? CacheKey.OriginalCode : CacheKey.DownlevelCode], - fileResult.integrity, - ); - fs.writeFileSync(filepath, result.code); - - return fileResult; -} +// Extract Sourcemap input type from the remapping function since it is not currently exported +type SourceMapInput = Exclude[0], unknown[]>; -async function terserMangle( - code: string, - options: { filename?: string; map?: RawSourceMap; compress?: boolean; ecma?: 5 | 2015 } = {}, -) { - // Note: Investigate converting the AST instead of re-parsing - // estree -> terser is already supported; need babel -> estree/terser - - // Mangle downlevel code - const minifyOutput = await minify(options.filename ? { [options.filename]: code } : code, { - compress: allowMinify && !!options.compress, - ecma: options.ecma || 5, - mangle: allowMangle, - safari10: true, - format: { - ascii_only: true, - webkit: true, - beautify: shouldBeautify, - wrap_func_args: false, - }, - sourceMap: - !!options.map && - ({ - asObject: true, - // typings don't include asObject option - // tslint:disable-next-line: no-any - } as any), - }); +// Lazy loaded webpack-sources object +// Webpack is only imported if needed during the processing +let webpackSources: typeof import('webpack').sources | undefined; - // tslint:disable-next-line: no-non-null-assertion - const outputCode = minifyOutput.code!; - - let outputMap; - if (options.map && minifyOutput.map) { - outputMap = await mergeSourceMaps( - code, - options.map, - outputCode, - minifyOutput.map as unknown as RawSourceMap, - options.filename || '0', - code.length > FAST_SOURCEMAP_THRESHOLD, - ); - } +const { i18n } = (workerData || {}) as { i18n?: I18nOptions }; - return { code: outputCode, map: outputMap }; -} - -function createFileEntry( - filename: string, - code: string, - map: string | undefined, - integrityAlgorithm?: string, -): ProcessBundleFile { - return { - filename: filename, - size: Buffer.byteLength(code), - integrity: integrityAlgorithm && generateIntegrityValue(integrityAlgorithm, code), - map: !map - ? undefined - : { - filename: filename + '.map', - size: Buffer.byteLength(map), - }, - }; -} +/** + * Internal flag to enable the direct usage of the `@angular/localize` translation plugins. + * Their usage is currently several times slower than the string manipulation method. + * Future work to optimize the plugins should enable plugin usage as the default. + */ +const USE_LOCALIZE_PLUGINS = false; -function generateIntegrityValue(hashAlgorithm: string, code: string) { - return ( - hashAlgorithm + - '-' + - createHash(hashAlgorithm) - .update(code) - .digest('base64') - ); -} +type LocalizeUtilityModule = typeof import('@angular/localize/tools'); -// The webpack runtime chunk is already ES5. -// However, two variants are still needed due to lazy routing and SRI differences -// NOTE: This should eventually be a babel plugin -async function processRuntime( - options: ProcessBundleOptions, -): Promise> { - let originalCode = options.code; - let downlevelCode = options.code; - - // Replace integrity hashes with updated values - if (options.integrityAlgorithm && options.runtimeData) { - for (const data of options.runtimeData) { - if (!data.integrity) { - continue; - } +/** + * Cached instance of the `@angular/localize/tools` module. + * This is used to remove the need to repeatedly import the module per file translation. + */ +let localizeToolsModule: LocalizeUtilityModule | undefined; - if (data.original && data.original.integrity) { - originalCode = originalCode.replace(data.integrity, data.original.integrity); - } - if (data.downlevel && data.downlevel.integrity) { - downlevelCode = downlevelCode.replace(data.integrity, data.downlevel.integrity); - } - } +/** + * Attempts to load the `@angular/localize/tools` module containing the functionality to + * perform the file translations. + * This module must be dynamically loaded as it is an ESM module and this file is CommonJS. + */ +async function loadLocalizeTools(): Promise { + if (localizeToolsModule !== undefined) { + return localizeToolsModule; } - // Adjust lazy loaded scripts to point to the proper variant - // Extra spacing is intentional to align source line positions - downlevelCode = downlevelCode.replace(/"\-(es20\d{2}|esnext)\./, ' "-es5.'); - - return { - original: await processBundle({ - ...options, - code: originalCode, - isOriginal: true, - }), - downlevel: await processBundle({ - ...options, - code: downlevelCode, - filename: options.filename.replace(/\-(es20\d{2}|esnext)/, '-es5'), - isOriginal: false, - }), - }; + // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + return loadEsmModule('@angular/localize/tools'); } -function createReplacePlugin(replacements: [string, string][]): PluginObj { - return { - visitor: { - StringLiteral(path: NodePath) { - for (const replacement of replacements) { - if (path.node.value === replacement[0]) { - path.node.value = replacement[1]; - } - } - }, - }, - }; -} - -function createIifeWrapperPlugin(): PluginObj { - return { - visitor: { - Program: { - exit(path: NodePath) { - // Save existing body and directives - const { body, directives } = path.node; - - // Clear out body and directives for wrapper - path.node.body = []; - path.node.directives = []; - - // Create the wrapper - "(function() { ... })();" - const wrapper = types.expressionStatement( - types.callExpression( - types.parenthesizedExpression( - types.functionExpression(undefined, [], types.blockStatement(body, directives)), - ), - [], - ), - ); - - // Insert the wrapper - path.pushContainer('body', wrapper); - }, - }, - }, - }; -} - -const USE_LOCALIZE_PLUGINS = false; - export async function createI18nPlugins( locale: string, translation: unknown | undefined, @@ -552,40 +72,21 @@ export async function createI18nPlugins( shouldInline: boolean, localeDataContent?: string, ) { - const plugins = []; - const localizeDiag = await import('@angular/localize/src/tools/src/diagnostics'); + const { Diagnostics, makeEs2015TranslatePlugin, makeLocalePlugin } = await loadLocalizeTools(); - const diagnostics = new localizeDiag.Diagnostics(); + const plugins = []; + const diagnostics = new Diagnostics(); if (shouldInline) { - const es2015 = await import( - // tslint:disable-next-line: trailing-comma - '@angular/localize/src/tools/src/translate/source_files/es2015_translate_plugin' - ); - plugins.push( - // tslint:disable-next-line: no-any - es2015.makeEs2015TranslatePlugin(diagnostics, (translation || {}) as any, { - missingTranslation: translation === undefined ? 'ignore' : missingTranslation, - }), - ); - - const es5 = await import( - // tslint:disable-next-line: trailing-comma - '@angular/localize/src/tools/src/translate/source_files/es5_translate_plugin' - ); plugins.push( - // tslint:disable-next-line: no-any - es5.makeEs5TranslatePlugin(diagnostics, (translation || {}) as any, { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + makeEs2015TranslatePlugin(diagnostics, (translation || {}) as any, { missingTranslation: translation === undefined ? 'ignore' : missingTranslation, }), ); } - const inlineLocale = await import( - // tslint:disable-next-line: trailing-comma - '@angular/localize/src/tools/src/translate/source_files/locale_plugin' - ); - plugins.push(inlineLocale.makeLocalePlugin(locale)); + plugins.push(makeLocalePlugin(locale)); if (localeDataContent) { plugins.push({ @@ -604,7 +105,6 @@ export interface InlineOptions { filename: string; code: string; map?: string; - es5: boolean; outputPath: string; missingTranslation?: 'warning' | 'error' | 'ignore'; setLocale?: boolean; @@ -632,23 +132,25 @@ export async function inlineLocales(options: InlineOptions) { return inlineCopyOnly(options); } + await loadLocalizeTools(); + let ast: ParseResult | undefined | null; try { ast = parseSync(options.code, { babelrc: false, configFile: false, - sourceType: 'script', + sourceType: 'unambiguous', filename: options.filename, }); } catch (error) { - if (error.message) { - // Make the error more readable. - // Same errors will contain the full content of the file as the error message - // Which makes it hard to find the actual error message. - const index = error.message.indexOf(')\n'); - const msg = index !== -1 ? error.message.substr(0, index + 1) : error.message; - throw new Error(`${msg}\nAn error occurred inlining file "${options.filename}"`); - } + assertIsError(error); + + // Make the error more readable. + // Same errors will contain the full content of the file as the error message + // Which makes it hard to find the actual error message. + const index = error.message.indexOf(')\n'); + const msg = index !== -1 ? error.message.slice(0, index + 1) : error.message; + throw new Error(`${msg}\nAn error occurred inlining file "${options.filename}"`); } if (!ast) { @@ -660,17 +162,16 @@ export async function inlineLocales(options: InlineOptions) { } const diagnostics = []; - const inputMap = options.map && (JSON.parse(options.map) as RawSourceMap); for (const locale of i18n.inlineLocales) { const isSourceLocale = locale === i18n.sourceLocale; - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const translations: any = isSourceLocale ? {} : i18n.locales[locale].translation || {}; let localeDataContent; if (options.setLocale) { // If locale data is provided, load it and prepend to file const localeDataPath = i18n.locales[locale]?.dataPath; if (localeDataPath) { - localeDataContent = await loadLocaleData(localeDataPath, true, options.es5); + localeDataContent = await loadLocaleData(localeDataPath, true); } } @@ -685,13 +186,13 @@ export async function inlineLocales(options: InlineOptions) { filename: options.filename, // using false ensures that babel will NOT search and process sourcemap comments (large memory usage) // The types do not include the false option even though it is valid - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any inputSourceMap: false as any, babelrc: false, configFile: false, plugins, compact: !shouldBeautify, - sourceMaps: !!inputMap, + sourceMaps: !!options.map, }); diagnostics.push(...localeDiagnostics.messages); @@ -705,19 +206,12 @@ export async function inlineLocales(options: InlineOptions) { i18n.flatOutput ? '' : locale, options.filename, ); - fs.writeFileSync(outputPath, transformResult.code); - - if (inputMap && transformResult.map) { - const outputMap = await mergeSourceMaps( - options.code, - inputMap, - transformResult.code, - transformResult.map, - options.filename, - options.code.length > FAST_SOURCEMAP_THRESHOLD, - ); + await fs.writeFile(outputPath, transformResult.code); - fs.writeFileSync(outputPath + '.map', JSON.stringify(outputMap)); + if (options.map && transformResult.map) { + const outputMap = remapping([transformResult.map as SourceMapInput, options.map], () => null); + + await fs.writeFile(outputPath + '.map', JSON.stringify(outputMap)); } } @@ -730,37 +224,39 @@ async function inlineLocalesDirect(ast: ParseResult, options: InlineOptions) { } const { default: generate } = await import('@babel/generator'); - - const utils = await import('@angular/localize/src/tools/src/source_file_utils'); - const localizeDiag = await import('@angular/localize/src/tools/src/diagnostics'); - + const localizeDiag = await loadLocalizeTools(); const diagnostics = new localizeDiag.Diagnostics(); - const positions = findLocalizePositions(ast, options, utils); + const positions = findLocalizePositions(ast, options, localizeDiag); if (positions.length === 0 && !options.setLocale) { return inlineCopyOnly(options); } - const inputMap = options.map && (JSON.parse(options.map) as RawSourceMap); + const inputMap = !!options.map && (JSON.parse(options.map) as { sourceRoot?: string }); // Cleanup source root otherwise it will be added to each source entry const mapSourceRoot = inputMap && inputMap.sourceRoot; if (inputMap) { delete inputMap.sourceRoot; } + // Load Webpack only when needed + if (webpackSources === undefined) { + webpackSources = (await import('webpack')).sources; + } + const { ConcatSource, OriginalSource, ReplaceSource, SourceMapSource } = webpackSources; + for (const locale of i18n.inlineLocales) { const content = new ReplaceSource( inputMap - ? // tslint:disable-next-line: no-any - new SourceMapSource(options.code, options.filename, inputMap as any) + ? new SourceMapSource(options.code, options.filename, inputMap) : new OriginalSource(options.code, options.filename), ); const isSourceLocale = locale === i18n.sourceLocale; - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const translations: any = isSourceLocale ? {} : i18n.locales[locale].translation || {}; for (const position of positions) { - const translated = utils.translate( + const translated = localizeDiag.translate( diagnostics, translations, position.messageParts, @@ -768,54 +264,54 @@ async function inlineLocalesDirect(ast: ParseResult, options: InlineOptions) { isSourceLocale ? 'ignore' : options.missingTranslation || 'warning', ); - const expression = utils.buildLocalizeReplacement(translated[0], translated[1]); + const expression = localizeDiag.buildLocalizeReplacement(translated[0], translated[1]); const { code } = generate(expression); content.replace(position.start, position.end - 1, code); } - let outputSource: sources.Source = content; + let outputSource: import('webpack').sources.Source = content; if (options.setLocale) { - const setLocaleText = `var $localize=Object.assign(void 0===$localize?{}:$localize,{locale:"${locale}"});\n`; + const setLocaleText = `globalThis.$localize=Object.assign(globalThis.$localize || {},{locale:"${locale}"});\n`; // If locale data is provided, load it and prepend to file - let localeDataSource: sources.Source | null = null; + let localeDataSource; const localeDataPath = i18n.locales[locale] && i18n.locales[locale].dataPath; if (localeDataPath) { - const localeDataContent = await loadLocaleData(localeDataPath, true, options.es5); + const localeDataContent = await loadLocaleData(localeDataPath, true); localeDataSource = new OriginalSource(localeDataContent, path.basename(localeDataPath)); } outputSource = localeDataSource - // The semicolon ensures that there is no syntax error between statements - ? new ConcatSource(setLocaleText, localeDataSource, ';\n', content) + ? // The semicolon ensures that there is no syntax error between statements + new ConcatSource(setLocaleText, localeDataSource, ';\n', content) : new ConcatSource(setLocaleText, content); } const { source: outputCode, map: outputMap } = outputSource.sourceAndMap() as { source: string; - map: RawSourceMap; + map: { file: string; sourceRoot?: string }; }; const outputPath = path.join( options.outputPath, i18n.flatOutput ? '' : locale, options.filename, ); - fs.writeFileSync(outputPath, outputCode); + await fs.writeFile(outputPath, outputCode); if (inputMap && outputMap) { outputMap.file = options.filename; if (mapSourceRoot) { outputMap.sourceRoot = mapSourceRoot; } - fs.writeFileSync(outputPath + '.map', JSON.stringify(outputMap)); + await fs.writeFile(outputPath + '.map', JSON.stringify(outputMap)); } } return { file: options.filename, diagnostics: diagnostics.messages, count: positions.length }; } -function inlineCopyOnly(options: InlineOptions) { +async function inlineCopyOnly(options: InlineOptions) { if (!i18n) { throw new Error('i18n options are missing'); } @@ -826,9 +322,9 @@ function inlineCopyOnly(options: InlineOptions) { i18n.flatOutput ? '' : locale, options.filename, ); - fs.writeFileSync(outputPath, options.code); + await fs.writeFile(outputPath, options.code); if (options.map) { - fs.writeFileSync(outputPath + '.map', options.map); + await fs.writeFile(outputPath + '.map', options.map); } } @@ -838,7 +334,7 @@ function inlineCopyOnly(options: InlineOptions) { function findLocalizePositions( ast: ParseResult, options: InlineOptions, - utils: LocalizeUtilities, + utils: LocalizeUtilityModule, ): LocalizePosition[] { const positions: LocalizePosition[] = []; @@ -846,51 +342,28 @@ function findLocalizePositions( const { File } = require('@babel/core'); const file = new File({}, { code: options.code, ast }); - if (options.es5) { - traverse(file.ast, { - CallExpression(path) { - const callee = path.get('callee'); - if ( - callee.isIdentifier() && - callee.node.name === localizeName && - utils.isGlobalIdentifier(callee) - ) { - const [messageParts, expressions] = unwrapLocalizeCall(path, utils); - positions.push({ - // tslint:disable-next-line: no-non-null-assertion - start: path.node.start!, - // tslint:disable-next-line: no-non-null-assertion - end: path.node.end!, - messageParts, - expressions, - }); - } - }, - }); - } else { - traverse(file.ast, { - TaggedTemplateExpression(path) { - if (types.isIdentifier(path.node.tag) && path.node.tag.name === localizeName) { - const [messageParts, expressions] = unwrapTemplateLiteral(path, utils); - positions.push({ - // tslint:disable-next-line: no-non-null-assertion - start: path.node.start!, - // tslint:disable-next-line: no-non-null-assertion - end: path.node.end!, - messageParts, - expressions, - }); - } - }, - }); - } + traverse(file.ast, { + TaggedTemplateExpression(path) { + if (types.isIdentifier(path.node.tag) && path.node.tag.name === localizeName) { + const [messageParts, expressions] = unwrapTemplateLiteral(path, utils); + positions.push({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + start: path.node.start!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + end: path.node.end!, + messageParts, + expressions, + }); + } + }, + }); return positions; } function unwrapTemplateLiteral( path: NodePath, - utils: LocalizeUtilities, + utils: LocalizeUtilityModule, ): [TemplateStringsArray, types.Expression[]] { const [messageParts] = utils.unwrapMessagePartsFromTemplateLiteral( path.get('quasi').get('quasis'), @@ -902,7 +375,7 @@ function unwrapTemplateLiteral( function unwrapLocalizeCall( path: NodePath, - utils: LocalizeUtilities, + utils: LocalizeUtilityModule, ): [TemplateStringsArray, types.Expression[]] { const [messageParts] = utils.unwrapMessagePartsFromLocalizeCall(path); const [expressions] = utils.unwrapSubstitutionsFromLocalizeCall(path); @@ -910,15 +383,15 @@ function unwrapLocalizeCall( return [messageParts, expressions]; } -async function loadLocaleData(path: string, optimize: boolean, es5: boolean): Promise { +async function loadLocaleData(path: string, optimize: boolean): Promise { // The path is validated during option processing before the build starts - const content = fs.readFileSync(path, 'utf8'); + const content = await fs.readFile(path, 'utf8'); // Downlevel and optimize the data const transformResult = await transformAsync(content, { filename: path, // The types do not include the false option even though it is valid - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any inputSourceMap: false as any, babelrc: false, configFile: false, @@ -927,8 +400,7 @@ async function loadLocaleData(path: string, optimize: boolean, es5: boolean): Pr require.resolve('@babel/preset-env'), { bugfixes: true, - // IE 11 is the oldest supported browser - targets: es5 ? { ie: '11' } : { esmodules: true }, + targets: { esmodules: true }, }, ], ], diff --git a/packages/angular_devkit/build_angular/src/utils/purge-cache.ts b/packages/angular_devkit/build_angular/src/utils/purge-cache.ts new file mode 100644 index 000000000000..d62717faf360 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/purge-cache.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import { existsSync, promises as fsPromises } from 'fs'; +import { join } from 'path'; +import { normalizeCacheOptions } from './normalize-cache'; + +/** Delete stale cache directories used by previous versions of build-angular. */ +export async function purgeStaleBuildCache(context: BuilderContext): Promise { + const projectName = context.target?.project; + if (!projectName) { + return; + } + + const metadata = await context.getProjectMetadata(projectName); + const { basePath, path, enabled } = normalizeCacheOptions(metadata, context.workspaceRoot); + + if (!enabled || !existsSync(basePath)) { + return; + } + + const entriesToDelete = (await fsPromises.readdir(basePath, { withFileTypes: true })) + .filter((d) => join(basePath, d.name) !== path && d.isDirectory()) + .map((d) => { + const subPath = join(basePath, d.name); + + return fsPromises + .rm(subPath, { force: true, recursive: true, maxRetries: 3 }) + .catch(() => void 0); + }); + + await Promise.all(entriesToDelete); +} diff --git a/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts b/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts index 9d58349af889..0fabccafb863 100644 --- a/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts +++ b/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts @@ -1,13 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { ParsedConfiguration } from '@angular/compiler-cli'; +import type { ParsedConfiguration } from '@angular/compiler-cli'; import * as path from 'path'; +import { loadEsmModule } from './load-esm'; /** * Reads and parses a given TsConfig file. @@ -16,18 +17,22 @@ import * as path from 'path'; * @param workspaceRoot - workspaceRoot root location when provided * it will resolve 'tsconfigPath' from this path. */ -export function readTsconfig(tsconfigPath: string, workspaceRoot?: string): ParsedConfiguration { - const tsConfigFullPath = workspaceRoot - ? path.resolve(workspaceRoot, tsconfigPath) - : tsconfigPath; +export async function readTsconfig( + tsconfigPath: string, + workspaceRoot?: string, +): Promise { + const tsConfigFullPath = workspaceRoot ? path.resolve(workspaceRoot, tsconfigPath) : tsconfigPath; - // We use 'ng' instead of 'ts' here because 'ts' is not aware of 'angularCompilerOptions' - // and will not merged them if they are at un upper level tsconfig file when using `extends`. - const ng: typeof import('@angular/compiler-cli') = require('@angular/compiler-cli'); + // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + const { formatDiagnostics, readConfiguration } = await loadEsmModule< + typeof import('@angular/compiler-cli') + >('@angular/compiler-cli'); - const configResult = ng.readConfiguration(tsConfigFullPath); + const configResult = readConfiguration(tsConfigFullPath); if (configResult.errors && configResult.errors.length) { - throw new Error(ng.formatDiagnostics(configResult.errors)); + throw new Error(formatDiagnostics(configResult.errors)); } return configResult; diff --git a/packages/angular_devkit/build_angular/src/utils/run-module-as-observable-fork.ts b/packages/angular_devkit/build_angular/src/utils/run-module-as-observable-fork.ts index ab13efdb0e2b..37b497fe7cac 100644 --- a/packages/angular_devkit/build_angular/src/utils/run-module-as-observable-fork.ts +++ b/packages/angular_devkit/build_angular/src/utils/run-module-as-observable-fork.ts @@ -1,25 +1,26 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderOutput } from '@angular-devkit/architect'; import { ForkOptions, fork } from 'child_process'; import { resolve } from 'path'; import { Observable } from 'rxjs'; -const treeKill = require('tree-kill'); +const treeKill = require('tree-kill'); export function runModuleAsObservableFork( cwd: string, modulePath: string, exportName: string | undefined, - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any[], ): Observable { - return new Observable(obs => { + return new Observable((obs) => { const workerPath: string = resolve(__dirname, './run-module-worker.js'); const debugArgRegex = /--inspect(?:-brk|-port)?|--debug(?:-brk|-port)/; @@ -28,10 +29,10 @@ export function runModuleAsObservableFork( // Workaround for https://github.com/nodejs/node/issues/9435 return !debugArgRegex.test(arg); }); - const forkOptions: ForkOptions = { + const forkOptions: ForkOptions = ({ cwd, execArgv, - } as {} as ForkOptions; + } as {}) as ForkOptions; // TODO: support passing in a logger to use as stdio streams // if (logger) { diff --git a/packages/angular_devkit/build_angular/src/utils/run-module-worker.js b/packages/angular_devkit/build_angular/src/utils/run-module-worker.js index 2370abead79b..d1be8e869590 100644 --- a/packages/angular_devkit/build_angular/src/utils/run-module-worker.js +++ b/packages/angular_devkit/build_angular/src/utils/run-module-worker.js @@ -1,11 +1,10 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - process.on('message', (message) => { // Only process messages with the hash in 'run-module-as-observable-fork.ts'. if (message.hash === '5d4b9a5c0a4e0f9977598437b0e85bcc') { @@ -17,4 +16,3 @@ process.on('message', (message) => { } } }); - diff --git a/packages/angular_devkit/build_angular/src/utils/service-worker.ts b/packages/angular_devkit/build_angular/src/utils/service-worker.ts index 10eb38973855..1d3b1bd1eb36 100644 --- a/packages/angular_devkit/build_angular/src/utils/service-worker.ts +++ b/packages/angular_devkit/build_angular/src/utils/service-worker.ts @@ -1,41 +1,38 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Path, getSystemPath, normalize } from '@angular-devkit/core'; -import { Config, Filesystem, Generator } from '@angular/service-worker/config'; + +import type { Config, Filesystem } from '@angular/service-worker/config'; import * as crypto from 'crypto'; -import { constants as fsConstants, createReadStream, promises as fs } from 'fs'; +import { constants as fsConstants, promises as fsPromises } from 'fs'; import * as path from 'path'; -import { pipeline } from 'stream'; +import { assertIsError } from './error'; +import { loadEsmModule } from './load-esm'; class CliFilesystem implements Filesystem { - constructor(private base: string) {} + constructor(private fs: typeof fsPromises, private base: string) {} list(dir: string): Promise { return this._recursiveList(this._resolve(dir), []); } read(file: string): Promise { - return fs.readFile(this._resolve(file), 'utf-8'); + return this.fs.readFile(this._resolve(file), 'utf-8'); } - hash(file: string): Promise { - return new Promise((resolve, reject) => { - const hash = crypto.createHash('sha1').setEncoding('hex'); - pipeline( - createReadStream(this._resolve(file)), - hash, - (error) => error ? reject(error) : resolve(hash.read()), - ); - }); + async hash(file: string): Promise { + return crypto + .createHash('sha1') + .update(await this.fs.readFile(this._resolve(file))) + .digest('hex'); } - write(file: string, content: string): Promise { - return fs.writeFile(this._resolve(file), content); + write(_file: string, _content: string): never { + throw new Error('This should never happen.'); } private _resolve(file: string): string { @@ -44,12 +41,15 @@ class CliFilesystem implements Filesystem { private async _recursiveList(dir: string, items: string[]): Promise { const subdirectories = []; - for await (const entry of await fs.opendir(dir)) { - if (entry.isFile()) { + for (const entry of await this.fs.readdir(dir)) { + const entryPath = path.join(dir, entry); + const stats = await this.fs.stat(entryPath); + + if (stats.isFile()) { // Uses posix paths since the service worker expects URLs - items.push('/' + path.posix.relative(this.base, path.posix.join(dir, entry.name))); - } else if (entry.isDirectory()) { - subdirectories.push(path.join(dir, entry.name)); + items.push('/' + path.relative(this.base, entryPath).replace(/\\/g, '/')); + } else if (stats.isDirectory()) { + subdirectories.push(entryPath); } } @@ -62,41 +62,30 @@ class CliFilesystem implements Filesystem { } export async function augmentAppWithServiceWorker( - projectRoot: Path, - appRoot: Path, - outputPath: Path, + appRoot: string, + workspaceRoot: string, + outputPath: string, baseHref: string, ngswConfigPath?: string, + inputputFileSystem = fsPromises, + outputFileSystem = fsPromises, ): Promise { - const distPath = getSystemPath(normalize(outputPath)); - const systemProjectRoot = getSystemPath(projectRoot); - - // Find the service worker package - const workerPath = require.resolve('@angular/service-worker/ngsw-worker.js', { - paths: [systemProjectRoot], - }); - const swConfigPath = require.resolve('@angular/service-worker/config', { - paths: [systemProjectRoot], - }); - // Determine the configuration file path - let configPath; - if (ngswConfigPath) { - configPath = getSystemPath(normalize(ngswConfigPath)); - } else { - configPath = path.join(getSystemPath(appRoot), 'ngsw-config.json'); - } + const configPath = ngswConfigPath + ? path.join(workspaceRoot, ngswConfigPath) + : path.join(appRoot, 'ngsw-config.json'); // Read the configuration file let config: Config | undefined; try { - const configurationData = await fs.readFile(configPath, 'utf-8'); + const configurationData = await inputputFileSystem.readFile(configPath, 'utf-8'); config = JSON.parse(configurationData) as Config; } catch (error) { + assertIsError(error); if (error.code === 'ENOENT') { throw new Error( 'Error: Expected to find an ngsw-config.json configuration file' + - ` in the ${getSystemPath(appRoot)} folder. Either provide one or` + + ` in the ${appRoot} folder. Either provide one or` + ' disable Service Worker in the angular.json configuration file.', ); } else { @@ -104,36 +93,95 @@ export async function augmentAppWithServiceWorker( } } + return augmentAppWithServiceWorkerCore( + config, + outputPath, + baseHref, + inputputFileSystem, + outputFileSystem, + ); +} + +// This is currently used by the esbuild-based builder +export async function augmentAppWithServiceWorkerEsbuild( + workspaceRoot: string, + configPath: string, + outputPath: string, + baseHref: string, +): Promise { + // Read the configuration file + let config: Config | undefined; + try { + const configurationData = await fsPromises.readFile(configPath, 'utf-8'); + config = JSON.parse(configurationData) as Config; + } catch (error) { + assertIsError(error); + if (error.code === 'ENOENT') { + // TODO: Generate an error object that can be consumed by the esbuild-based builder + const message = `Service worker configuration file "${path.relative( + workspaceRoot, + configPath, + )}" could not be found.`; + throw new Error(message); + } else { + throw error; + } + } + + // TODO: Return the output files and any errors/warnings + return augmentAppWithServiceWorkerCore(config, outputPath, baseHref); +} + +export async function augmentAppWithServiceWorkerCore( + config: Config, + outputPath: string, + baseHref: string, + inputputFileSystem = fsPromises, + outputFileSystem = fsPromises, +): Promise { + // Load ESM `@angular/service-worker/config` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + const GeneratorConstructor = ( + await loadEsmModule( + '@angular/service-worker/config', + ) + ).Generator; + // Generate the manifest - const GeneratorConstructor = require(swConfigPath).Generator as typeof Generator; - const generator = new GeneratorConstructor(new CliFilesystem(distPath), baseHref); + const generator = new GeneratorConstructor( + new CliFilesystem(outputFileSystem, outputPath), + baseHref, + ); const output = await generator.process(config); // Write the manifest const manifest = JSON.stringify(output, null, 2); - await fs.writeFile(path.join(distPath, 'ngsw.json'), manifest); + await outputFileSystem.writeFile(path.join(outputPath, 'ngsw.json'), manifest); + + // Find the service worker package + const workerPath = require.resolve('@angular/service-worker/ngsw-worker.js'); + + const copy = async (src: string, dest: string): Promise => { + const resolvedDest = path.join(outputPath, dest); + + return inputputFileSystem === outputFileSystem + ? // Native FS (Builder). + inputputFileSystem.copyFile(workerPath, resolvedDest, fsConstants.COPYFILE_FICLONE) + : // memfs (Webpack): Read the file from the input FS (disk) and write it to the output FS (memory). + outputFileSystem.writeFile(resolvedDest, await inputputFileSystem.readFile(src)); + }; // Write the worker code - await fs.copyFile( - workerPath, - path.join(distPath, 'ngsw-worker.js'), - fsConstants.COPYFILE_FICLONE, - ); + await copy(workerPath, 'ngsw-worker.js'); // If present, write the safety worker code - const safetyPath = path.join(path.dirname(workerPath), 'safety-worker.js'); try { - await fs.copyFile( - safetyPath, - path.join(distPath, 'worker-basic.min.js'), - fsConstants.COPYFILE_FICLONE, - ); - await fs.copyFile( - safetyPath, - path.join(distPath, 'safety-worker.js'), - fsConstants.COPYFILE_FICLONE, - ); + const safetyPath = path.join(path.dirname(workerPath), 'safety-worker.js'); + await copy(safetyPath, 'worker-basic.min.js'); + await copy(safetyPath, 'safety-worker.js'); } catch (error) { + assertIsError(error); if (error.code !== 'ENOENT') { throw error; } diff --git a/packages/angular_devkit/build_angular/src/utils/spinner.ts b/packages/angular_devkit/build_angular/src/utils/spinner.ts index 35c16486832d..4a0e3deb587a 100644 --- a/packages/angular_devkit/build_angular/src/utils/spinner.ts +++ b/packages/angular_devkit/build_angular/src/utils/spinner.ts @@ -1,19 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as ora from 'ora'; +import ora from 'ora'; import { colors } from './color'; +import { isTTY } from './tty'; export class Spinner { private readonly spinner: ora.Ora; /** When false, only fail messages will be displayed. */ enabled = true; + readonly #isTTY = isTTY(); constructor(text?: string) { this.spinner = ora({ @@ -22,6 +24,7 @@ export class Spinner { // when the underlying process is sync. hideCursor: false, discardStdin: false, + isEnabled: this.#isTTY, }); } @@ -29,6 +32,10 @@ export class Spinner { this.spinner.text = text; } + get isSpinning(): boolean { + return this.spinner.isSpinning || !this.#isTTY; + } + succeed(text?: string): void { if (this.enabled) { this.spinner.succeed(text); diff --git a/packages/angular_devkit/build_angular/src/utils/strip-bom.ts b/packages/angular_devkit/build_angular/src/utils/strip-bom.ts index f4aba38228b3..7cc13e4748c4 100644 --- a/packages/angular_devkit/build_angular/src/utils/strip-bom.ts +++ b/packages/angular_devkit/build_angular/src/utils/strip-bom.ts @@ -1,11 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable + // TODO: cleanup this file, it's copied as is from Angular CLI. // Strip BOM from file data. diff --git a/packages/angular_devkit/build_angular/src/utils/supported-browsers.ts b/packages/angular_devkit/build_angular/src/utils/supported-browsers.ts new file mode 100644 index 000000000000..2aa537e8b2a1 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/supported-browsers.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { logging } from '@angular-devkit/core'; +import browserslist from 'browserslist'; + +export function getSupportedBrowsers(projectRoot: string, logger: logging.LoggerApi): string[] { + browserslist.defaults = [ + 'last 1 Chrome version', + 'last 1 Firefox version', + 'last 2 Edge major versions', + 'last 2 Safari major versions', + 'last 2 iOS major versions', + 'Firefox ESR', + ]; + + // Get browsers from config or default. + const browsersFromConfigOrDefault = new Set(browserslist(undefined, { path: projectRoot })); + + // Get browsers that support ES6 modules. + const browsersThatSupportEs6 = new Set(browserslist('supports es6-module')); + + const unsupportedBrowsers: string[] = []; + for (const browser of browsersFromConfigOrDefault) { + if (!browsersThatSupportEs6.has(browser)) { + browsersFromConfigOrDefault.delete(browser); + unsupportedBrowsers.push(browser); + } + } + + if (unsupportedBrowsers.length) { + logger.warn( + `One or more browsers which are configured in the project's Browserslist configuration ` + + 'will be ignored as ES5 output is not supported by the Angular CLI.\n' + + `Ignored browsers: ${unsupportedBrowsers.join(', ')}`, + ); + } + + return Array.from(browsersFromConfigOrDefault); +} diff --git a/packages/angular_devkit/build_angular/src/utils/tty.ts b/packages/angular_devkit/build_angular/src/utils/tty.ts index dd5931e26fb6..1e5658ebfd57 100644 --- a/packages/angular_devkit/build_angular/src/utils/tty.ts +++ b/packages/angular_devkit/build_angular/src/utils/tty.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license diff --git a/packages/angular_devkit/build_angular/src/utils/url.ts b/packages/angular_devkit/build_angular/src/utils/url.ts index 39b047b407bb..dcf713db3fe1 100644 --- a/packages/angular_devkit/build_angular/src/utils/url.ts +++ b/packages/angular_devkit/build_angular/src/utils/url.ts @@ -1,12 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - export function urlJoin(...parts: string[]): string { const [p, ...rest] = parts; diff --git a/packages/angular_devkit/build_angular/src/utils/url_spec.ts b/packages/angular_devkit/build_angular/src/utils/url_spec.ts index ca0d7935a741..7eef5cc4d070 100644 --- a/packages/angular_devkit/build_angular/src/utils/url_spec.ts +++ b/packages/angular_devkit/build_angular/src/utils/url_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { urlJoin } from './url'; describe('urlJoin', () => { diff --git a/packages/angular_devkit/build_angular/src/utils/version.ts b/packages/angular_devkit/build_angular/src/utils/version.ts index 0217bcca6147..dd2f0e14fdf9 100644 --- a/packages/angular_devkit/build_angular/src/utils/version.ts +++ b/packages/angular_devkit/build_angular/src/utils/version.ts @@ -1,36 +1,36 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { logging, tags } from '@angular-devkit/core'; -import { SemVer, gte, satisfies } from 'semver'; -export function assertCompatibleAngularVersion(projectRoot: string, logger: logging.LoggerApi) { +/* eslint-disable no-console */ + +import { tags } from '@angular-devkit/core'; +import { SemVer, satisfies } from 'semver'; + +export function assertCompatibleAngularVersion(projectRoot: string): void | never { let angularCliPkgJson; let angularPkgJson; - let rxjsPkgJson; const resolveOptions = { paths: [projectRoot] }; try { const angularPackagePath = require.resolve('@angular/core/package.json', resolveOptions); - const rxjsPackagePath = require.resolve('rxjs/package.json', resolveOptions); angularPkgJson = require(angularPackagePath); - rxjsPkgJson = require(rxjsPackagePath); } catch { - logger.error(tags.stripIndents` - You seem to not be depending on "@angular/core" and/or "rxjs". This is an error. + console.error(tags.stripIndents` + You seem to not be depending on "@angular/core". This is an error. `); process.exit(2); } - if (!(angularPkgJson && angularPkgJson['version'] && rxjsPkgJson && rxjsPkgJson['version'])) { - logger.error(tags.stripIndents` - Cannot determine versions of "@angular/core" and/or "rxjs". + if (!(angularPkgJson && angularPkgJson['version'])) { + console.error(tags.stripIndents` + Cannot determine versions of "@angular/core". This likely means your local installation is broken. Please reinstall your packages. `); @@ -55,14 +55,12 @@ export function assertCompatibleAngularVersion(projectRoot: string, logger: logg return; } + const supportedAngularSemver = + require('../../package.json')['peerDependencies']['@angular/compiler-cli']; const angularVersion = new SemVer(angularPkgJson['version']); - const cliMajor = new SemVer(angularCliPkgJson['version']).major; - // e.g. CLI 8.0 supports '>=8.0.0 <9.0.0', including pre-releases (next, rcs, snapshots) - // of both 8 and 9. - const supportedAngularSemver = `^${cliMajor}.0.0-next || >=${cliMajor}.0.0 <${cliMajor + 1}.0.0`; if (!satisfies(angularVersion, supportedAngularSemver, { includePrerelease: true })) { - logger.error( + console.error( tags.stripIndents` This version of CLI is only compatible with Angular versions ${supportedAngularSemver}, but Angular version ${angularVersion} was found instead. diff --git a/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts b/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts index d8831849c6fa..abdcfa35c863 100644 --- a/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts +++ b/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts @@ -1,26 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderContext } from '@angular-devkit/architect'; -import { - getSystemPath, - logging, - normalize, - resolve, -} from '@angular-devkit/core'; +import { logging } from '@angular-devkit/core'; import * as path from 'path'; -import * as webpack from 'webpack'; +import { Configuration, javascript } from 'webpack'; import { merge as webpackMerge } from 'webpack-merge'; -import { Schema as BrowserBuilderSchema } from '../browser/schema'; -import { - NormalizedBrowserBuilderSchema, - defaultProgress, - normalizeBrowserSchema, -} from '../utils'; +import { Schema as BrowserBuilderSchema } from '../builders/browser/schema'; +import { NormalizedBrowserBuilderSchema, defaultProgress, normalizeBrowserSchema } from '../utils'; import { WebpackConfigOptions } from '../utils/build-options'; import { readTsconfig } from '../utils/read-tsconfig'; import { BuilderWatchPlugin, BuilderWatcherFactory } from '../webpack/plugins/builder-watch-plugin'; @@ -28,25 +20,27 @@ import { I18nOptions, configureI18nBuild } from './i18n-options'; export type BrowserWebpackConfigOptions = WebpackConfigOptions; +export type WebpackPartialGenerator = ( + configurationOptions: BrowserWebpackConfigOptions, +) => (Promise | Configuration)[]; + export async function generateWebpackConfig( workspaceRoot: string, projectRoot: string, sourceRoot: string | undefined, + projectName: string, options: NormalizedBrowserBuilderSchema, - webpackPartialGenerator: (wco: BrowserWebpackConfigOptions) => webpack.Configuration[], + webpackPartialGenerator: WebpackPartialGenerator, logger: logging.LoggerApi, extraBuildOptions: Partial, -): Promise { +): Promise { // Ensure Build Optimizer is only used with AOT. if (options.buildOptimizer && !options.aot) { throw new Error(`The 'buildOptimizer' option cannot be used without 'aot'.`); } const tsConfigPath = path.resolve(workspaceRoot, options.tsConfig); - const tsConfig = readTsconfig(tsConfigPath); - - const ts = await import('typescript'); - const scriptTarget = tsConfig.options.target || ts.ScriptTarget.ES5; + const tsConfig = await readTsconfig(tsConfigPath); const buildOptions: NormalizedBrowserBuilderSchema = { ...options, ...extraBuildOptions }; const wco: BrowserWebpackConfigOptions = { @@ -57,12 +51,13 @@ export async function generateWebpackConfig( buildOptions, tsConfig, tsConfigPath, - scriptTarget, + projectName, }; wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress); - const webpackConfig = webpackMerge(webpackPartialGenerator(wco)); + const partials = await Promise.all(webpackPartialGenerator(wco)); + const webpackConfig = webpackMerge(partials); return webpackConfig; } @@ -70,14 +65,21 @@ export async function generateWebpackConfig( export async function generateI18nBrowserWebpackConfigFromContext( options: BrowserBuilderSchema, context: BuilderContext, - webpackPartialGenerator: (wco: BrowserWebpackConfigOptions) => webpack.Configuration[], + webpackPartialGenerator: WebpackPartialGenerator, extraBuildOptions: Partial = {}, -): Promise<{ config: webpack.Configuration; projectRoot: string; projectSourceRoot?: string, i18n: I18nOptions }> { +): Promise<{ + config: Configuration; + projectRoot: string; + projectSourceRoot?: string; + i18n: I18nOptions; +}> { const { buildOptions, i18n } = await configureI18nBuild(context, options); const result = await generateBrowserWebpackConfigFromContext( buildOptions, context, - webpackPartialGenerator, + (wco) => { + return webpackPartialGenerator(wco); + }, extraBuildOptions, ); const config = result.config; @@ -90,14 +92,14 @@ export async function generateI18nBrowserWebpackConfigFromContext( } if (Array.isArray(config.resolve.alias)) { config.resolve.alias.push({ - alias: '@angular/localize/init', - name: require.resolve('./empty.js'), + name: '@angular/localize/init', + alias: false, }); } else { if (!config.resolve.alias) { config.resolve.alias = {}; } - config.resolve.alias['@angular/localize/init'] = require.resolve('./empty.js'); + config.resolve.alias['@angular/localize/init'] = false; } } @@ -106,25 +108,14 @@ export async function generateI18nBrowserWebpackConfigFromContext( (data, locale) => data + locale.files.map((file) => file.integrity || '').join('|'), '', ); - if (!config.plugins) { - config.plugins = []; - } + + config.plugins ??= []; config.plugins.push({ - apply(compiler: webpack.Compiler) { - compiler.hooks.compilation.tap('build-angular', compilation => { - // Webpack typings do not contain template hashForChunk hook - // tslint:disable-next-line: no-any - (compilation.mainTemplate.hooks as any).hashForChunk.tap( - 'build-angular', - (hash: { update(data: string): void }) => { - hash.update('$localize' + i18nHash); - }, - ); - // Webpack typings do not contain hooks property - // tslint:disable-next-line: no-any - (compilation.chunkTemplate as any).hooks.hashForChunk.tap( + apply(compiler) { + compiler.hooks.compilation.tap('build-angular', (compilation) => { + javascript.JavascriptModulesPlugin.getCompilationHooks(compilation).chunkHash.tap( 'build-angular', - (hash: { update(data: string): void }) => { + (_, hash) => { hash.update('$localize' + i18nHash); }, ); @@ -138,33 +129,34 @@ export async function generateI18nBrowserWebpackConfigFromContext( export async function generateBrowserWebpackConfigFromContext( options: BrowserBuilderSchema, context: BuilderContext, - webpackPartialGenerator: (wco: BrowserWebpackConfigOptions) => webpack.Configuration[], + webpackPartialGenerator: WebpackPartialGenerator, extraBuildOptions: Partial = {}, -): Promise<{ config: webpack.Configuration; projectRoot: string; projectSourceRoot?: string }> { +): Promise<{ config: Configuration; projectRoot: string; projectSourceRoot?: string }> { const projectName = context.target && context.target.project; if (!projectName) { throw new Error('The builder requires a target.'); } - const workspaceRoot = normalize(context.workspaceRoot); + const workspaceRoot = context.workspaceRoot; const projectMetadata = await context.getProjectMetadata(projectName); - const projectRoot = resolve(workspaceRoot, normalize((projectMetadata.root as string) || '')); - const projectSourceRoot = projectMetadata.sourceRoot as string | undefined; - const sourceRoot = projectSourceRoot - ? resolve(workspaceRoot, normalize(projectSourceRoot)) - : undefined; + const projectRoot = path.join(workspaceRoot, (projectMetadata.root as string | undefined) ?? ''); + const sourceRoot = projectMetadata.sourceRoot as string | undefined; + const projectSourceRoot = sourceRoot ? path.join(workspaceRoot, sourceRoot) : undefined; const normalizedOptions = normalizeBrowserSchema( workspaceRoot, projectRoot, - sourceRoot, + projectSourceRoot, options, + projectMetadata, + context.logger, ); const config = await generateWebpackConfig( - getSystemPath(workspaceRoot), - getSystemPath(projectRoot), - sourceRoot && getSystemPath(sourceRoot), + workspaceRoot, + projectRoot, + projectSourceRoot, + projectName, normalizedOptions, webpackPartialGenerator, context.logger, @@ -173,9 +165,11 @@ export async function generateBrowserWebpackConfigFromContext( // If builder watch support is present in the context, add watch plugin // This is internal only and currently only used for testing - const watcherFactory = (context as { - watcherFactory?: BuilderWatcherFactory; - }).watcherFactory; + const watcherFactory = ( + context as { + watcherFactory?: BuilderWatcherFactory; + } + ).watcherFactory; if (watcherFactory) { if (!config.plugins) { config.plugins = []; @@ -185,8 +179,8 @@ export async function generateBrowserWebpackConfigFromContext( return { config, - projectRoot: getSystemPath(projectRoot), - projectSourceRoot: sourceRoot && getSystemPath(sourceRoot), + projectRoot, + projectSourceRoot, }; } diff --git a/packages/angular_devkit/build_angular/src/utils/webpack-diagnostics.ts b/packages/angular_devkit/build_angular/src/utils/webpack-diagnostics.ts index 4cccd20788dc..d04c34f3ef04 100644 --- a/packages/angular_devkit/build_angular/src/utils/webpack-diagnostics.ts +++ b/packages/angular_devkit/build_angular/src/utils/webpack-diagnostics.ts @@ -1,18 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license -*/ -import { Compilation, WebpackError } from 'webpack'; + */ -export function addWarning(compilation: Compilation, message: string): void { - compilation.warnings.push(new WebpackError(message)); +import type { Compilation } from 'webpack'; +export function addWarning(compilation: Compilation, message: string): void { + compilation.warnings.push(new compilation.compiler.webpack.WebpackError(message)); } export function addError(compilation: Compilation, message: string): void { - compilation.errors.push(new WebpackError(message)); - + compilation.errors.push(new compilation.compiler.webpack.WebpackError(message)); } diff --git a/packages/angular_devkit/build_angular/src/utils/workers.ts b/packages/angular_devkit/build_angular/src/utils/workers.ts deleted file mode 100644 index 83b4c778fff8..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/workers.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { cpus } from 'os'; -/** - * Use CPU count -1 with limit to 7 for workers not to clog the system. - * Some environments, like CircleCI which use Docker report a number of CPUs by the host and not the count of available. - * This cause `Error: Call retries were exceeded` errors when trying to use them. - * - * See: - * - * https://github.com/nodejs/node/issues/28762 - * - * https://github.com/webpack-contrib/terser-webpack-plugin/issues/143 - * - * https://github.com/angular/angular-cli/issues/16860#issuecomment-588828079 - * - */ -export const maxWorkers = Math.max(Math.min(cpus().length, 8) - 1, 1); diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/browser.ts b/packages/angular_devkit/build_angular/src/webpack/configs/browser.ts deleted file mode 100644 index 08d81c29a814..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/configs/browser.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { ScriptTarget } from 'typescript'; -import * as webpack from 'webpack'; -import { BuildBrowserFeatures } from '../../utils'; -import { WebpackConfigOptions } from '../../utils/build-options'; -import { CommonJsUsageWarnPlugin } from '../plugins'; -import { getSourceMapDevTool } from '../utils/helpers'; - -export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configuration { - const { buildOptions } = wco; - const { - crossOrigin = 'none', - subresourceIntegrity, - extractLicenses, - vendorChunk, - commonChunk, - allowedCommonJsDependencies, - } = buildOptions; - - const extraPlugins = []; - - const { - styles: stylesSourceMap, - scripts: scriptsSourceMap, - hidden: hiddenSourceMap, - } = buildOptions.sourceMap; - - if (subresourceIntegrity) { - const SubresourceIntegrityPlugin = require('webpack-subresource-integrity'); - extraPlugins.push(new SubresourceIntegrityPlugin({ - hashFuncNames: ['sha384'], - })); - } - - if (extractLicenses) { - const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin; - extraPlugins.push(new LicenseWebpackPlugin({ - stats: { - warnings: false, - errors: false, - }, - perChunkOutput: false, - outputFilename: '3rdpartylicenses.txt', - skipChildCompilers: true, - })); - } - - if (scriptsSourceMap || stylesSourceMap) { - extraPlugins.push(getSourceMapDevTool( - scriptsSourceMap, - stylesSourceMap, - buildOptions.differentialLoadingNeeded && !buildOptions.watch ? true : hiddenSourceMap, - false, - )); - } - - let crossOriginLoading: 'anonymous' | 'use-credentials' | false = false; - if (subresourceIntegrity && crossOrigin === 'none') { - crossOriginLoading = 'anonymous'; - } else if (crossOrigin !== 'none') { - crossOriginLoading = crossOrigin; - } - - const buildBrowserFeatures = new BuildBrowserFeatures( - wco.projectRoot, - ); - - return { - devtool: false, - resolve: { - mainFields: ['es2015', 'browser', 'module', 'main'], - }, - target: - wco.tsConfig.options.target === ScriptTarget.ES5 || buildBrowserFeatures.isEs5SupportNeeded() - ? ['web', 'es5'] - : 'web', - output: { - crossOriginLoading, - }, - optimization: { - runtimeChunk: 'single', - splitChunks: { - maxAsyncRequests: Infinity, - cacheGroups: { - default: !!commonChunk && { - chunks: 'async', - minChunks: 2, - priority: 10, - }, - common: !!commonChunk && { - name: 'common', - chunks: 'async', - minChunks: 2, - enforce: true, - priority: 5, - }, - vendors: false, - defaultVendors: !!vendorChunk && { - name: 'vendor', - chunks: (chunk) => chunk.name === 'main', - enforce: true, - test: /[\\/]node_modules[\\/]/, - }, - }, - }, - }, - plugins: [ - new CommonJsUsageWarnPlugin({ - allowedDependencies: allowedCommonJsDependencies, - }), - ...extraPlugins, - ], - node: false, - }; -} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts index c050e894e784..84fba1774ead 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts @@ -1,501 +1,484 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - BuildOptimizerWebpackPlugin, - buildOptimizerLoaderPath, -} from '@angular-devkit/build-optimizer'; -import * as CopyWebpackPlugin from 'copy-webpack-plugin'; -import { createWriteStream, existsSync, promises as fsPromises } from 'fs'; + +import { AngularWebpackLoaderPath } from '@ngtools/webpack'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; import * as path from 'path'; -import { ScriptTarget } from 'typescript'; import { Compiler, Configuration, ContextReplacementPlugin, - ProgressPlugin, RuleSetRule, - debug, + SourceMapDevToolPlugin, } from 'webpack'; -import { AssetPatternClass } from '../../browser/schema'; -import { BuildBrowserFeatures, maxWorkers } from '../../utils'; +import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity'; +import { AngularBabelLoaderOptions } from '../../babel/webpack-loader'; import { WebpackConfigOptions } from '../../utils/build-options'; -import { findCachePath } from '../../utils/cache-path'; -import { - allowMangle, - allowMinify, - cachingDisabled, - profilingEnabled, - shouldBeautify, -} from '../../utils/environment-options'; -import { findAllNodeModules } from '../../utils/find-up'; -import { Spinner } from '../../utils/spinner'; -import { addError } from '../../utils/webpack-diagnostics'; +import { allowMangle } from '../../utils/environment-options'; +import { loadEsmModule } from '../../utils/load-esm'; import { + CommonJsUsageWarnPlugin, DedupeModuleResolvePlugin, - OptimizeCssWebpackPlugin, + JavaScriptOptimizerPlugin, + JsonStatsPlugin, ScriptsWebpackPlugin, } from '../plugins'; -import { getEsVersionForFileName, getOutputHashFormat, getWatchOptions, normalizeExtraEntryPoints } from '../utils/helpers'; -import { IGNORE_WARNINGS } from '../utils/stats'; - -const TerserPlugin = require('terser-webpack-plugin'); - -// tslint:disable-next-line:no-big-function -export function getCommonConfig(wco: WebpackConfigOptions): Configuration { - const { root, projectRoot, buildOptions, tsConfig } = wco; +import { DevToolsIgnorePlugin } from '../plugins/devtools-ignore-plugin'; +import { NamedChunksPlugin } from '../plugins/named-chunks-plugin'; +import { OccurrencesPlugin } from '../plugins/occurrences-plugin'; +import { ProgressPlugin } from '../plugins/progress-plugin'; +import { TransferSizePlugin } from '../plugins/transfer-size-plugin'; +import { createIvyPlugin } from '../plugins/typescript'; +import { WatchFilesLogsPlugin } from '../plugins/watch-files-logs-plugin'; +import { + assetPatterns, + getCacheSettings, + getInstrumentationExcludedPaths, + getOutputHashFormat, + getStatsOptions, + globalScriptsByBundleName, + isPlatformServerInstalled, +} from '../utils/helpers'; + +const VENDORS_TEST = /[\\/]node_modules[\\/]/; + +// eslint-disable-next-line max-lines-per-function +export async function getCommonConfig(wco: WebpackConfigOptions): Promise { + const { root, projectRoot, buildOptions, tsConfig, projectName, sourceRoot, tsConfigPath } = wco; const { + cache, + codeCoverage, + crossOrigin = 'none', platform = 'browser', + aot = true, + codeCoverageExclude = [], + main, + polyfills, sourceMap: { styles: stylesSourceMap, scripts: scriptsSourceMap, vendor: vendorSourceMap, + hidden: hiddenSourceMap, }, - optimization: { - styles: stylesOptimization, - scripts: scriptsOptimization, - }, + optimization: { styles: stylesOptimization, scripts: scriptsOptimization }, + commonChunk, + vendorChunk, + subresourceIntegrity, + verbose, + poll, + webWorkerTsConfig, + externalDependencies = [], + allowedCommonJsDependencies, } = buildOptions; + const isPlatformServer = buildOptions.platform === 'server'; const extraPlugins: { apply(compiler: Compiler): void }[] = []; const extraRules: RuleSetRule[] = []; - const entryPoints: { [key: string]: [string, ...string[]] } = {}; + const entryPoints: Configuration['entry'] = {}; + + // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + const { + GLOBAL_DEFS_FOR_TERSER, + GLOBAL_DEFS_FOR_TERSER_WITH_AOT, + VERSION: NG_VERSION, + } = await loadEsmModule('@angular/compiler-cli'); // determine hashing format - const hashFormat = getOutputHashFormat(buildOptions.outputHashing || 'none'); + const hashFormat = getOutputHashFormat(buildOptions.outputHashing); + + if (buildOptions.progress) { + extraPlugins.push(new ProgressPlugin(platform)); + } - const targetInFileName = getEsVersionForFileName( - tsConfig.options.target, - buildOptions.differentialLoadingNeeded, + const localizePackageInitEntryPoint = '@angular/localize/init'; + const hasLocalizeType = tsConfig.options.types?.some( + (t) => t === '@angular/localize' || t === localizePackageInitEntryPoint, ); + if (hasLocalizeType) { + entryPoints['main'] = [localizePackageInitEntryPoint]; + } + if (buildOptions.main) { const mainPath = path.resolve(root, buildOptions.main); - entryPoints['main'] = [mainPath]; + if (Array.isArray(entryPoints['main'])) { + entryPoints['main'].push(mainPath); + } else { + entryPoints['main'] = [mainPath]; + } } - const differentialLoadingMode = buildOptions.differentialLoadingNeeded && !buildOptions.watch; - if (platform !== 'server') { - if (differentialLoadingMode || tsConfig.options.target === ScriptTarget.ES5) { - const buildBrowserFeatures = new BuildBrowserFeatures( - projectRoot, - ); + if (isPlatformServer) { + // Fixes Critical dependency: the request of a dependency is an expression + extraPlugins.push(new ContextReplacementPlugin(/@?hapi|express[\\/]/)); - if (buildBrowserFeatures.isEs5SupportNeeded()) { - const polyfillsChunkName = 'polyfills-es5'; - entryPoints[polyfillsChunkName] = [path.join(__dirname, '..', 'es5-polyfills.js')]; - - if (!buildOptions.aot) { - if (differentialLoadingMode) { - entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'jit-polyfills.js')); - } - entryPoints[polyfillsChunkName].push(path.join(__dirname, '..', 'es5-jit-polyfills.js')); - } - // If not performing a full differential build the polyfills need to be added to ES5 bundle - if (buildOptions.polyfills) { - entryPoints[polyfillsChunkName].push(path.resolve(root, buildOptions.polyfills)); - } - } + if (isPlatformServerInstalled(wco.root) && Array.isArray(entryPoints['main'])) { + // This import must come before any imports (direct or transitive) that rely on DOM built-ins being + // available, such as `@angular/elements`. + entryPoints['main'].unshift('@angular/platform-server/init'); } + } - if (buildOptions.polyfills) { - const projectPolyfills = path.resolve(root, buildOptions.polyfills); - if (entryPoints['polyfills']) { - entryPoints['polyfills'].push(projectPolyfills); - } else { - entryPoints['polyfills'] = [projectPolyfills]; - } + if (polyfills?.length) { + // `zone.js/testing` is a **special** polyfill because when not imported in the main it fails with the below errors: + // `Error: Expected to be running in 'ProxyZone', but it was not found.` + // This was also the reason why previously it was imported in `test.ts` as the first module. + // From Jia li: + // This is because the jasmine functions such as beforeEach/it will not be patched by zone.js since + // jasmine will not be loaded yet, so the ProxyZone will not be there. We have to load zone-testing.js after + // jasmine is ready. + // We could force loading 'zone.js/testing' prior to jasmine by changing the order of scripts in 'karma-context.html'. + // But this has it's own problems as zone.js needs to be loaded prior to jasmine due to patching of timing functions + // See: https://github.com/jasmine/jasmine/issues/1944 + // Thus the correct order is zone.js -> jasmine -> zone.js/testing. + const zoneTestingEntryPoint = 'zone.js/testing'; + const polyfillsExludingZoneTesting = polyfills.filter((p) => p !== zoneTestingEntryPoint); + + if (Array.isArray(entryPoints['polyfills'])) { + entryPoints['polyfills'].push(...polyfillsExludingZoneTesting); + } else { + entryPoints['polyfills'] = polyfillsExludingZoneTesting; } - if (!buildOptions.aot) { - const jitPolyfills = path.join(__dirname, '..', 'jit-polyfills.js'); - if (entryPoints['polyfills']) { - entryPoints['polyfills'].push(jitPolyfills); + if (polyfillsExludingZoneTesting.length !== polyfills.length) { + if (Array.isArray(entryPoints['main'])) { + entryPoints['main'].unshift(zoneTestingEntryPoint); } else { - entryPoints['polyfills'] = [jitPolyfills]; + entryPoints['main'] = [zoneTestingEntryPoint]; } } } - if (profilingEnabled) { + if (allowedCommonJsDependencies) { + // When this is not defined it means the builder doesn't support showing common js usages. + // When it does it will be an array. extraPlugins.push( - new debug.ProfilingPlugin({ - outputPath: path.resolve(root, 'chrome-profiler-events.json'), + new CommonJsUsageWarnPlugin({ + allowedDependencies: allowedCommonJsDependencies, }), ); } // process global scripts - const globalScriptsByBundleName = normalizeExtraEntryPoints( - buildOptions.scripts, - 'scripts', - ).reduce((prev: { bundleName: string; paths: string[]; inject: boolean }[], curr) => { - const { bundleName, inject, input } = curr; - let resolvedPath = path.resolve(root, input); - - if (!existsSync(resolvedPath)) { - try { - resolvedPath = require.resolve(input, { paths: [root] }); - } catch { - throw new Error(`Script file ${input} does not exist.`); - } - } - - const existingEntry = prev.find(el => el.bundleName === bundleName); - if (existingEntry) { - if (existingEntry.inject && !inject) { - // All entries have to be lazy for the bundle to be lazy. - throw new Error( - `The ${bundleName} bundle is mixing injected and non-injected scripts.`, - ); - } - - existingEntry.paths.push(resolvedPath); - } else { - prev.push({ - bundleName, - inject, - paths: [resolvedPath], - }); - } - - return prev; - }, []); - // Add a new asset for each entry. - for (const script of globalScriptsByBundleName) { + for (const { bundleName, inject, paths } of globalScriptsByBundleName(buildOptions.scripts)) { // Lazy scripts don't get a hash, otherwise they can't be loaded by name. - const hash = script.inject ? hashFormat.script : ''; - const bundleName = script.bundleName; - - extraPlugins.push(new ScriptsWebpackPlugin({ - name: bundleName, - sourceMap: scriptsSourceMap, - filename: `${path.basename(bundleName)}${hash}.js`, - scripts: script.paths, - basePath: projectRoot, - })); + const hash = inject ? hashFormat.script : ''; + + extraPlugins.push( + new ScriptsWebpackPlugin({ + name: bundleName, + sourceMap: scriptsSourceMap, + scripts: paths, + filename: `${path.basename(bundleName)}${hash}.js`, + basePath: root, + }), + ); } // process asset entries if (buildOptions.assets.length) { - const copyWebpackPluginPatterns = buildOptions.assets.map((asset: AssetPatternClass) => { - // Resolve input paths relative to workspace root and add slash at the end. - // tslint:disable-next-line: prefer-const - let { input, output, ignore = [], glob } = asset; - input = path.resolve(root, input).replace(/\\/g, '/'); - input = input.endsWith('/') ? input : input + '/'; - output = output.endsWith('/') ? output : output + '/'; - - if (output.startsWith('..')) { - throw new Error('An asset cannot be written to a location outside of the output path.'); - } + extraPlugins.push( + new CopyWebpackPlugin({ + patterns: assetPatterns(root, buildOptions.assets), + }), + ); + } - return { - context: input, - // Now we remove starting slash to make Webpack place it from the output root. - to: output.replace(/^\//, ''), - from: glob, - noErrorOnMissing: true, - force: true, - globOptions: { - dot: true, - followSymbolicLinks: !!asset.followSymlinks, - ignore: [ - '.gitkeep', - '**/.DS_Store', - '**/Thumbs.db', - // Negate patterns needs to be absolute because copy-webpack-plugin uses absolute globs which - // causes negate patterns not to match. - // See: https://github.com/webpack-contrib/copy-webpack-plugin/issues/498#issuecomment-639327909 - ...ignore, - ].map(i => path.posix.join(input, i)), + if (buildOptions.extractLicenses) { + const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin; + extraPlugins.push( + new LicenseWebpackPlugin({ + stats: { + warnings: false, + errors: false, }, - }; - }); - - extraPlugins.push(new CopyWebpackPlugin({ - patterns: copyWebpackPluginPatterns, - // The typings for copy-webpack-plugin use the old @types/webpack package - // tslint:disable-next-line: no-any - }) as any); + perChunkOutput: false, + outputFilename: '3rdpartylicenses.txt', + skipChildCompilers: true, + }), + ); } - if (buildOptions.progress) { - const spinner = new Spinner(); - - let previousPercentage: number | undefined; - extraPlugins.push(new ProgressPlugin({ - handler: (percentage: number, message: string) => { - if (previousPercentage === 1 && percentage !== 0) { - // In some scenarios in Webpack 5 percentage goes from 1 back to 0.99. - // Ex: 0.99 -> 1 -> 0.99 -> 1 - // This causes the "complete" message to be displayed multiple times. - - return; - } - - switch (percentage) { - case 0: - spinner.start(`Generating ${platform} application bundles...`); - break; - case 1: - spinner.succeed(`${platform.replace(/^\w/, s => s.toUpperCase())} application bundle generation complete.`); - break; - default: - spinner.text = `Generating ${platform} application bundles (phase: ${message})...`; - break; - } - - previousPercentage = percentage; - }, - })); - } + if (scriptsSourceMap || stylesSourceMap) { + const include = []; + if (scriptsSourceMap) { + include.push(/js$/); + } + + if (stylesSourceMap) { + include.push(/css$/); + } + + extraPlugins.push(new DevToolsIgnorePlugin()); - if (buildOptions.showCircularDependencies) { - const CircularDependencyPlugin = require('circular-dependency-plugin'); extraPlugins.push( - new CircularDependencyPlugin({ - exclude: /[\\\/]node_modules[\\\/]/, + new SourceMapDevToolPlugin({ + filename: '[file].map', + include, + // We want to set sourceRoot to `webpack:///` for non + // inline sourcemaps as otherwise paths to sourcemaps will be broken in browser + // `webpack:///` is needed for Visual Studio breakpoints to work properly as currently + // there is no way to set the 'webRoot' + sourceRoot: 'webpack:///', + moduleFilenameTemplate: '[resource-path]', + append: hiddenSourceMap ? false : undefined, }), ); } + if (verbose) { + extraPlugins.push(new WatchFilesLogsPlugin()); + } + if (buildOptions.statsJson) { extraPlugins.push( - new (class { - apply(compiler: Compiler) { - compiler.hooks.done.tapPromise('angular-cli-stats', async (stats) => { - const { stringifyStream } = await import('@discoveryjs/json-ext'); - const data = stats.toJson('verbose'); - const statsOutputPath = path.join(root, buildOptions.outputPath, 'stats.json'); - - try { - await fsPromises.mkdir(path.dirname(statsOutputPath), { recursive: true }); - - await new Promise((resolve, reject) => - stringifyStream(data) - .pipe(createWriteStream(statsOutputPath)) - .on('close', resolve) - .on('error', reject), - ); - } catch (error) { - addError( - stats.compilation, - `Unable to write stats file: ${error.message || 'unknown error'}`, - ); - } - }); - } - })(), + new JsonStatsPlugin(path.resolve(root, buildOptions.outputPath, 'stats.json')), ); } - if ((scriptsSourceMap || stylesSourceMap)) { + if (subresourceIntegrity) { + extraPlugins.push( + new SubresourceIntegrityPlugin({ + hashFuncNames: ['sha384'], + }), + ); + } + + if (scriptsSourceMap || stylesSourceMap) { extraRules.push({ - test: /\.m?js$/, - exclude: vendorSourceMap - ? undefined - : /[\\\/]node_modules[\\\/]/, + test: /\.[cm]?jsx?$/, enforce: 'pre', loader: require.resolve('source-map-loader'), + options: { + filterSourceMappingUrl: (_mapUri: string, resourcePath: string) => { + if (vendorSourceMap) { + // Consume all sourcemaps when vendor option is enabled. + return true; + } + + // Don't consume sourcemaps in node_modules when vendor is disabled. + // But, do consume local libraries sourcemaps. + return !resourcePath.includes('node_modules'); + }, + }, }); } - let buildOptimizerUseRule: RuleSetRule[] = []; - if (buildOptions.buildOptimizer) { - extraPlugins.push(new BuildOptimizerWebpackPlugin()); - buildOptimizerUseRule = [ - { - loader: buildOptimizerLoaderPath, - options: { sourceMap: scriptsSourceMap }, - }, - ]; + if (main || polyfills) { + extraRules.push({ + test: tsConfig.options.allowJs ? /\.[cm]?[tj]sx?$/ : /\.[cm]?tsx?$/, + loader: AngularWebpackLoaderPath, + // The below are known paths that are not part of the TypeScript compilation even when allowJs is enabled. + exclude: [ + /[\\/]node_modules[/\\](?:css-loader|mini-css-extract-plugin|webpack-dev-server|webpack)[/\\]/, + ], + }); + extraPlugins.push(createIvyPlugin(wco, aot, tsConfigPath)); } - const extraMinimizers = []; - if (stylesOptimization.minify) { - extraMinimizers.push( - new OptimizeCssWebpackPlugin({ - sourceMap: stylesSourceMap, - // component styles retain their original file name - test: file => /\.(?:css|scss|sass|less|styl)$/.test(file), - }), - ); + if (webWorkerTsConfig) { + extraPlugins.push(createIvyPlugin(wco, false, path.resolve(wco.root, webWorkerTsConfig))); } + const extraMinimizers = []; if (scriptsOptimization) { - const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = require('@angular/compiler-cli'); - const angularGlobalDefinitions = buildOptions.aot - ? GLOBAL_DEFS_FOR_TERSER_WITH_AOT - : GLOBAL_DEFS_FOR_TERSER; - - // TODO: Investigate why this fails for some packages: wco.supportES2015 ? 6 : 5; - const terserEcma = 5; - - const terserOptions = { - warnings: !!buildOptions.verbose, - safari10: true, - output: { - ecma: terserEcma, - // For differential loading, this is handled in the bundle processing. - ascii_only: !differentialLoadingMode, - // Default behavior (undefined value) is to keep only important comments (licenses, etc.) - comments: !buildOptions.extractLicenses && undefined, - webkit: true, - beautify: shouldBeautify, - wrap_func_args: false, - }, - // On server, we don't want to compress anything. We still set the ngDevMode = false for it - // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code - compress: - allowMinify && - (platform === 'server' - ? { - ecma: terserEcma, - global_defs: angularGlobalDefinitions, - keep_fnames: true, - } - : { - ecma: terserEcma, - pure_getters: buildOptions.buildOptimizer, - // PURE comments work best with 3 passes. - // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926. - passes: buildOptions.buildOptimizer ? 3 : 1, - global_defs: angularGlobalDefinitions, - pure_funcs: ['forwardRef'], - }), - // We also want to avoid mangling on server. - // Name mangling is handled within the browser builder - mangle: allowMangle && platform !== 'server' && !differentialLoadingMode, - }; - - const globalScriptsNames = globalScriptsByBundleName.map(s => s.bundleName); extraMinimizers.push( - new TerserPlugin({ - sourceMap: scriptsSourceMap, - parallel: maxWorkers, - cache: !cachingDisabled && findCachePath('terser-webpack'), - extractComments: false, - exclude: globalScriptsNames, - terserOptions, - }), - // Script bundles are fully optimized here in one step since they are never downleveled. - // They are shared between ES2015 & ES5 outputs so must support ES5. - new TerserPlugin({ - sourceMap: scriptsSourceMap, - parallel: maxWorkers, - cache: !cachingDisabled && findCachePath('terser-webpack'), - extractComments: false, - include: globalScriptsNames, - terserOptions: { - ...terserOptions, - compress: allowMinify && { - ...terserOptions.compress, - ecma: 5, - }, - output: { - ...terserOptions.output, - ecma: 5, - }, - mangle: allowMangle && platform !== 'server', - }, + new JavaScriptOptimizerPlugin({ + define: buildOptions.aot ? GLOBAL_DEFS_FOR_TERSER_WITH_AOT : GLOBAL_DEFS_FOR_TERSER, + sourcemap: scriptsSourceMap, + supportedBrowsers: buildOptions.supportedBrowsers, + keepIdentifierNames: !allowMangle || isPlatformServer, + keepNames: isPlatformServer, + removeLicenses: buildOptions.extractLicenses, + advanced: buildOptions.buildOptimizer, }), ); } + if (platform === 'browser' && (scriptsOptimization || stylesOptimization.minify)) { + extraMinimizers.push(new TransferSizePlugin()); + } + + let crossOriginLoading: NonNullable['crossOriginLoading'] = false; + if (subresourceIntegrity && crossOrigin === 'none') { + crossOriginLoading = 'anonymous'; + } else if (crossOrigin !== 'none') { + crossOriginLoading = crossOrigin; + } + return { mode: scriptsOptimization || stylesOptimization.minify ? 'production' : 'development', devtool: false, + target: [isPlatformServer ? 'node' : 'web', 'es2015'], profile: buildOptions.statsJson, resolve: { roots: [projectRoot], extensions: ['.ts', '.tsx', '.mjs', '.js'], symlinks: !buildOptions.preserveSymlinks, - modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'], + modules: [tsConfig.options.baseUrl || projectRoot, 'node_modules'], + mainFields: isPlatformServer + ? ['es2020', 'es2015', 'module', 'main'] + : ['es2020', 'es2015', 'browser', 'module', 'main'], + conditionNames: ['es2020', 'es2015', '...'], }, resolveLoader: { symlinks: !buildOptions.preserveSymlinks, - modules: [ - // Allow loaders to be in a node_modules nested inside the devkit/build-angular package. - // This is important in case loaders do not get hoisted. - // If this file moves to another location, alter potentialNodeModules as well. - 'node_modules', - ...findAllNodeModules(__dirname, projectRoot), - ], }, context: root, entry: entryPoints, + externals: externalDependencies, output: { + uniqueName: projectName, + hashFunction: 'xxhash64', // todo: remove in webpack 6. This is part of `futureDefaults`. + clean: buildOptions.deleteOutputPath ?? true, path: path.resolve(root, buildOptions.outputPath), - publicPath: buildOptions.deployUrl, - filename: ({ chunk }) => { - if (chunk?.name === 'polyfills-es5') { - return `polyfills-es5${hashFormat.chunk}.js`; - } else { - return `[name]${targetInFileName}${hashFormat.chunk}.js`; - } - }, - chunkFilename: `[id]${targetInFileName}${hashFormat.chunk}.js`, + publicPath: buildOptions.deployUrl ?? '', + filename: `[name]${hashFormat.chunk}.js`, + chunkFilename: `[name]${hashFormat.chunk}.js`, + libraryTarget: isPlatformServer ? 'commonjs' : undefined, + crossOriginLoading, + trustedTypes: 'angular#bundler', + scriptType: 'module', }, watch: buildOptions.watch, - watchOptions: getWatchOptions(buildOptions.poll), + watchOptions: { + poll, + // The below is needed as when preserveSymlinks is enabled we disable `resolve.symlinks`. + followSymlinks: buildOptions.preserveSymlinks, + ignored: poll === undefined ? undefined : '**/node_modules/**', + }, + snapshot: { + module: { + // Use hash of content instead of timestamp because the timestamp of the symlink will be used + // instead of the referenced files which causes changes in symlinks not to be picked up. + hash: buildOptions.preserveSymlinks, + }, + }, performance: { hints: false, }, - ignoreWarnings: IGNORE_WARNINGS, + ignoreWarnings: [ + // https://github.com/webpack-contrib/source-map-loader/blob/b2de4249c7431dd8432da607e08f0f65e9d64219/src/index.js#L83 + /Failed to parse source map from/, + // https://github.com/webpack-contrib/postcss-loader/blob/bd261875fdf9c596af4ffb3a1a73fe3c549befda/src/index.js#L153-L158 + /Add postcss as project dependency/, + // esbuild will issue a warning, while still hoists the @charset at the very top. + // This is caused by a bug in css-loader https://github.com/webpack-contrib/css-loader/issues/1212 + /"@charset" must be the first rule in the file/, + ], module: { // Show an error for missing exports instead of a warning. strictExportPresence: true, + parser: { + javascript: { + requireContext: false, + // Disable auto URL asset module creation. This doesn't effect `new Worker(new URL(...))` + // https://webpack.js.org/guides/asset-modules/#url-assets + url: false, + worker: !!webWorkerTsConfig, + }, + }, rules: [ { - // Mark files inside `@angular/core` as using SystemJS style dynamic imports. - // Removing this will cause deprecation warnings to appear. - test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/, - parser: { system: true }, + test: /\.?(svg|html)$/, + // Only process HTML and SVG which are known Angular component resources. + resourceQuery: /\?ngResource/, + type: 'asset/source', }, { // Mark files inside `rxjs/add` as containing side effects. // If this is fixed upstream and the fixed version becomes the minimum // supported version, this can be removed. - test: /[\/\\]rxjs[\/\\]add[\/\\].+\.js$/, + test: /[/\\]rxjs[/\\]add[/\\].+\.js$/, sideEffects: true, }, { - test: /\.[cm]?js$|\.tsx?$/, - exclude: [/[\/\\](?:core-js|\@babel|tslib|web-animations-js)[\/\\]/], + test: /\.[cm]?[tj]sx?$/, + // The below is needed due to a bug in `@babel/runtime`. See: https://github.com/babel/babel/issues/12824 + resolve: { fullySpecified: false }, + exclude: [ + /[\\/]node_modules[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill|whatwg-url)[/\\]/, + ], use: [ { loader: require.resolve('../../babel/webpack-loader'), options: { - cacheDirectory: findCachePath('babel-webpack'), - scriptTarget: wco.scriptTarget, + cacheDirectory: (cache.enabled && path.join(cache.path, 'babel-webpack')) || false, aot: buildOptions.aot, - }, + optimize: buildOptions.buildOptimizer, + supportedBrowsers: buildOptions.supportedBrowsers, + instrumentCode: codeCoverage + ? { + includedBasePath: sourceRoot ?? projectRoot, + excludedPaths: getInstrumentationExcludedPaths(root, codeCoverageExclude), + } + : undefined, + } as AngularBabelLoaderOptions, }, - ...buildOptimizerUseRule, ], }, ...extraRules, ], }, - cache: !!buildOptions.watch && !cachingDisabled, + experiments: { + backCompat: false, + syncWebAssembly: true, + asyncWebAssembly: true, + }, + infrastructureLogging: { + debug: verbose, + level: verbose ? 'verbose' : 'error', + }, + stats: getStatsOptions(verbose), + cache: getCacheSettings(wco, NG_VERSION.full), optimization: { minimizer: extraMinimizers, moduleIds: 'deterministic', chunkIds: buildOptions.namedChunks ? 'named' : 'deterministic', emitOnErrors: false, + runtimeChunk: isPlatformServer ? false : 'single', + splitChunks: { + maxAsyncRequests: Infinity, + cacheGroups: { + default: !!commonChunk && { + chunks: 'async', + minChunks: 2, + priority: 10, + }, + common: !!commonChunk && { + name: 'common', + chunks: 'async', + minChunks: 2, + enforce: true, + priority: 5, + }, + vendors: false, + defaultVendors: !!vendorChunk && { + name: 'vendor', + chunks: (chunk) => chunk.name === 'main', + enforce: true, + test: VENDORS_TEST, + }, + }, + }, }, plugins: [ - // Always replace the context for the System.import in angular/core to prevent warnings. - // https://github.com/angular/angular/issues/11580 - new ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)/, path.join(projectRoot, '$_lazy_route_resources'), {}), - new DedupeModuleResolvePlugin({ verbose: buildOptions.verbose }), + new NamedChunksPlugin(), + new OccurrencesPlugin({ + aot, + scriptsOptimization, + }), + new DedupeModuleResolvePlugin({ verbose }), ...extraPlugins, ], + node: false, }; } diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/dev-server.ts b/packages/angular_devkit/build_angular/src/webpack/configs/dev-server.ts index 50466b039638..6d542ca46c1a 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/dev-server.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/dev-server.ts @@ -1,86 +1,57 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { logging, tags } from '@angular-devkit/core'; -import { existsSync, readFileSync } from 'fs'; -import { posix, resolve } from 'path'; -import * as url from 'url'; -import * as webpack from 'webpack'; -import { Configuration } from 'webpack-dev-server'; -import { normalizeOptimization } from '../../utils'; +import { existsSync, promises as fsPromises } from 'fs'; +import { extname, posix, resolve } from 'path'; +import { URL, pathToFileURL } from 'url'; +import { Configuration, RuleSetRule } from 'webpack'; +import type { Configuration as DevServerConfiguration } from 'webpack-dev-server'; import { WebpackConfigOptions, WebpackDevServerOptions } from '../../utils/build-options'; +import { assertIsError } from '../../utils/error'; +import { loadEsmModule } from '../../utils/load-esm'; import { getIndexOutputFile } from '../../utils/webpack-browser-config'; import { HmrLoader } from '../plugins/hmr/hmr-loader'; -import { getWatchOptions } from '../utils/helpers'; -export function getDevServerConfig( +export async function getDevServerConfig( wco: WebpackConfigOptions, -): webpack.Configuration { +): Promise { const { - buildOptions: { - optimization, - host, - port, - index, - headers, - poll, - ssl, - hmr, - main, - disableHostCheck, - liveReload, - allowedHosts, - watch, - proxyConfig, - }, + buildOptions: { host, port, index, headers, watch, hmr, main, liveReload, proxyConfig }, logger, root, } = wco; const servePath = buildServePath(wco.buildOptions, logger); - const { styles: stylesOptimization, scripts: scriptsOptimization } = normalizeOptimization(optimization); - - const extraPlugins = []; - // Resolve public host and client address. - let publicHost = wco.buildOptions.publicHost; - if (publicHost) { - if (!/^\w+:\/\//.test(publicHost)) { - publicHost = `${ssl ? 'https' : 'http'}://${publicHost}`; - } - - const parsedHost = url.parse(publicHost); - publicHost = parsedHost.host ?? undefined; - } else { - publicHost = '0.0.0.0:0'; + const extraRules: RuleSetRule[] = []; + if (hmr) { + extraRules.push({ + loader: HmrLoader, + include: [resolve(wco.root, main)], + }); } + const extraPlugins = []; if (!watch) { // There's no option to turn off file watching in webpack-dev-server, but // we can override the file watcher instead. extraPlugins.push({ - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any apply: (compiler: any) => { compiler.hooks.afterEnvironment.tap('angular-cli', () => { - compiler.watchFileSystem = { watch: () => { } }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + compiler.watchFileSystem = { watch: () => {} }; }); }, }); } - const extraRules: webpack.RuleSetRule[] = []; - if (hmr) { - extraRules.push({ - loader: HmrLoader, - include: [main].map(p => resolve(wco.root, p)), - }); - } - return { plugins: extraPlugins, module: { @@ -94,43 +65,36 @@ export function getDevServerConfig( ...headers, }, historyApiFallback: !!index && { - index: `${servePath}/${getIndexOutputFile(index)}`, + index: posix.join(servePath, getIndexOutputFile(index)), disableDotRule: true, htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], rewrites: [ { from: new RegExp(`^(?!${servePath})/.*`), - to: context => url.format(context.parsedUrl), + to: (context) => context.parsedUrl.href, }, ], }, - sockPath: posix.join(servePath, 'sockjs-node'), - stats: false, - compress: stylesOptimization.minify || scriptsOptimization, - watchOptions: getWatchOptions(poll), - https: getSslConfig(root, wco.buildOptions), - overlay: { - errors: !(stylesOptimization.minify || scriptsOptimization), - warnings: false, + // When setupExitSignals is enabled webpack-dev-server will shutdown gracefully which would + // require CTRL+C to be pressed multiple times to exit. + // See: https://github.com/webpack/webpack-dev-server/blob/c76b6d11a3821436c5e20207c8a38deb6ab7e33c/lib/Server.js#L1801-L1827 + setupExitSignals: false, + compress: false, + static: false, + server: getServerConfig(root, wco.buildOptions), + allowedHosts: getAllowedHostsConfig(wco.buildOptions), + devMiddleware: { + publicPath: servePath, + stats: false, }, - public: publicHost, - allowedHosts, - disableHostCheck, - // This should always be true, but at the moment this breaks 'SuppressExtractedTextChunksWebpackPlugin' - // because it will include addition JS in the styles.js. - inline: hmr, - publicPath: servePath, liveReload, - hotOnly: hmr && !liveReload, - hot: hmr, - proxy: addProxyConfig(root, proxyConfig), - contentBase: false, - logLevel: 'silent', - } as Configuration & { logLevel: Configuration['clientLogLevel'] }, + hot: hmr && !liveReload ? 'only' : hmr, + proxy: await addProxyConfig(root, proxyConfig), + ...getWebSocketSettings(wco.buildOptions, servePath), + }, }; } - /** * Resolve and build a URL _path_ that will be the root of the server. This resolved base href and * deploy URL from the browser options and returns a path from the root. @@ -152,7 +116,7 @@ export function buildServePath( } if (servePath.endsWith('/')) { - servePath = servePath.substr(0, servePath.length - 1); + servePath = servePath.slice(0, -1); } if (!servePath.startsWith('/')) { @@ -166,39 +130,113 @@ export function buildServePath( * Private method to enhance a webpack config with SSL configuration. * @private */ -function getSslConfig( +function getServerConfig( root: string, options: WebpackDevServerOptions, -) { +): DevServerConfiguration['server'] { const { ssl, sslCert, sslKey } = options; - if (ssl && sslCert && sslKey) { - return { - key: readFileSync(resolve(root, sslKey), 'utf-8'), - cert: readFileSync(resolve(root, sslCert), 'utf-8'), - }; + if (!ssl) { + return 'http'; } - return ssl; + return { + type: 'https', + options: + sslCert && sslKey + ? { + key: resolve(root, sslKey), + cert: resolve(root, sslCert), + } + : undefined, + }; } /** * Private method to enhance a webpack config with Proxy configuration. * @private */ -function addProxyConfig( - root: string, - proxyConfig: string | undefined, -) { +async function addProxyConfig(root: string, proxyConfig: string | undefined) { if (!proxyConfig) { return undefined; } const proxyPath = resolve(root, proxyConfig); - if (existsSync(proxyPath)) { - return require(proxyPath); + + if (!existsSync(proxyPath)) { + throw new Error(`Proxy configuration file ${proxyPath} does not exist.`); } - throw new Error('Proxy config file ' + proxyPath + ' does not exist.'); + switch (extname(proxyPath)) { + case '.json': { + const content = await fsPromises.readFile(proxyPath, 'utf-8'); + + const { parse, printParseErrorCode } = await import('jsonc-parser'); + const parseErrors: import('jsonc-parser').ParseError[] = []; + const proxyConfiguration = parse(content, parseErrors, { allowTrailingComma: true }); + + if (parseErrors.length > 0) { + let errorMessage = `Proxy configuration file ${proxyPath} contains parse errors:`; + for (const parseError of parseErrors) { + const { line, column } = getJsonErrorLineColumn(parseError.offset, content); + errorMessage += `\n[${line}, ${column}] ${printParseErrorCode(parseError.error)}`; + } + throw new Error(errorMessage); + } + + return proxyConfiguration; + } + case '.mjs': + // Load the ESM configuration file using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + return (await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath))).default; + case '.cjs': + return require(proxyPath); + default: + // The file could be either CommonJS or ESM. + // CommonJS is tried first then ESM if loading fails. + try { + return require(proxyPath); + } catch (e) { + assertIsError(e); + if (e.code === 'ERR_REQUIRE_ESM') { + // Load the ESM configuration file using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + return (await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath))).default; + } + + throw e; + } + } +} + +/** + * Calculates the line and column for an error offset in the content of a JSON file. + * @param location The offset error location from the beginning of the content. + * @param content The full content of the file containing the error. + * @returns An object containing the line and column + */ +function getJsonErrorLineColumn(offset: number, content: string) { + if (offset === 0) { + return { line: 1, column: 1 }; + } + + let line = 0; + let position = 0; + // eslint-disable-next-line no-constant-condition + while (true) { + ++line; + + const nextNewline = content.indexOf('\n', position); + if (nextNewline === -1 || nextNewline > offset) { + break; + } + + position = nextNewline + 1; + } + + return { line, column: offset - position + 1 }; } /** @@ -220,7 +258,7 @@ function findDefaultServePath(baseHref?: string, deployUrl?: string): string | n // normalize baseHref // for ng serve the starting base is always `/` so a relative // and root relative value are identical - const baseHrefParts = (baseHref || '').split('/').filter(part => part !== ''); + const baseHrefParts = (baseHref || '').split('/').filter((part) => part !== ''); if (baseHref && !baseHref.endsWith('/')) { baseHrefParts.pop(); } @@ -238,3 +276,59 @@ function findDefaultServePath(baseHref?: string, deployUrl?: string): string | n // Join together baseHref and deployUrl return `${normalizedBaseHref}${deployUrl || ''}`; } + +function getAllowedHostsConfig( + options: WebpackDevServerOptions, +): DevServerConfiguration['allowedHosts'] { + if (options.disableHostCheck) { + return 'all'; + } else if (options.allowedHosts?.length) { + return options.allowedHosts; + } + + return undefined; +} + +function getWebSocketSettings( + options: WebpackDevServerOptions, + servePath: string, +): { + webSocketServer?: DevServerConfiguration['webSocketServer']; + client?: DevServerConfiguration['client']; +} { + const { hmr, liveReload } = options; + if (!hmr && !liveReload) { + return { + webSocketServer: false, + client: undefined, + }; + } + + const webSocketPath = posix.join(servePath, 'ng-cli-ws'); + + return { + webSocketServer: { + options: { + path: webSocketPath, + }, + }, + client: { + logging: 'info', + webSocketURL: getPublicHostOptions(options, webSocketPath), + overlay: { + errors: true, + warnings: false, + }, + }, + }; +} + +function getPublicHostOptions(options: WebpackDevServerOptions, webSocketPath: string): string { + let publicHost: string | null | undefined = options.publicHost; + if (publicHost) { + const hostWithProtocol = !/^\w+:\/\//.test(publicHost) ? `https://${publicHost}` : publicHost; + publicHost = new URL(hostWithProtocol).host; + } + + return `auto://${publicHost || '0.0.0.0:0'}${webSocketPath}`; +} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/index.ts b/packages/angular_devkit/build_angular/src/webpack/configs/index.ts index badd91583a97..b57c5fe13fb4 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/index.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/index.ts @@ -1,16 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -export * from './browser'; + export * from './common'; export * from './dev-server'; -export * from './server'; export * from './styles'; -export * from './test'; -export * from './typescript'; -export * from './stats'; -export * from './worker'; diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/server.ts b/packages/angular_devkit/build_angular/src/webpack/configs/server.ts deleted file mode 100644 index ca19993a0c6d..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/configs/server.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { isAbsolute } from 'path'; -import { Configuration, ContextReplacementPlugin } from 'webpack'; -import { WebpackConfigOptions } from '../../utils/build-options'; -import { getSourceMapDevTool } from '../utils/helpers'; - -/** - * Returns a partial Webpack configuration specific to creating a bundle for node - * @param wco Options which include the build options and app config - */ -export function getServerConfig(wco: WebpackConfigOptions): Configuration { - const { - sourceMap, - bundleDependencies, - externalDependencies = [], - } = wco.buildOptions; - - const extraPlugins = []; - const { scripts, styles, hidden } = sourceMap; - if (scripts || styles) { - extraPlugins.push(getSourceMapDevTool(scripts, styles, hidden)); - } - - const externals: Configuration['externals'] = [...externalDependencies]; - if (!bundleDependencies) { - externals.push(({ context, request }, callback) => - externalizePackages(context ?? wco.projectRoot, request, callback), - ); - } - - const config: Configuration = { - resolve: { - mainFields: ['es2015', 'main', 'module'], - }, - target: 'node', - output: { - libraryTarget: 'commonjs', - }, - plugins: [ - // Fixes Critical dependency: the request of a dependency is an expression - new ContextReplacementPlugin(/@?hapi(\\|\/)/), - new ContextReplacementPlugin(/express(\\|\/)/), - ...extraPlugins, - ], - node: false, - externals, - }; - - return config; -} - -function externalizePackages( - context: string, - request: string | undefined, - callback: (error?: Error, result?: string) => void, -): void { - if (!request) { - return; - } - - // Absolute & Relative paths are not externals - if (request.startsWith('.') || isAbsolute(request)) { - callback(); - - return; - } - - try { - require.resolve(request, { paths: [context] }); - callback(undefined, request); - } catch { - // Node couldn't find it, so it must be user-aliased - callback(); - } -} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/stats.ts b/packages/angular_devkit/build_angular/src/webpack/configs/stats.ts deleted file mode 100644 index dca47f16a4d1..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/configs/stats.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { WebpackConfigOptions } from '../../utils/build-options'; - -const webpackOutputOptions = { - all: false, // Fallback value for stats options when an option is not defined. It has precedence over local webpack defaults. - colors: true, - hash: true, // required by custom stat output - timings: true, // required by custom stat output - chunks: true, // required by custom stat output - builtAt: true, // required by custom stat output - chunkModules: false, - children: false, // listing all children is very noisy in AOT and hides warnings/errors - modules: false, - reasons: false, - warnings: true, - errors: true, - assets: true, // required by custom stat output - version: false, - errorDetails: false, - moduleTrace: false, -}; - -const verboseWebpackOutputOptions: Record = { - // The verbose output will most likely be piped to a file, so colors just mess it up. - colors: false, - usedExports: true, - optimizationBailout: true, - reasons: true, - children: true, - assets: true, - version: true, - chunkModules: true, - errorDetails: true, - moduleTrace: true, - logging: 'verbose', -}; - -verboseWebpackOutputOptions['modulesSpace'] = Infinity; - -export function getWebpackStatsConfig(verbose = false) { - return verbose - ? { ...webpackOutputOptions, ...verboseWebpackOutputOptions } - : webpackOutputOptions; -} - -export function getStatsConfig(wco: WebpackConfigOptions) { - const verbose = !!wco.buildOptions.verbose; - - return { stats: getWebpackStatsConfig(verbose) }; -} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts index 288653c610a5..eac8f9ab8d54 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts @@ -1,140 +1,99 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as fs from 'fs'; -import * as path from 'path'; -import * as webpack from 'webpack'; -import { ExtraEntryPoint } from '../../browser/schema'; -import { BuildBrowserFeatures } from '../../utils/build-browser-features'; + +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import type { FileImporter } from 'sass'; +import type { Configuration, LoaderContext, RuleSetUseItem } from 'webpack'; +import { + FileImporterWithRequestContextOptions, + SassWorkerImplementation, +} from '../../sass/sass-service'; +import { SassLegacyWorkerImplementation } from '../../sass/sass-service-legacy'; import { WebpackConfigOptions } from '../../utils/build-options'; +import { useLegacySass } from '../../utils/environment-options'; import { AnyComponentStyleBudgetChecker, PostcssCliResources, RemoveHashPlugin, SuppressExtractedTextChunksWebpackPlugin, } from '../plugins'; +import { CssOptimizerPlugin } from '../plugins/css-optimizer-plugin'; +import { StylesWebpackPlugin } from '../plugins/styles-webpack-plugin'; import { assetNameTemplateFactory, getOutputHashFormat, - normalizeExtraEntryPoints, + normalizeGlobalStyles, } from '../utils/helpers'; -function resolveGlobalStyles( - styleEntrypoints: ExtraEntryPoint[], - root: string, - preserveSymlinks: boolean, -): { entryPoints: Record; noInjectNames: string[]; paths: string[] } { - const entryPoints: Record = {}; - const noInjectNames: string[] = []; - const paths: string[] = []; - - if (styleEntrypoints.length === 0) { - return { entryPoints, noInjectNames, paths }; - } - - for (const style of normalizeExtraEntryPoints(styleEntrypoints, 'styles')) { - let resolvedPath = path.resolve(root, style.input); - if (!fs.existsSync(resolvedPath)) { - try { - resolvedPath = require.resolve(style.input, { paths: [root] }); - } catch {} - } - - if (!preserveSymlinks) { - resolvedPath = fs.realpathSync(resolvedPath); - } - - // Add style entry points. - if (entryPoints[style.bundleName]) { - entryPoints[style.bundleName].push(resolvedPath); - } else { - entryPoints[style.bundleName] = [resolvedPath]; - } - - // Add non injected styles to the list. - if (!style.inject) { - noInjectNames.push(style.bundleName); - } - - // Add global css paths. - paths.push(resolvedPath); - } - - return { entryPoints, noInjectNames, paths }; -} - -// tslint:disable-next-line: no-big-function -export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuration { - const MiniCssExtractPlugin = require('mini-css-extract-plugin'); - const postcssImports = require('postcss-import'); - const postcssPresetEnv: typeof import('postcss-preset-env') = require('postcss-preset-env'); - - const { root, buildOptions } = wco; - const extraPlugins: { apply(compiler: webpack.Compiler): void }[] = []; +// eslint-disable-next-line max-lines-per-function +export function getStylesConfig(wco: WebpackConfigOptions): Configuration { + const { root, buildOptions, logger } = wco; + const extraPlugins: Configuration['plugins'] = []; extraPlugins.push(new AnyComponentStyleBudgetChecker(buildOptions.budgets)); const cssSourceMap = buildOptions.sourceMap.styles; // Determine hashing format. - const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string); + const hashFormat = getOutputHashFormat(buildOptions.outputHashing); // use includePaths from appConfig const includePaths = buildOptions.stylePreprocessorOptions?.includePaths?.map((p) => path.resolve(root, p)) ?? []; // Process global styles. - const { entryPoints, noInjectNames, paths: globalStylePaths } = resolveGlobalStyles( - buildOptions.styles, - root, - !!buildOptions.preserveSymlinks, - ); - if (noInjectNames.length > 0) { - // Add plugin to remove hashes from lazy styles. - extraPlugins.push(new RemoveHashPlugin({ chunkNames: noInjectNames, hashFormat })); - } - - let sassImplementation: {} | undefined; - try { - // tslint:disable-next-line:no-implicit-dependencies - sassImplementation = require('node-sass'); - wco.logger.warn( - `'node-sass' usage is deprecated and will be removed in a future major version. ` + - `To opt-out of the deprecated behaviour and start using 'sass' uninstall 'node-sass'.`, + if (buildOptions.styles.length > 0) { + const { entryPoints, noInjectNames } = normalizeGlobalStyles(buildOptions.styles); + extraPlugins.push( + new StylesWebpackPlugin({ + root, + entryPoints, + preserveSymlinks: buildOptions.preserveSymlinks, + }), ); - } catch { - sassImplementation = require('sass'); + + if (noInjectNames.length > 0) { + // Add plugin to remove hashes from lazy styles. + extraPlugins.push(new RemoveHashPlugin({ chunkNames: noInjectNames, hashFormat })); + } } + const sassImplementation = useLegacySass + ? new SassLegacyWorkerImplementation() + : new SassWorkerImplementation(); + + extraPlugins.push({ + apply(compiler) { + compiler.hooks.shutdown.tap('sass-worker', () => { + sassImplementation.close(); + }); + }, + }); + const assetNameTemplate = assetNameTemplateFactory(hashFormat); const extraPostcssPlugins: import('postcss').Plugin[] = []; // Attempt to setup Tailwind CSS - // A configuration file can exist in the project or workspace root - const tailwindConfigFile = 'tailwind.config.js'; - let tailwindConfigPath; - for (const basePath of [wco.projectRoot, wco.root]) { - const fullPath = path.join(basePath, tailwindConfigFile); - if (fs.existsSync(fullPath)) { - tailwindConfigPath = fullPath; - break; - } - } // Only load Tailwind CSS plugin if configuration file was found. // This acts as a guard to ensure the project actually wants to use Tailwind CSS. // The package may be unknowningly present due to a third-party transitive package dependency. + const tailwindConfigPath = getTailwindConfigPath(wco); if (tailwindConfigPath) { let tailwindPackagePath; try { tailwindPackagePath = require.resolve('tailwindcss', { paths: [wco.root] }); } catch { const relativeTailwindConfigPath = path.relative(wco.root, tailwindConfigPath); - wco.logger.warn( + logger.warn( `Tailwind CSS configuration file found (${relativeTailwindConfigPath})` + ` but the 'tailwindcss' package is not installed.` + ` To enable Tailwind CSS, please install the 'tailwindcss' package.`, @@ -145,10 +104,10 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio } } - const { supportedBrowsers } = new BuildBrowserFeatures(wco.projectRoot); - const postcssOptionsCreator = (inlineSourcemaps: boolean, extracted: boolean | undefined) => { - // tslint:disable-next-line: no-any - const optionGenerator = (loader: any) => ({ + const autoprefixer: typeof import('autoprefixer') = require('autoprefixer'); + + const postcssOptionsCreator = (inlineSourcemaps: boolean, extracted: boolean) => { + const optionGenerator = (loader: LoaderContext) => ({ map: inlineSourcemaps ? { inline: true, @@ -156,23 +115,6 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio } : undefined, plugins: [ - postcssImports({ - resolve: (url: string) => (url.startsWith('~') ? url.substr(1) : url), - load: (filename: string) => { - return new Promise((resolve, reject) => { - loader.fs.readFile(filename, (err: Error, data: Buffer) => { - if (err) { - reject(err); - - return; - } - - const content = data.toString(); - resolve(content); - }); - }); - }, - }), PostcssCliResources({ baseHref: buildOptions.baseHref, deployUrl: buildOptions.deployUrl, @@ -183,11 +125,9 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio extracted, }), ...extraPostcssPlugins, - postcssPresetEnv({ - // tslint:disable-next-line: no-any - browsers: supportedBrowsers as any, // Typings only allow a string - autoprefixer: true, - stage: 3, + autoprefixer({ + ignoreUnknownVersions: true, + overrideBrowserslist: buildOptions.supportedBrowsers, }), ], }); @@ -197,57 +137,72 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio return optionGenerator; }; - // load component css as raw strings - const componentsSourceMap = !!( - cssSourceMap && - // Never use component css sourcemap when style optimizations are on. - // It will just increase bundle size without offering good debug experience. - !buildOptions.optimization.styles.minify && - // Inline all sourcemap types except hidden ones, which are the same as no sourcemaps - // for component css. - !buildOptions.sourceMap.hidden - ); - - if (buildOptions.extractCss) { - // extract global css from js files into own css file. - extraPlugins.push(new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` })); - - if (!buildOptions.hmr) { - // don't remove `.js` files for `.css` when we are using HMR these contain HMR accept codes. - // suppress empty .js files in css only entry points. - extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin()); + let componentsSourceMap = !!cssSourceMap; + if (cssSourceMap) { + if (buildOptions.optimization.styles.minify) { + // Never use component css sourcemap when style optimizations are on. + // It will just increase bundle size without offering good debug experience. + logger.warn( + 'Components styles sourcemaps are not generated when styles optimization is enabled.', + ); + componentsSourceMap = false; + } else if (buildOptions.sourceMap.hidden) { + // Inline all sourcemap types except hidden ones, which are the same as no sourcemaps + // for component css. + logger.warn('Components styles sourcemaps are not generated when sourcemaps are hidden.'); + componentsSourceMap = false; } } - const componentStyleLoaders: webpack.RuleSetUseItem[] = [ - { loader: require.resolve('raw-loader') }, + // extract global css from js files into own css file. + extraPlugins.push(new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` })); + + if (!buildOptions.hmr) { + // don't remove `.js` files for `.css` when we are using HMR these contain HMR accept codes. + // suppress empty .js files in css only entry points. + extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin()); + } + + const postCss = require('postcss'); + const postCssLoaderPath = require.resolve('postcss-loader'); + + const componentStyleLoaders: RuleSetUseItem[] = [ { - loader: require.resolve('postcss-loader'), + loader: require.resolve('css-loader'), options: { - implementation: require('postcss'), + url: false, + sourceMap: componentsSourceMap, + importLoaders: 1, + exportType: 'string', + esModule: false, + }, + }, + { + loader: postCssLoaderPath, + options: { + implementation: postCss, postcssOptions: postcssOptionsCreator(componentsSourceMap, false), }, }, ]; - const globalStyleLoaders: webpack.RuleSetUseItem[] = [ - buildOptions.extractCss - ? { - loader: MiniCssExtractPlugin.loader, - } - : require.resolve('style-loader'), + const globalStyleLoaders: RuleSetUseItem[] = [ + { + loader: MiniCssExtractPlugin.loader, + }, { loader: require.resolve('css-loader'), options: { url: false, sourceMap: !!cssSourceMap, + importLoaders: 1, }, }, { - loader: require.resolve('postcss-loader'), + loader: postCssLoaderPath, options: { - implementation: require('postcss'), - postcssOptions: postcssOptionsCreator(false, buildOptions.extractCss), + implementation: postCss, + postcssOptions: postcssOptionsCreator(false, true), sourceMap: !!cssSourceMap, }, }, @@ -255,17 +210,14 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio const styleLanguages: { extensions: string[]; - mimetype?: string; - use: webpack.RuleSetUseItem[]; + use: RuleSetUseItem[]; }[] = [ { extensions: ['css'], - mimetype: 'text/css', use: [], }, { extensions: ['scss'], - mimetype: 'text/x-scss', use: [ { loader: require.resolve('resolve-url-loader'), @@ -275,26 +227,19 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio }, { loader: require.resolve('sass-loader'), - options: { - implementation: sassImplementation, - sourceMap: true, - sassOptions: { - // bootstrap-sass requires a minimum precision of 8 - precision: 8, - includePaths, - // Use expanded as otherwise sass will remove comments that are needed for autoprefixer - // Ex: /* autoprefixer grid: autoplace */ - // tslint:disable-next-line: max-line-length - // See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70 - outputStyle: 'expanded', - }, - }, + options: getSassLoaderOptions( + root, + sassImplementation, + includePaths, + false, + !!buildOptions.verbose, + !!buildOptions.preserveSymlinks, + ), }, ], }, { extensions: ['sass'], - mimetype: 'text/x-sass', use: [ { loader: require.resolve('resolve-url-loader'), @@ -304,27 +249,19 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio }, { loader: require.resolve('sass-loader'), - options: { - implementation: sassImplementation, - sourceMap: true, - sassOptions: { - indentedSyntax: true, - // bootstrap-sass requires a minimum precision of 8 - precision: 8, - includePaths, - // Use expanded as otherwise sass will remove comments that are needed for autoprefixer - // Ex: /* autoprefixer grid: autoplace */ - // tslint:disable-next-line: max-line-length - // See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70 - outputStyle: 'expanded', - }, - }, + options: getSassLoaderOptions( + root, + sassImplementation, + includePaths, + true, + !!buildOptions.verbose, + !!buildOptions.preserveSymlinks, + ), }, ], }, { extensions: ['less'], - mimetype: 'text/x-less', use: [ { loader: require.resolve('less-loader'), @@ -339,64 +276,200 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio }, ], }, - { - extensions: ['styl'], - mimetype: 'text/x-stylus', - use: [ - { - loader: require.resolve('stylus-loader'), - options: { - sourceMap: cssSourceMap, - stylusOptions: { - compress: false, - sourceMap: { comment: false }, - paths: includePaths, - }, + ]; + + return { + module: { + rules: styleLanguages.map(({ extensions, use }) => ({ + test: new RegExp(`\\.(?:${extensions.join('|')})$`, 'i'), + rules: [ + // Setup processing rules for global and component styles + { + oneOf: [ + // Global styles are only defined global styles + { + use: globalStyleLoaders, + resourceQuery: /\?ngGlobalStyle/, + }, + // Component styles are all styles except defined global styles + { + use: componentStyleLoaders, + resourceQuery: /\?ngResource/, + }, + ], }, - }, - ], + { use }, + ], + })), }, - ]; + optimization: { + minimizer: buildOptions.optimization.styles.minify + ? [ + new CssOptimizerPlugin({ + supportedBrowsers: buildOptions.supportedBrowsers, + }), + ] + : undefined, + }, + plugins: extraPlugins, + }; +} - const inlineLanguageRules: webpack.RuleSetRule[] = []; - const fileLanguageRules: webpack.RuleSetRule[] = []; - for (const language of styleLanguages) { - if (language.mimetype) { - // inline component styles use data URIs and processing is selected by mimetype - inlineLanguageRules.push({ - mimetype: language.mimetype, - use: [...componentStyleLoaders, ...language.use], - }); +function getTailwindConfigPath({ projectRoot, root }: WebpackConfigOptions): string | undefined { + // A configuration file can exist in the project or workspace root + // The list of valid config files can be found: + // https://github.com/tailwindlabs/tailwindcss/blob/8845d112fb62d79815b50b3bae80c317450b8b92/src/util/resolveConfigPath.js#L46-L52 + const tailwindConfigFiles = ['tailwind.config.js', 'tailwind.config.cjs']; + for (const basePath of [projectRoot, root]) { + for (const configFile of tailwindConfigFiles) { + // Irrespective of the name project level configuration should always take precedence. + const fullPath = path.join(basePath, configFile); + if (fs.existsSync(fullPath)) { + return fullPath; + } } + } - fileLanguageRules.push({ - test: new RegExp(`\\.(?:${language.extensions.join('|')})$`, 'i'), - rules: [ - // Setup processing rules for global and component styles - { - oneOf: [ - // Component styles are all styles except defined global styles - { - exclude: globalStylePaths, - use: componentStyleLoaders, - }, - // Global styles are only defined global styles - { - include: globalStylePaths, - use: globalStyleLoaders, - }, - ], + return undefined; +} + +function getSassLoaderOptions( + root: string, + implementation: SassWorkerImplementation | SassLegacyWorkerImplementation, + includePaths: string[], + indentedSyntax: boolean, + verbose: boolean, + preserveSymlinks: boolean, +): Record { + return implementation instanceof SassWorkerImplementation + ? { + sourceMap: true, + api: 'modern', + implementation, + // Webpack importer is only implemented in the legacy API and we have our own custom Webpack importer. + // See: https://github.com/webpack-contrib/sass-loader/blob/997f3eb41d86dd00d5fa49c395a1aeb41573108c/src/utils.js#L642-L651 + webpackImporter: false, + sassOptions: (loaderContext: LoaderContext<{}>) => ({ + importers: [getSassResolutionImporter(loaderContext, root, preserveSymlinks)], + loadPaths: includePaths, + // Use expanded as otherwise sass will remove comments that are needed for autoprefixer + // Ex: /* autoprefixer grid: autoplace */ + // See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70 + style: 'expanded', + // Silences compiler warnings from 3rd party stylesheets + quietDeps: !verbose, + verbose, + syntax: indentedSyntax ? 'indented' : 'scss', + sourceMapIncludeSources: true, + }), + } + : { + sourceMap: true, + api: 'legacy', + implementation, + sassOptions: { + importer: (url: string, from: string) => { + if (url.charAt(0) === '~') { + throw new Error( + `'${from}' imports '${url}' with a tilde. Usage of '~' in imports is no longer supported.`, + ); + } + + return null; + }, + // Prevent use of `fibers` package as it no longer works in newer Node.js versions + fiber: false, + indentedSyntax, + // bootstrap-sass requires a minimum precision of 8 + precision: 8, + includePaths, + // Use expanded as otherwise sass will remove comments that are needed for autoprefixer + // Ex: /* autoprefixer grid: autoplace */ + // See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70 + outputStyle: 'expanded', + // Silences compiler warnings from 3rd party stylesheets + quietDeps: !verbose, + verbose, }, - { use: language.use }, - ], - }); - } + }; +} + +function getSassResolutionImporter( + loaderContext: LoaderContext<{}>, + root: string, + preserveSymlinks: boolean, +): FileImporter<'async'> { + const commonResolverOptions: Parameters[0] = { + conditionNames: ['sass', 'style'], + mainFields: ['sass', 'style', 'main', '...'], + extensions: ['.scss', '.sass', '.css'], + restrictions: [/\.((sa|sc|c)ss)$/i], + preferRelative: true, + symlinks: !preserveSymlinks, + }; + + // Sass also supports import-only files. If you name a file .import.scss, it will only be loaded for imports, not for @uses. + // See: https://sass-lang.com/documentation/at-rules/import#import-only-files + const resolveImport = loaderContext.getResolve({ + ...commonResolverOptions, + dependencyType: 'sass-import', + mainFiles: ['_index.import', '_index', 'index.import', 'index', '...'], + }); + + const resolveModule = loaderContext.getResolve({ + ...commonResolverOptions, + dependencyType: 'sass-module', + mainFiles: ['_index', 'index', '...'], + }); return { - entry: entryPoints, - module: { - rules: [...fileLanguageRules, ...inlineLanguageRules], + findFileUrl: async ( + url, + { fromImport, previousResolvedModules }: FileImporterWithRequestContextOptions, + ): Promise => { + if (url.charAt(0) === '.') { + // Let Sass handle relative imports. + return null; + } + + const resolve = fromImport ? resolveImport : resolveModule; + // Try to resolve from root of workspace + let result = await tryResolve(resolve, root, url); + + // Try to resolve from previously resolved modules. + if (!result && previousResolvedModules) { + for (const path of previousResolvedModules) { + result = await tryResolve(resolve, path, url); + if (result) { + break; + } + } + } + + return result ? pathToFileURL(result) : null; }, - plugins: extraPlugins, }; } + +async function tryResolve( + resolve: ReturnType['getResolve']>, + root: string, + url: string, +): Promise { + try { + return await resolve(root, url); + } catch { + // Try to resolve a partial file + // @use '@material/button/button' as mdc-button; + // `@material/button/button` -> `@material/button/_button` + const lastSlashIndex = url.lastIndexOf('/'); + const underscoreIndex = lastSlashIndex + 1; + if (underscoreIndex > 0 && url.charAt(underscoreIndex) !== '_') { + const partialFileUrl = `${url.slice(0, underscoreIndex)}_${url.slice(underscoreIndex)}`; + + return resolve(root, partialFileUrl).catch(() => undefined); + } + } + + return undefined; +} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/test.ts b/packages/angular_devkit/build_angular/src/webpack/configs/test.ts deleted file mode 100644 index ab89bb017d2b..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/configs/test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import * as glob from 'glob'; -import * as path from 'path'; -import * as webpack from 'webpack'; -import { WebpackConfigOptions, WebpackTestOptions } from '../../utils/build-options'; -import { getSourceMapDevTool, isPolyfillsEntry } from '../utils/helpers'; - -export function getTestConfig( - wco: WebpackConfigOptions, -): webpack.Configuration { - const { - buildOptions: { codeCoverage, codeCoverageExclude, main, sourceMap }, - root, - sourceRoot, - } = wco; - - const extraRules: webpack.RuleSetRule[] = []; - const extraPlugins: { apply(compiler: webpack.Compiler): void }[] = []; - - if (codeCoverage) { - const exclude: (string | RegExp)[] = [ - /\.(e2e|spec)\.tsx?$/, - /node_modules/, - ]; - - if (codeCoverageExclude) { - for (const excludeGlob of codeCoverageExclude) { - glob - .sync(path.join(root, excludeGlob), { nodir: true }) - .forEach((file) => exclude.push(path.normalize(file))); - } - } - - extraRules.push({ - test: /\.(jsx?|tsx?)$/, - loader: require.resolve('@jsdevtools/coverage-istanbul-loader'), - options: { esModules: true }, - enforce: 'post', - exclude, - include: sourceRoot, - }); - } - - if (sourceMap.scripts || sourceMap.styles) { - extraPlugins.push(getSourceMapDevTool( - sourceMap.scripts, - sourceMap.styles, - false, - true, - )); - } - - return { - mode: 'development', - resolve: { - mainFields: ['es2015', 'browser', 'module', 'main'], - }, - devtool: false, - entry: { - main: path.resolve(root, main), - }, - module: { - rules: extraRules, - }, - plugins: extraPlugins, - optimization: { - splitChunks: { - chunks: (chunk) => !isPolyfillsEntry(chunk.name), - cacheGroups: { - vendors: false, - defaultVendors: { - name: 'vendor', - chunks: (chunk) => chunk.name === 'main', - enforce: true, - test: /[\\/]node_modules[\\/]/, - }, - }, - }, - }, - }; -} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts b/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts deleted file mode 100644 index e9d35ef1880e..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { buildOptimizerLoaderPath } from '@angular-devkit/build-optimizer'; -import { getSystemPath } from '@angular-devkit/core'; -import { CompilerOptions } from '@angular/compiler-cli'; -import { AngularWebpackLoaderPath, AngularWebpackPlugin } from '@ngtools/webpack'; -import { WebpackConfigOptions } from '../../utils/build-options'; - -function ensureIvy(wco: WebpackConfigOptions): void { - if (wco.tsConfig.options.enableIvy !== false) { - return; - } - - wco.logger.warn( - 'Project is attempting to disable the Ivy compiler. ' + - 'Angular versions 12 and higher do not support the deprecated View Engine compiler for applications. ' + - 'The Ivy compiler will be used to build this project. ' + - '\nFor additional information or if the build fails, please see https://angular.io/guide/ivy', - ); - - wco.tsConfig.options.enableIvy = true; -} - -function createIvyPlugin( - wco: WebpackConfigOptions, - aot: boolean, - tsconfig: string, -): AngularWebpackPlugin { - const { buildOptions } = wco; - const optimize = buildOptions.optimization.scripts; - - const compilerOptions: CompilerOptions = { - sourceMap: buildOptions.sourceMap.scripts, - declaration: false, - declarationMap: false, - }; - - if (buildOptions.preserveSymlinks !== undefined) { - compilerOptions.preserveSymlinks = buildOptions.preserveSymlinks; - } - - const fileReplacements: Record = {}; - if (buildOptions.fileReplacements) { - for (const replacement of buildOptions.fileReplacements) { - fileReplacements[getSystemPath(replacement.replace)] = getSystemPath(replacement.with); - } - } - - let inlineStyleMimeType; - switch (buildOptions.inlineStyleLanguage) { - case 'less': - inlineStyleMimeType = 'text/x-less'; - break; - case 'sass': - inlineStyleMimeType = 'text/x-sass'; - break; - case 'scss': - inlineStyleMimeType = 'text/x-scss'; - break; - case 'css': - default: - inlineStyleMimeType = 'text/css'; - break; - } - - return new AngularWebpackPlugin({ - tsconfig, - compilerOptions, - fileReplacements, - jitMode: !aot, - emitNgModuleScope: !optimize, - inlineStyleMimeType, - }); -} - -export function getNonAotConfig(wco: WebpackConfigOptions) { - const { tsConfigPath } = wco; - - return { - module: { - rules: [ - { - test: /\.[jt]sx?$/, - loader: AngularWebpackLoaderPath, - }, - ], - }, - plugins: [ - createIvyPlugin(wco, false, tsConfigPath), - ], - }; -} - -export function getAotConfig(wco: WebpackConfigOptions) { - const { tsConfigPath, buildOptions } = wco; - - ensureIvy(wco); - - return { - module: { - rules: [ - { - test: /\.tsx?$/, - use: [ - ...(buildOptions.buildOptimizer - ? [ - { - loader: buildOptimizerLoaderPath, - options: { sourceMap: buildOptions.sourceMap.scripts }, - }, - ] - : []), - AngularWebpackLoaderPath, - ], - }, - // "allowJs" support with ivy plugin - ensures build optimizer is not run twice - { - test: /\.jsx?$/, - use: [AngularWebpackLoaderPath], - }, - ], - }, - plugins: [ - createIvyPlugin(wco, true, tsConfigPath), - ], - }; -} - -export function getTypescriptWorkerPlugin(wco: WebpackConfigOptions, workerTsConfigPath: string) { - return createIvyPlugin(wco, false, workerTsConfigPath); -} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/worker.ts b/packages/angular_devkit/build_angular/src/webpack/configs/worker.ts deleted file mode 100644 index 96145c4d21fb..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/configs/worker.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { resolve } from 'path'; -import { Configuration } from 'webpack'; -import { WebpackConfigOptions } from '../../utils/build-options'; -import { getTypescriptWorkerPlugin } from './typescript'; - -export function getWorkerConfig(wco: WebpackConfigOptions): Configuration { - const { buildOptions } = wco; - - if (!buildOptions.webWorkerTsConfig) { - return {}; - } - - if (typeof buildOptions.webWorkerTsConfig != 'string') { - throw new Error('The `webWorkerTsConfig` must be a string.'); - } - - const workerTsConfigPath = resolve(wco.root, buildOptions.webWorkerTsConfig); - - return { - plugins: [getTypescriptWorkerPlugin(wco, workerTsConfigPath)], - }; -} diff --git a/packages/angular_devkit/build_angular/src/webpack/es5-jit-polyfills.js b/packages/angular_devkit/build_angular/src/webpack/es5-jit-polyfills.js deleted file mode 100644 index febcbb5329c9..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/es5-jit-polyfills.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import 'core-js/es/reflect'; diff --git a/packages/angular_devkit/build_angular/src/webpack/es5-polyfills.js b/packages/angular_devkit/build_angular/src/webpack/es5-polyfills.js deleted file mode 100644 index caa670e6268e..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/es5-polyfills.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -// ES2015 symbol capabilities -import 'core-js/es/symbol'; - -// ES2015 function capabilities -import 'core-js/modules/es.function.bind'; -import 'core-js/modules/es.function.name'; -import 'core-js/modules/es.function.has-instance'; - -// ES2015 object capabilities -import 'core-js/modules/es.object.create'; -import 'core-js/modules/es.object.define-property'; -import 'core-js/modules/es.object.define-properties'; -import 'core-js/modules/es.object.get-own-property-descriptor'; -import 'core-js/modules/es.object.get-prototype-of'; -import 'core-js/modules/es.object.keys'; -import 'core-js/modules/es.object.get-own-property-names'; -import 'core-js/modules/es.object.freeze'; -import 'core-js/modules/es.object.seal'; -import 'core-js/modules/es.object.prevent-extensions'; -import 'core-js/modules/es.object.is-frozen'; -import 'core-js/modules/es.object.is-sealed'; -import 'core-js/modules/es.object.is-extensible'; -import 'core-js/modules/es.object.assign'; -import 'core-js/modules/es.object.is'; -import 'core-js/modules/es.object.set-prototype-of'; -import 'core-js/modules/es.object.to-string'; -import 'core-js/modules/es.object.entries'; -import 'core-js/modules/es.object.values'; -import 'core-js/modules/es.object.get-own-property-descriptors'; -import 'core-js/modules/es.object.from-entries'; - -// ES2015 array capabilities -import 'core-js/modules/es.array.concat'; -import 'core-js/modules/es.array.is-array'; -import 'core-js/modules/es.array.from'; -import 'core-js/modules/es.array.of'; -import 'core-js/modules/es.array.join'; -import 'core-js/modules/es.array.slice'; -import 'core-js/modules/es.array.splice'; -import 'core-js/modules/es.array.sort'; -import 'core-js/modules/es.array.for-each'; -import 'core-js/modules/es.array.map'; -import 'core-js/modules/es.array.filter'; -import 'core-js/modules/es.array.some'; -import 'core-js/modules/es.array.every'; -import 'core-js/modules/es.array.reduce'; -import 'core-js/modules/es.array.reduce-right'; -import 'core-js/modules/es.array.index-of'; -import 'core-js/modules/es.array.last-index-of'; -import 'core-js/modules/es.array.copy-within'; -import 'core-js/modules/es.array.fill'; -import 'core-js/modules/es.array.find'; -import 'core-js/modules/es.array.find-index'; -import 'core-js/modules/es.array.iterator'; -import 'core-js/modules/es.array.includes'; -import 'core-js/modules/es.array.flat'; -import 'core-js/modules/es.array.flat-map'; - -// ES2015 string capabilities -import 'core-js/modules/es.string.from-code-point'; -import 'core-js/modules/es.string.raw'; -import 'core-js/modules/es.string.trim'; -import 'core-js/modules/es.string.iterator'; -import 'core-js/modules/es.string.code-point-at'; -import 'core-js/modules/es.string.ends-with'; -import 'core-js/modules/es.string.includes'; -import 'core-js/modules/es.string.repeat'; -import 'core-js/modules/es.string.starts-with'; -import 'core-js/modules/es.string.anchor'; -import 'core-js/modules/es.string.big'; -import 'core-js/modules/es.string.blink'; -import 'core-js/modules/es.string.bold'; -import 'core-js/modules/es.string.fixed'; -import 'core-js/modules/es.string.fontcolor'; -import 'core-js/modules/es.string.fontsize'; -import 'core-js/modules/es.string.italics'; -import 'core-js/modules/es.string.link'; -import 'core-js/modules/es.string.small'; -import 'core-js/modules/es.string.strike'; -import 'core-js/modules/es.string.sub'; -import 'core-js/modules/es.string.sup'; -import 'core-js/modules/es.string.match'; -import 'core-js/modules/es.string.replace'; -import 'core-js/modules/es.string.search'; -import 'core-js/modules/es.string.split'; - -import 'core-js/modules/es.parse-int'; -import 'core-js/modules/es.parse-float'; - -import 'core-js/es/number'; -import 'core-js/es/math'; -import 'core-js/es/date'; - -import 'core-js/modules/es.regexp.constructor'; -import 'core-js/modules/es.regexp.to-string'; -import 'core-js/modules/es.regexp.flags'; - -import 'core-js/modules/es.map'; -import 'core-js/modules/es.weak-map'; -import 'core-js/modules/es.set'; -import 'core-js/modules/web.dom-collections.for-each'; -import 'core-js/modules/web.dom-collections.iterator'; -import 'core-js/modules/es.promise'; -import 'core-js/modules/es.json.to-string-tag'; - -import 'regenerator-runtime/runtime'; - -// Zone.js -import 'zone.js/plugins/zone-legacy'; \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/src/webpack/jit-polyfills.js b/packages/angular_devkit/build_angular/src/webpack/jit-polyfills.js deleted file mode 100644 index b57ee9bd423a..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/jit-polyfills.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import 'core-js/proposals/reflect-metadata'; diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/analytics.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/analytics.ts deleted file mode 100644 index 70867734c69b..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/analytics.ts +++ /dev/null @@ -1,279 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { analytics } from '@angular-devkit/core'; -import { - Compilation, - Compiler, - Module, - Stats, - sources, -} from 'webpack'; - -const NormalModule = require('webpack/lib/NormalModule'); - -interface NormalModule extends Module { - _source?: sources.OriginalSource | null; - resource?: string; -} - -const webpackAllErrorMessageRe = /^([^(]+)\(\d+,\d\): (.*)$/gm; -const webpackTsErrorMessageRe = /^[^(]+\(\d+,\d\): error (TS\d+):/; - -/** - * Faster than using a RegExp, so we use this to count occurences in source code. - * @param source The source to look into. - * @param match The match string to look for. - * @param wordBreak Whether to check for word break before and after a match was found. - * @return The number of matches found. - * @private - */ -export function countOccurrences(source: string, match: string, wordBreak = false): number { - if (match.length == 0) { - return source.length + 1; - } - - let count = 0; - // We condition here so branch prediction happens out of the loop, not in it. - if (wordBreak) { - const re = /\w/; - for (let pos = source.lastIndexOf(match); pos >= 0; pos = source.lastIndexOf(match, pos)) { - if (!(re.test(source[pos - 1] || '') || re.test(source[pos + match.length] || ''))) { - count++; // 1 match, AH! AH! AH! 2 matches, AH! AH! AH! - } - - pos -= match.length; - if (pos < 0) { - break; - } - } - } else { - for (let pos = source.lastIndexOf(match); pos >= 0; pos = source.lastIndexOf(match, pos)) { - count++; // 1 match, AH! AH! AH! 2 matches, AH! AH! AH! - pos -= match.length; - if (pos < 0) { - break; - } - } - } - - return count; -} - - -/** - * Holder of statistics related to the build. - */ -class AnalyticsBuildStats { - public errors: string[] = []; - public numberOfNgOnInit = 0; - public numberOfComponents = 0; - public initialChunkSize = 0; - public totalChunkCount = 0; - public totalChunkSize = 0; - public lazyChunkCount = 0; - public lazyChunkSize = 0; - public assetCount = 0; - public assetSize = 0; - public polyfillSize = 0; - public cssSize = 0; -} - - -/** - * Analytics plugin that reports the analytics we want from the CLI. - */ -export class NgBuildAnalyticsPlugin { - protected _built = false; - protected _stats = new AnalyticsBuildStats(); - - constructor( - protected _projectRoot: string, - protected _analytics: analytics.Analytics, - protected _category: string, - private _isIvy: boolean, - ) { - } - - protected _reset() { - this._stats = new AnalyticsBuildStats(); - } - - protected _getMetrics(stats: Stats) { - const startTime = +(stats.startTime || 0); - const endTime = +(stats.endTime || 0); - const metrics: (string | number)[] = []; - metrics[analytics.NgCliAnalyticsMetrics.BuildTime] = (endTime - startTime); - metrics[analytics.NgCliAnalyticsMetrics.NgOnInitCount] = this._stats.numberOfNgOnInit; - metrics[analytics.NgCliAnalyticsMetrics.NgComponentCount] = this._stats.numberOfComponents; - metrics[analytics.NgCliAnalyticsMetrics.InitialChunkSize] = this._stats.initialChunkSize; - metrics[analytics.NgCliAnalyticsMetrics.TotalChunkCount] = this._stats.totalChunkCount; - metrics[analytics.NgCliAnalyticsMetrics.TotalChunkSize] = this._stats.totalChunkSize; - metrics[analytics.NgCliAnalyticsMetrics.LazyChunkCount] = this._stats.lazyChunkCount; - metrics[analytics.NgCliAnalyticsMetrics.LazyChunkSize] = this._stats.lazyChunkSize; - metrics[analytics.NgCliAnalyticsMetrics.AssetCount] = this._stats.assetCount; - metrics[analytics.NgCliAnalyticsMetrics.AssetSize] = this._stats.assetSize; - metrics[analytics.NgCliAnalyticsMetrics.PolyfillSize] = this._stats.polyfillSize; - metrics[analytics.NgCliAnalyticsMetrics.CssSize] = this._stats.cssSize; - - return metrics; - } - protected _getDimensions() { - const dimensions: (string | number | boolean)[] = []; - - if (this._stats.errors.length) { - // Adding commas before and after so the regex are easier to define filters. - dimensions[analytics.NgCliAnalyticsDimensions.BuildErrors] = `,${this._stats.errors.join()},`; - } - - dimensions[analytics.NgCliAnalyticsDimensions.NgIvyEnabled] = this._isIvy; - - return dimensions; - } - - protected _reportBuildMetrics(stats: Stats) { - const dimensions = this._getDimensions(); - const metrics = this._getMetrics(stats); - this._analytics.event(this._category, 'build', { dimensions, metrics }); - } - - protected _reportRebuildMetrics(stats: Stats) { - const dimensions = this._getDimensions(); - const metrics = this._getMetrics(stats); - this._analytics.event(this._category, 'rebuild', { dimensions, metrics }); - } - - protected _checkTsNormalModule(module: NormalModule) { - if (module._source) { - // PLEASE REMEMBER: - // We're dealing with ES5 _or_ ES2015 JavaScript at this point (we don't know for sure). - - // Just count the ngOnInit occurences. Comments/Strings/calls occurences should be sparse - // so we just consider them within the margin of error. We do break on word break though. - this._stats.numberOfNgOnInit += countOccurrences(module._source.source().toString(), 'ngOnInit', true); - - // Count the number of `Component({` strings (case sensitive), which happens in __decorate(). - this._stats.numberOfComponents += countOccurrences(module._source.source().toString(), 'Component({'); - // For Ivy we just count ɵcmp. - this._stats.numberOfComponents += countOccurrences(module._source.source().toString(), '.ɵcmp', true); - // for ascii_only true - this._stats.numberOfComponents += countOccurrences(module._source.source().toString(), '.\u0275cmp', true); - } - } - - protected _collectErrors(stats: Stats) { - if (stats.hasErrors()) { - for (const errObject of stats.compilation.errors) { - if (errObject instanceof Error) { - const allErrors = errObject.message.match(webpackAllErrorMessageRe); - for (const err of [...allErrors || []].slice(1)) { - const message = (err.match(webpackTsErrorMessageRe) || [])[1]; - if (message) { - // At this point this should be a TS1234. - this._stats.errors.push(message); - } - } - } - } - } - } - - protected _collectBundleStats(compilation: Compilation) { - const chunkAssets = new Set(); - for (const chunk of compilation.chunks) { - if (!chunk.rendered) { - continue; - } - - const firstFile = Array.from(chunk.files)[0]; - const size = compilation.getAsset(firstFile)?.source.size() ?? 0; - chunkAssets.add(firstFile); - - if (chunk.canBeInitial()) { - this._stats.initialChunkSize += size; - } else { - this._stats.lazyChunkCount++; - this._stats.lazyChunkSize += size; - } - - this._stats.totalChunkCount++; - this._stats.totalChunkSize += size; - - if (firstFile.endsWith('.css')) { - this._stats.cssSize += size; - } - } - - for (const asset of compilation.getAssets()) { - // Only count non-JavaScript related files - if (chunkAssets.has(asset.name)) { - continue; - } - - this._stats.assetSize += asset.source.size(); - this._stats.assetCount++; - - if (asset.name == 'polyfill') { - this._stats.polyfillSize += asset.source.size(); - } - } - } - - /************************************************************************************************ - * The next section is all the different Webpack hooks for this plugin. - */ - - /** - * Reports a succeed module. - * @private - */ - protected _succeedModule(mod: Module) { - // Only report NormalModule instances. - if (mod.constructor !== NormalModule) { - return; - } - const module = mod as {} as NormalModule; - - // Only reports modules that are part of the user's project. We also don't do node_modules. - // There is a chance that someone name a file path `hello_node_modules` or something and we - // will ignore that file for the purpose of gathering, but we're willing to take the risk. - if (!module.resource - || !module.resource.startsWith(this._projectRoot) - || module.resource.indexOf('node_modules') >= 0) { - return; - } - - // Check that it's a source file from the project. - if (module.resource.endsWith('.ts')) { - this._checkTsNormalModule(module); - } - } - - protected _compilation(compiler: Compiler, compilation: Compilation) { - this._reset(); - compilation.hooks.succeedModule.tap('NgBuildAnalyticsPlugin', this._succeedModule.bind(this)); - } - - protected _done(stats: Stats) { - this._collectErrors(stats); - this._collectBundleStats(stats.compilation); - if (this._built) { - this._reportRebuildMetrics(stats); - } else { - this._reportBuildMetrics(stats); - this._built = true; - } - } - - apply(compiler: Compiler): void { - compiler.hooks.compilation.tap( - 'NgBuildAnalyticsPlugin', - this._compilation.bind(this, compiler), - ); - compiler.hooks.done.tap('NgBuildAnalyticsPlugin', this._done.bind(this)); - } -} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/analytics_spec.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/analytics_spec.ts deleted file mode 100644 index e0266f68bdee..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/analytics_spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { countOccurrences } from './analytics'; - - -function _randomString(len: number) { - const charSpace = `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`; - - let s = ''; - for (let i = 0; i < len; i++) { - s += charSpace[Math.floor(Math.random() * charSpace.length)]; - } - - return s; -} - -describe('countOccurrences', () => { - // Every use cases is a text, search, word break or not, and expected result. - const useCases: [string, string, boolean, number][] = [ - ['abc1def1ghi1jkl1mno1pqrs1tuvw1xyz', '1', false, 7], // 0 - ['abc1def12ghi1jkl1mno12pqrs12tuvw1xyz12', '12', false, 4], // 1 - ['abc', 'abc', false, 1], // 2 - ['abc', 'abc', true, 1], // 3 - ['aaaaa', 'aaa', false, 1], // 4 - ['aa aaa', 'aaa', true, 1], // 5 - ['aaaaaa', 'aaa', false, 2], // 6 - ['aaa aaa', 'aaa', true, 2], // 7 - ['a', 'a', false, 1], // 8 - ['a', 'a', true, 1], // 9 - ]; - - useCases.forEach(([text, search, wordBreak, expected], i) => { - it(`works (${i})`, () => { - expect(countOccurrences(text, search, wordBreak)).toBe(expected); - }); - }); - - // Random testing. - it('can count (random, wordBreak=false)', () => { - // Generate a random string with injected search strings in it. - let text = _randomString(10000); - const search = _randomString(100).toLowerCase(); - const nb = Math.floor(Math.random() * 200 + 100); - - // Insert nb search string in. - new Array(nb).fill(0) - // Map it with a random position. - .map(() => Math.floor(Math.random() * text.length)) - // Sort from highest to lowest. - .sort((a, b) => b - a) - // Insert the search string for each position created this way. - .forEach(pos => { - text = text.slice(0, pos) + search + text.slice(pos); - }); - - expect(countOccurrences(text, search, false)).toBe(nb); - expect(countOccurrences(text, search, true)).toBe(0); - }); - - it('can count (random, wordBreak=true)', () => { - // Generate a random string with injected search strings in it. - let text = _randomString(10000); - const search = _randomString(100).toLowerCase(); - let nb = Math.floor(Math.random() * 200 + 100); - - // Insert nb search string in. - new Array(nb).fill(0) - // Map it with a random position. - .map(() => Math.floor(Math.random() * text.length)) - // Sort from highest to lowest. - .sort((a, b) => b - a) - // Insert the search string for each position created this way. - .forEach(pos => { - switch (Math.floor(Math.random() * 5)) { - case 0: - // Do not insert a wordbreak. - text = text.slice(0, pos) + search + text.slice(pos); - nb--; - break; - - case 1: text = text.slice(0, pos) + ' ' + search + ' ' + text.slice(pos); break; - case 2: text = text.slice(0, pos) + '(' + search + '$' + text.slice(pos); break; - case 3: text = text.slice(0, pos) + '|' + search + ')' + text.slice(pos); break; - case 4: text = text.slice(0, pos) + '-' + search + '.' + text.slice(pos); break; - } - }); - - expect(countOccurrences(text, search, true)).toBe(nb); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/any-component-style-budget-checker.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/any-component-style-budget-checker.ts index 2810ed3397a6..eb18e0917777 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/any-component-style-budget-checker.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/any-component-style-budget-checker.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -8,8 +8,12 @@ import * as path from 'path'; import { Compilation, Compiler } from 'webpack'; -import { Budget, Type } from '../../browser/schema'; -import { ThresholdSeverity, calculateThresholds, checkThresholds } from '../../utils/bundle-calculator'; +import { Budget, Type } from '../../builders/browser/schema'; +import { + ThresholdSeverity, + calculateThresholds, + checkThresholds, +} from '../../utils/bundle-calculator'; import { addError, addWarning } from '../../utils/webpack-diagnostics'; const PLUGIN_NAME = 'AnyComponentStyleBudgetChecker'; @@ -27,61 +31,62 @@ export class AnyComponentStyleBudgetChecker { apply(compiler: Compiler) { compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { - const afterOptimizeChunkAssets = () => { - // In AOT compilations component styles get processed in child compilations. - // tslint:disable-next-line: no-any - const parentCompilation = (compilation.compiler as any).parentCompilation; - if (!parentCompilation) { - return; - } + compilation.hooks.processAssets.tap( + { + name: PLUGIN_NAME, + stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE, + }, + () => { + // No budgets. + if (this.budgets.length === 0) { + return; + } + + // In AOT compilations component styles get processed in child compilations. + if (!compilation.compiler.parentCompilation) { + return; + } - const cssExtensions = [ - '.css', - '.scss', - '.less', - '.styl', - '.sass', - ]; + const cssExtensions = ['.css', '.scss', '.less', '.sass']; - const componentStyles = Object.keys(compilation.assets) - .filter((name) => cssExtensions.includes(path.extname(name))) - .map((name) => ({ - size: compilation.assets[name].size(), - label: name, - })); - const thresholds = flatMap(this.budgets, (budget) => calculateThresholds(budget)); + const componentStyles = Object.keys(compilation.assets) + .filter((name) => cssExtensions.includes(path.extname(name))) + .map((name) => ({ + size: compilation.assets[name].size(), + label: name, + })); - for (const {size, label} of componentStyles) { - for (const {severity, message} of checkThresholds(thresholds[Symbol.iterator](), size, label)) { - switch (severity) { - case ThresholdSeverity.Warning: - addWarning(compilation, message); - break; - case ThresholdSeverity.Error: - addError(compilation, message); - break; - default: - assertNever(severity); - break; + const thresholds = this.budgets.flatMap((budget) => [...calculateThresholds(budget)]); + for (const { size, label } of componentStyles) { + for (const { severity, message } of checkThresholds( + thresholds[Symbol.iterator](), + size, + label, + )) { + switch (severity) { + case ThresholdSeverity.Warning: + addWarning(compilation, message); + break; + case ThresholdSeverity.Error: + addError(compilation, message); + break; + default: + assertNever(severity); + } } } - } - }; - - compilation.hooks.processAssets.tap({ - name: PLUGIN_NAME, - stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE, - }, afterOptimizeChunkAssets); + }, + ); }); } } function assertNever(input: never): never { - throw new Error(`Unexpected call to assertNever() with input: ${ - JSON.stringify(input, null /* replacer */, 4 /* tabSize */)}`); -} - -function flatMap(list: T[], mapper: (item: T, index: number, array: T[]) => IterableIterator): R[] { - return ([] as R[]).concat(...list.map(mapper).map((iterator) => Array.from(iterator))); - + throw new Error( + `Unexpected call to assertNever() with input: ${JSON.stringify( + input, + null /* replacer */, + 4 /* tabSize */, + )}`, + ); } diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/builder-watch-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/builder-watch-plugin.ts index 3d9a41b70553..3656b93e2f69 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/builder-watch-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/builder-watch-plugin.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Compiler } from 'webpack'; export type BuilderWatcherCallback = ( @@ -19,17 +20,6 @@ export interface BuilderWatcherFactory { ): { close(): void }; } -export interface WebpackWatcher { - close(): void; - pause(): void; - // Webpack 4 - getFileTimestamps(): Map; - getContextTimestamps(): Map; - // Webpack 5 - getFileTimeInfoEntries(): Map; - getContextTimeInfoEntries(): Map; -} - class TimeInfoMap extends Map { update(path: string, timestamp: number): void { this.set(path, Object.freeze({ safeTime: timestamp, timestamp })); @@ -45,30 +35,15 @@ class TimeInfoMap extends Map { } } -type WatchCallback = ( - error: Error | undefined, - files: Map, - contexts: Map, - changes: Set, - removals: Set, -) => void; - -export interface WebpackWatchFileSystem { - watch( - files: Iterable, - directories: Iterable, - missing: Iterable, - startTime: number, - options: {}, - callback: WatchCallback, - callbackUndelayed: (file: string, time: number) => void, - ): WebpackWatcher; -} +// Extract watch related types from the Webpack compiler type since they are not directly exported +type WebpackWatchFileSystem = Compiler['watchFileSystem']; +type WatchOptions = Parameters[4]; +type WatchCallback = Parameters[5]; class BuilderWatchFileSystem implements WebpackWatchFileSystem { constructor( private readonly watcherFactory: BuilderWatcherFactory, - private readonly inputFileSystem: { purge?(path?: string): void }, + private readonly inputFileSystem: Compiler['inputFileSystem'], ) {} watch( @@ -76,10 +51,10 @@ class BuilderWatchFileSystem implements WebpackWatchFileSystem { directories: Iterable, missing: Iterable, startTime: number, - _options: {}, + _options: WatchOptions, callback: WatchCallback, callbackUndelayed?: (file: string, time: number) => void, - ): WebpackWatcher { + ): ReturnType { const watchedFiles = new Set(files); const watchedDirectories = new Set(directories); const watchedMissing = new Set(missing); @@ -125,10 +100,12 @@ class BuilderWatchFileSystem implements WebpackWatchFileSystem { } } + const timeInfoMap = new Map(timeInfo); + callback( undefined, - new Map(timeInfo), - new Map(timeInfo), + timeInfoMap, + timeInfoMap, new Set([...fileChanges, ...directoryChanges, ...missingChanges]), removals, ); @@ -140,12 +117,6 @@ class BuilderWatchFileSystem implements WebpackWatchFileSystem { watcher.close(); }, pause() {}, - getFileTimestamps() { - return timeInfo.toTimestamps(); - }, - getContextTimestamps() { - return timeInfo.toTimestamps(); - }, getFileTimeInfoEntries() { return new Map(timeInfo); }, @@ -159,7 +130,7 @@ class BuilderWatchFileSystem implements WebpackWatchFileSystem { export class BuilderWatchPlugin { constructor(private readonly watcherFactory: BuilderWatcherFactory) {} - apply(compiler: Compiler & { watchFileSystem: unknown }): void { + apply(compiler: Compiler): void { compiler.hooks.environment.tap('BuilderWatchPlugin', () => { compiler.watchFileSystem = new BuilderWatchFileSystem( this.watcherFactory, diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/common-js-usage-warn-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/common-js-usage-warn-plugin.ts index 46b6ef783205..a6e12bf72c33 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/common-js-usage-warn-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/common-js-usage-warn-plugin.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -11,8 +11,10 @@ import { Compilation, Compiler, Dependency, Module, NormalModule } from 'webpack import { addWarning } from '../../utils/webpack-diagnostics'; // Webpack doesn't export these so the deep imports can potentially break. -const CommonJsRequireDependency = require('webpack/lib/dependencies/CommonJsRequireDependency'); const AMDDefineDependency = require('webpack/lib/dependencies/AMDDefineDependency'); +const CommonJsExportsDependency = require('webpack/lib/dependencies/CommonJsExportsDependency'); +const CommonJsRequireDependency = require('webpack/lib/dependencies/CommonJsRequireDependency'); +const CommonJsSelfReferenceDependency = require('webpack/lib/dependencies/CommonJsSelfReferenceDependency'); export interface CommonJsUsageWarnPluginOptions { /** A list of CommonJS packages that are allowed to be used without a warning. */ @@ -28,8 +30,8 @@ export class CommonJsUsageWarnPlugin { } apply(compiler: Compiler) { - compiler.hooks.compilation.tap('CommonJsUsageWarnPlugin', compilation => { - compilation.hooks.finishModules.tap('CommonJsUsageWarnPlugin', modules => { + compiler.hooks.compilation.tap('CommonJsUsageWarnPlugin', (compilation) => { + compilation.hooks.finishModules.tap('CommonJsUsageWarnPlugin', (modules) => { const mainEntry = compilation.entries.get('main'); if (!mainEntry) { return; @@ -39,7 +41,7 @@ export class CommonJsUsageWarnPlugin { ); for (const module of modules) { - const {dependencies, rawRequest} = module as NormalModule; + const { dependencies, rawRequest } = module as NormalModule; if ( !rawRequest || rawRequest.startsWith('.') || @@ -63,7 +65,10 @@ export class CommonJsUsageWarnPlugin { // Check if it's parent issuer is also a CommonJS dependency. // In case it is skip as an warning will be show for the parent CommonJS dependency. const parentDependencies = getIssuer(compilation, issuer)?.dependencies; - if (parentDependencies && this.hasCommonJsDependencies(compilation, parentDependencies, true)) { + if ( + parentDependencies && + this.hasCommonJsDependencies(compilation, parentDependencies, true) + ) { continue; } @@ -78,9 +83,9 @@ export class CommonJsUsageWarnPlugin { // Only show warnings for modules from main entrypoint. // And if the issuer request is not from 'webpack-dev-server', as 'webpack-dev-server' // will require CommonJS libraries for live reloading such as 'sockjs-node'. - // tslint:disable-next-line: no-any if (mainIssuer && mainModules.has(mainIssuer)) { - const warning = `${(issuer as NormalModule | null)?.userRequest} depends on '${rawRequest}'. ` + + const warning = + `${(issuer as NormalModule | null)?.userRequest} depends on '${rawRequest}'. ` + 'CommonJS or AMD dependencies can cause optimization bailouts.\n' + 'For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies'; @@ -99,9 +104,15 @@ export class CommonJsUsageWarnPlugin { private hasCommonJsDependencies( compilation: Compilation, dependencies: Dependency[], - checkParentModules = false): boolean { + checkParentModules = false, + ): boolean { for (const dep of dependencies) { - if (dep instanceof CommonJsRequireDependency || dep instanceof AMDDefineDependency) { + if ( + dep instanceof CommonJsRequireDependency || + dep instanceof CommonJsExportsDependency || + dep instanceof CommonJsSelfReferenceDependency || + dep instanceof AMDDefineDependency + ) { return true; } @@ -118,10 +129,10 @@ export class CommonJsUsageWarnPlugin { private rawRequestToPackageName(rawRequest: string): string { return rawRequest.startsWith('@') - // Scoped request ex: @angular/common/locale/en -> @angular/common - ? rawRequest.split('/', 2).join('/') - // Non-scoped request ex: lodash/isEmpty -> lodash - : rawRequest.split('/', 1)[0]; + ? // Scoped request ex: @angular/common/locale/en -> @angular/common + rawRequest.split('/', 2).join('/') + : // Non-scoped request ex: lodash/isEmpty -> lodash + rawRequest.split('/', 1)[0]; } } diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/css-optimizer-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/css-optimizer-plugin.ts new file mode 100644 index 000000000000..548ce7b63372 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/css-optimizer-plugin.ts @@ -0,0 +1,162 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { Message, TransformResult } from 'esbuild'; +import type { Compilation, Compiler, sources } from 'webpack'; +import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets'; +import { addWarning } from '../../utils/webpack-diagnostics'; +import { EsbuildExecutor } from './esbuild-executor'; + +/** + * The name of the plugin provided to Webpack when tapping Webpack compiler hooks. + */ +const PLUGIN_NAME = 'angular-css-optimizer'; + +export interface CssOptimizerPluginOptions { + supportedBrowsers?: string[]; +} + +/** + * A Webpack plugin that provides CSS optimization capabilities. + * + * The plugin uses both `esbuild` to provide both fast and highly-optimized + * code output. + */ +export class CssOptimizerPlugin { + private targets: string[] | undefined; + private esbuild = new EsbuildExecutor(); + + constructor(options?: CssOptimizerPluginOptions) { + if (options?.supportedBrowsers) { + this.targets = transformSupportedBrowsersToTargets(options.supportedBrowsers); + } + } + + apply(compiler: Compiler) { + const { OriginalSource, SourceMapSource } = compiler.webpack.sources; + + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { + const logger = compilation.getLogger('build-angular.CssOptimizerPlugin'); + + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, + }, + async (compilationAssets) => { + const cache = compilation.options.cache && compilation.getCache(PLUGIN_NAME); + + logger.time('optimize css assets'); + for (const assetName of Object.keys(compilationAssets)) { + if (!/\.(?:css|scss|sass|less)$/.test(assetName)) { + continue; + } + + const asset = compilation.getAsset(assetName); + // Skip assets that have already been optimized or are verbatim copies (project assets) + if (!asset || asset.info.minimized || asset.info.copied) { + continue; + } + + const { source: styleAssetSource, name } = asset; + let cacheItem; + + if (cache) { + const eTag = cache.getLazyHashedEtag(styleAssetSource); + cacheItem = cache.getItemCache(name, eTag); + const cachedOutput = await cacheItem.getPromise< + { source: sources.Source; warnings: Message[] } | undefined + >(); + + if (cachedOutput) { + logger.debug(`${name} restored from cache`); + await this.addWarnings(compilation, cachedOutput.warnings); + compilation.updateAsset(name, cachedOutput.source, (assetInfo) => ({ + ...assetInfo, + minimized: true, + })); + continue; + } + } + + const { source, map: inputMap } = styleAssetSource.sourceAndMap(); + const input = typeof source === 'string' ? source : source.toString(); + + const optimizeAssetLabel = `optimize asset: ${asset.name}`; + logger.time(optimizeAssetLabel); + const { code, warnings, map } = await this.optimize( + input, + asset.name, + inputMap, + this.targets, + ); + logger.timeEnd(optimizeAssetLabel); + + await this.addWarnings(compilation, warnings); + + const optimizedAsset = map + ? new SourceMapSource(code, name, map) + : new OriginalSource(code, name); + compilation.updateAsset(name, optimizedAsset, (assetInfo) => ({ + ...assetInfo, + minimized: true, + })); + + await cacheItem?.storePromise({ + source: optimizedAsset, + warnings, + }); + } + logger.timeEnd('optimize css assets'); + }, + ); + }); + } + + /** + * Optimizes a CSS asset using esbuild. + * + * @param input The CSS asset source content to optimize. + * @param name The name of the CSS asset. Used to generate source maps. + * @param inputMap Optionally specifies the CSS asset's original source map that will + * be merged with the intermediate optimized source map. + * @param target Optionally specifies the target browsers for the output code. + * @returns A promise resolving to the optimized CSS, source map, and any warnings. + */ + private optimize( + input: string, + name: string, + inputMap: object, + target: string[] | undefined, + ): Promise { + let sourceMapLine; + if (inputMap) { + // esbuild will automatically remap the sourcemap if provided + sourceMapLine = `\n/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${Buffer.from( + JSON.stringify(inputMap), + ).toString('base64')} */`; + } + + return this.esbuild.transform(sourceMapLine ? input + sourceMapLine : input, { + loader: 'css', + legalComments: 'inline', + minify: true, + sourcemap: !!inputMap && 'external', + sourcefile: name, + target, + }); + } + + private async addWarnings(compilation: Compilation, warnings: Message[]) { + if (warnings.length > 0) { + for (const warning of await this.esbuild.formatMessages(warnings, { kind: 'warning' })) { + addWarning(compilation, warning); + } + } + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/dedupe-module-resolve-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/dedupe-module-resolve-plugin.ts index b4366fb8337e..5c44420db96c 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/dedupe-module-resolve-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/dedupe-module-resolve-plugin.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Compiler } from 'webpack'; import { addWarning } from '../../utils/webpack-diagnostics'; @@ -19,32 +20,16 @@ export interface DedupeModuleResolvePluginOptions { verbose?: boolean; } -// tslint:disable-next-line: no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any function getResourceData(resolveData: any): ResourceData { - if (resolveData.createData) { - // Webpack 5+ - const { - descriptionFileData, - relativePath, - } = resolveData.createData.resourceResolveData; - - return { - packageName: descriptionFileData?.name, - packageVersion: descriptionFileData?.version, - relativePath, - resource: resolveData.createData.resource, - }; - } else { - // Webpack 4 - const { resource, resourceResolveData } = resolveData; - - return { - packageName: resourceResolveData.descriptionFileData?.name, - packageVersion: resourceResolveData.descriptionFileData?.version, - relativePath: resourceResolveData.relativePath, - resource: resource, - }; - } + const { descriptionFileData, relativePath } = resolveData.createData.resourceResolveData; + + return { + packageName: descriptionFileData?.name, + packageVersion: descriptionFileData?.version, + relativePath, + resource: resolveData.createData.resource, + }; } /** @@ -57,58 +42,56 @@ function getResourceData(resolveData: any): ResourceData { * @see https://github.com/webpack/webpack/blob/4a1f068828c2ab47537d8be30d542cd3a1076db4/lib/NormalModuleReplacementPlugin.js#L9 */ export class DedupeModuleResolvePlugin { - modules = new Map(); + modules = new Map(); - constructor(private options?: DedupeModuleResolvePluginOptions) { } + constructor(private options?: DedupeModuleResolvePluginOptions) {} apply(compiler: Compiler) { - compiler.hooks.compilation.tap('DedupeModuleResolvePlugin', (compilation, { normalModuleFactory }) => { - normalModuleFactory.hooks.afterResolve.tap('DedupeModuleResolvePlugin', (result) => { - if (!result) { - return; - } - - const { packageName, packageVersion, relativePath, resource } = getResourceData(result); - - // Empty name or versions are no valid primary entrypoints of a library - if (!packageName || !packageVersion) { - return; - } - - const moduleId = packageName + '@' + packageVersion + ':' + relativePath; - const prevResolvedModule = this.modules.get(moduleId); - - if (!prevResolvedModule) { - // This is the first time we visit this module. - this.modules.set(moduleId, { - resource, - request: result.request, - }); - - return; - } - - const { resource: prevResource, request: prevRequest } = prevResolvedModule; - if (resource === prevResource) { - // No deduping needed. - // Current path and previously resolved path are the same. - return; - } - - if (this.options?.verbose) { - addWarning(compilation, `[DedupeModuleResolvePlugin]: ${resource} -> ${prevResource}`); - } - - // Alter current request with previously resolved module. - // tslint:disable-next-line: no-any - const createData = (result as any).createData; - if (createData) { + compiler.hooks.compilation.tap( + 'DedupeModuleResolvePlugin', + (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.afterResolve.tap('DedupeModuleResolvePlugin', (result) => { + if (!result) { + return; + } + + const { packageName, packageVersion, relativePath, resource } = getResourceData(result); + + // Empty name or versions are no valid primary entrypoints of a library + if (!packageName || !packageVersion) { + return; + } + + const moduleId = packageName + '@' + packageVersion + ':' + relativePath; + const prevResolvedModule = this.modules.get(moduleId); + + if (!prevResolvedModule) { + // This is the first time we visit this module. + this.modules.set(moduleId, { + resource, + request: result.request, + }); + + return; + } + + const { resource: prevResource, request: prevRequest } = prevResolvedModule; + if (resource === prevResource) { + // No deduping needed. + // Current path and previously resolved path are the same. + return; + } + + if (this.options?.verbose) { + addWarning(compilation, `[DedupeModuleResolvePlugin]: ${resource} -> ${prevResource}`); + } + + // Alter current request with previously resolved module. + const createData = result.createData as { resource: string; userRequest: string }; createData.resource = prevResource; - createData.userRequest = prevResource; - } else { - result.request = prevRequest; - } - }); - }); + createData.userRequest = prevRequest; + }); + }, + ); } } diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/devtools-ignore-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/devtools-ignore-plugin.ts new file mode 100644 index 000000000000..a2f43a2e0c6b --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/devtools-ignore-plugin.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Compilation, Compiler } from 'webpack'; + +// Following the naming conventions from +// https://sourcemaps.info/spec.html#h.ghqpj1ytqjbm +const IGNORE_LIST = 'x_google_ignoreList'; + +const PLUGIN_NAME = 'devtools-ignore-plugin'; + +interface SourceMap { + sources: string[]; + [IGNORE_LIST]: number[]; +} + +/** + * This plugin adds a field to source maps that identifies which sources are + * vendored or runtime-injected (aka third-party) sources. These are consumed by + * Chrome DevTools to automatically ignore-list sources. + */ +export class DevToolsIgnorePlugin { + apply(compiler: Compiler) { + const { RawSource } = compiler.webpack.sources; + + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { + compilation.hooks.processAssets.tap( + { + name: PLUGIN_NAME, + stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, + additionalAssets: true, + }, + (assets) => { + for (const [name, asset] of Object.entries(assets)) { + // Instead of using `asset.map()` to fetch the source maps from + // SourceMapSource assets, process them directly as a RawSource. + // This is because `.map()` is slow and can take several seconds. + if (!name.endsWith('.map')) { + // Ignore non source map files. + continue; + } + + const mapContent = asset.source().toString(); + if (!mapContent) { + continue; + } + + const map = JSON.parse(mapContent) as SourceMap; + const ignoreList = []; + + for (const [index, path] of map.sources.entries()) { + if (path.includes('/node_modules/') || path.startsWith('webpack/')) { + ignoreList.push(index); + } + } + + map[IGNORE_LIST] = ignoreList; + compilation.updateAsset(name, new RawSource(JSON.stringify(map))); + } + }, + ); + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/esbuild-executor.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/esbuild-executor.ts new file mode 100644 index 000000000000..5f2ad37f3c77 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/esbuild-executor.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { + FormatMessagesOptions, + PartialMessage, + TransformOptions, + TransformResult, +} from 'esbuild'; + +/** + * Provides the ability to execute esbuild regardless of the current platform's support + * for using the native variant of esbuild. The native variant will be preferred (assuming + * the `alwaysUseWasm` constructor option is `false) due to its inherent performance advantages. + * At first use of esbuild, a supportability test will be automatically performed and the + * WASM-variant will be used if needed by the platform. + */ +export class EsbuildExecutor + implements Pick +{ + private esbuildTransform: this['transform']; + private esbuildFormatMessages: this['formatMessages']; + private initialized = false; + + /** + * Constructs an instance of the `EsbuildExecutor` class. + * + * @param alwaysUseWasm If true, the WASM-variant will be preferred and no support test will be + * performed; if false (default), the native variant will be preferred. + */ + constructor(private alwaysUseWasm = false) { + this.esbuildTransform = this.esbuildFormatMessages = () => { + throw new Error('esbuild implementation missing'); + }; + } + + /** + * Determines whether the native variant of esbuild can be used on the current platform. + * + * @returns A promise which resolves to `true`, if the native variant of esbuild is support or `false`, if the WASM variant is required. + */ + static async hasNativeSupport(): Promise { + // Try to use native variant to ensure it is functional for the platform. + try { + const { formatMessages } = await import('esbuild'); + await formatMessages([], { kind: 'error' }); + + return true; + } catch { + return false; + } + } + + /** + * Initializes the esbuild transform and format messages functions. + * + * @returns A promise that fulfills when esbuild has been loaded and available for use. + */ + private async ensureEsbuild(): Promise { + if (this.initialized) { + return; + } + + // If the WASM variant was preferred at class construction or native is not supported, use WASM + if (this.alwaysUseWasm || !(await EsbuildExecutor.hasNativeSupport())) { + await this.useWasm(); + this.initialized = true; + + return; + } + + try { + // Use the faster native variant if available. + const { transform, formatMessages } = await import('esbuild'); + + this.esbuildTransform = transform; + this.esbuildFormatMessages = formatMessages; + } catch { + // If the native variant is not installed then use the WASM-based variant + await this.useWasm(); + } + + this.initialized = true; + } + + /** + * Transitions an executor instance to use the WASM-variant of esbuild. + */ + private async useWasm(): Promise { + const { transform, formatMessages } = await import('esbuild-wasm'); + this.esbuildTransform = transform; + this.esbuildFormatMessages = formatMessages; + + // The ESBUILD_BINARY_PATH environment variable cannot exist when attempting to use the + // WASM variant. If it is then the binary located at the specified path will be used instead + // of the WASM variant. + delete process.env.ESBUILD_BINARY_PATH; + + this.alwaysUseWasm = true; + } + + async transform( + input: string | Uint8Array, + options?: TransformOptions, + ): Promise { + await this.ensureEsbuild(); + + return this.esbuildTransform(input, options); + } + + async formatMessages( + messages: PartialMessage[], + options: FormatMessagesOptions, + ): Promise { + await this.ensureEsbuild(); + + return this.esbuildFormatMessages(messages, options); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-accept.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-accept.ts index 10aee6b52427..ef0e5a7cd162 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-accept.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-accept.ts @@ -1,21 +1,27 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable-next-line: no-implicit-dependencies -import { ApplicationRef, PlatformRef, Type, isDevMode, ɵresetCompiledComponents } from '@angular/core'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { + ApplicationRef, + PlatformRef, + Type, + isDevMode, + ɵresetCompiledComponents, +} from '@angular/core'; import { filter, take } from 'rxjs/operators'; // For the time being we cannot use the DOM lib because it conflicts with @types/node, // In future when we remove `yarn admin build` we should have this as a seperate compilation unit // which includes DOM lib. -// tslint:disable: no-console -// tslint:disable: no-any +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-explicit-any */ declare const ng: any; declare const document: any; declare const MutationObserver: any; @@ -28,7 +34,9 @@ export default function (mod: any): void { } if (!isDevMode()) { - console.error(`[NG HMR] Cannot use HMR when Angular is running in production mode. To prevent production mode, do not call 'enableProdMode()'.`); + console.error( + `[NG HMR] Cannot use HMR when Angular is running in production mode. To prevent production mode, do not call 'enableProdMode()'.`, + ); return; } @@ -36,7 +44,9 @@ export default function (mod: any): void { mod['hot'].accept(); mod['hot'].dispose(() => { if (typeof ng === 'undefined') { - console.warn(`[NG HMR] Cannot find global 'ng'. Likely this is caused because scripts optimization is enabled.`); + console.warn( + `[NG HMR] Cannot find global 'ng'. Likely this is caused because scripts optimization is enabled.`, + ); return; } @@ -63,17 +73,13 @@ export default function (mod: any): void { const oldOptions = document.querySelectorAll('option'); // Create new application - appRef.components - .forEach(cp => { - const element = cp.location.nativeElement; - const parentNode = element.parentNode; - parentNode.insertBefore( - document.createElement(element.tagName), - element, - ); - - parentNode.removeChild(element); - }); + appRef.components.forEach((cp) => { + const element = cp.location.nativeElement; + const parentNode = element.parentNode; + parentNode.insertBefore(document.createElement(element.tagName), element); + + parentNode.removeChild(element); + }); // Destroy old application, injectors, !!isStable), + filter((isStable) => !!isStable), take(1), ) .subscribe(() => restoreFormValues(oldInputs, oldOptions)); - }) - .observe(bodyElement, { - attributes: true, - subtree: true, - attributeFilter: ['ng-version'], - }); + }).observe(bodyElement, { + attributes: true, + subtree: true, + attributeFilter: ['ng-version'], + }); }); } @@ -129,8 +134,8 @@ function getAppRoot(): any { return appRoot; } -function getToken(appRoot: any, token: Type ): T | undefined { - return typeof ng === 'object' && ng.getInjector(appRoot).get(token) || undefined; +function getToken(appRoot: any, token: Type): T | undefined { + return (typeof ng === 'object' && ng.getInjector(appRoot).get(token)) || undefined; } function getApplicationRef(appRoot: any): ApplicationRef | undefined { @@ -156,10 +161,12 @@ function getPlatformRef(appRoot: any): PlatformRef | undefined { } function dispatchEvents(element: any): void { - element.dispatchEvent(new Event('input', { - bubbles: true, - cancelable: true, - })); + element.dispatchEvent( + new Event('input', { + bubbles: true, + cancelable: true, + }), + ); element.blur(); @@ -190,7 +197,6 @@ function restoreFormValues(oldInputs: any[], oldOptions: any[]): void { case 'date': case 'datetime-local': case 'email': - case 'file': case 'hidden': case 'month': case 'number': @@ -205,6 +211,10 @@ function restoreFormValues(oldInputs: any[], oldOptions: any[]): void { case 'week': newElement.value = oldElement.value; break; + case 'file': + // Ignored due: Uncaught DOMException: Failed to set the 'value' property on 'HTMLInputElement': + // This input element accepts a filename, which may only be programmatically set to the empty string. + break; default: console.warn('[NG HMR] Unknown input type ' + oldElement.type + '.'); continue; diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-loader.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-loader.ts index cc7b813c5809..45e80733f695 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-loader.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-loader.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -11,14 +11,11 @@ import { join } from 'path'; export const HmrLoader = __filename; const hmrAcceptPath = join(__dirname, './hmr-accept.js').replace(/\\/g, '/'); -export default function ( - // tslint:disable-next-line: no-any - this: any, +export default function localizeExtractLoader( + this: import('webpack').LoaderContext<{}>, content: string, - // Source map types are broken in the webpack type definitions - // tslint:disable-next-line: no-any - map: any, -): void { + map: Parameters[1], +) { const source = `${content} // HMR Accept Code diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/index-html-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/index-html-webpack-plugin.ts index 0bf19d407a1c..d7cfc58704e9 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/index-html-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/index-html-webpack-plugin.ts @@ -1,21 +1,25 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { basename, dirname, extname } from 'path'; import { Compilation, Compiler, sources } from 'webpack'; +import { assertIsError } from '../../utils/error'; import { FileInfo } from '../../utils/index-file/augment-index-html'; -import { IndexHtmlGenerator, IndexHtmlGeneratorOptions, IndexHtmlGeneratorProcessOptions } from '../../utils/index-file/index-html-generator'; +import { + IndexHtmlGenerator, + IndexHtmlGeneratorOptions, + IndexHtmlGeneratorProcessOptions, +} from '../../utils/index-file/index-html-generator'; import { addError, addWarning } from '../../utils/webpack-diagnostics'; -export interface IndexHtmlWebpackPluginOptions extends IndexHtmlGeneratorOptions, - Omit { - noModuleEntrypoints: string[]; - moduleEntrypoints: string[]; -} +export interface IndexHtmlWebpackPluginOptions + extends IndexHtmlGeneratorOptions, + Omit {} const PLUGIN_NAME = 'index-html-webpack-plugin'; export class IndexHtmlWebpackPlugin extends IndexHtmlGenerator { @@ -28,52 +32,42 @@ export class IndexHtmlWebpackPlugin extends IndexHtmlGenerator { throw new Error('compilation is undefined.'); } - constructor(readonly options: IndexHtmlWebpackPluginOptions) { + constructor(override readonly options: IndexHtmlWebpackPluginOptions) { super(options); } apply(compiler: Compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { this._compilation = compilation; - compilation.hooks.processAssets.tapPromise({ - name: PLUGIN_NAME, - stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + 1, - }, callback); + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + 1, + }, + callback, + ); }); const callback = async (assets: Record) => { - // Get all files for selected entrypoints const files: FileInfo[] = []; - const noModuleFiles: FileInfo[] = []; - const moduleFiles: FileInfo[] = []; try { - for (const [entryName, entrypoint] of this.compilation.entrypoints) { - const entryFiles: FileInfo[] = entrypoint?.getFiles()?.map( - (f: string): FileInfo => ({ - name: entryName, - file: f, - extension: extname(f), - }), - ); - - if (!entryFiles) { - continue; - } - - if (this.options.noModuleEntrypoints.includes(entryName)) { - noModuleFiles.push(...entryFiles); - } else if (this.options.moduleEntrypoints.includes(entryName)) { - moduleFiles.push(...entryFiles); - } else { - files.push(...entryFiles); + for (const chunk of this.compilation.chunks) { + for (const file of chunk.files) { + if (file.endsWith('.hot-update.js')) { + continue; + } + + files.push({ + name: chunk.name, + file, + extension: extname(file), + }); } } const { content, warnings, errors } = await this.process({ files, - noModuleFiles, - moduleFiles, outputPath: dirname(this.options.outputPath), baseHref: this.options.baseHref, lang: this.options.lang, @@ -81,32 +75,36 @@ export class IndexHtmlWebpackPlugin extends IndexHtmlGenerator { assets[this.options.outputPath] = new sources.RawSource(content); - warnings.forEach(msg => addWarning(this.compilation, msg)); - errors.forEach(msg => addError(this.compilation, msg)); + warnings.forEach((msg) => addWarning(this.compilation, msg)); + errors.forEach((msg) => addError(this.compilation, msg)); } catch (error) { + assertIsError(error); addError(this.compilation, error.message); } }; } - async readAsset(path: string): Promise { + override async readAsset(path: string): Promise { const data = this.compilation.assets[basename(path)].source(); return typeof data === 'string' ? data : data.toString(); } - protected async readIndex(path: string): Promise { + protected override async readIndex(path: string): Promise { return new Promise((resolve, reject) => { - this.compilation.inputFileSystem.readFile(path, (err?: Error, data?: string | Buffer) => { - if (err) { - reject(err); + this.compilation.inputFileSystem.readFile( + path, + (err?: Error | null, data?: string | Buffer) => { + if (err) { + reject(err); - return; - } + return; + } - this.compilation.fileDependencies.add(path); - resolve(data?.toString() ?? ''); - }); + this.compilation.fileDependencies.add(path); + resolve(data?.toString() ?? ''); + }, + ); }); } } diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/index.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/index.ts index 802f415ff460..70b29f8529ff 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/index.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -8,12 +8,13 @@ // Exports the webpack plugins we use internally. export { AnyComponentStyleBudgetChecker } from './any-component-style-budget-checker'; -export { OptimizeCssWebpackPlugin, OptimizeCssWebpackPluginOptions } from './optimize-css-webpack-plugin'; export { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } from './scripts-webpack-plugin'; export { SuppressExtractedTextChunksWebpackPlugin } from './suppress-entry-chunks-webpack-plugin'; export { RemoveHashPlugin, RemoveHashPluginOptions } from './remove-hash-plugin'; export { DedupeModuleResolvePlugin } from './dedupe-module-resolve-plugin'; export { CommonJsUsageWarnPlugin } from './common-js-usage-warn-plugin'; +export { JsonStatsPlugin } from './json-stats-plugin'; +export { JavaScriptOptimizerPlugin } from './javascript-optimizer-plugin'; export { default as PostcssCliResources, PostcssCliResourcesOptions, diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-plugin.ts new file mode 100644 index 000000000000..b6f02d40a544 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-plugin.ts @@ -0,0 +1,242 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import Piscina from 'piscina'; +import type { Compiler, sources } from 'webpack'; +import { maxWorkers } from '../../utils/environment-options'; +import { transformSupportedBrowsersToTargets } from '../../utils/esbuild-targets'; +import { addError } from '../../utils/webpack-diagnostics'; +import { EsbuildExecutor } from './esbuild-executor'; +import type { OptimizeRequestOptions } from './javascript-optimizer-worker'; + +/** + * The maximum number of Workers that will be created to execute optimize tasks. + */ +const MAX_OPTIMIZE_WORKERS = maxWorkers; + +/** + * The name of the plugin provided to Webpack when tapping Webpack compiler hooks. + */ +const PLUGIN_NAME = 'angular-javascript-optimizer'; + +/** + * The options used to configure the {@link JavaScriptOptimizerPlugin}. + */ +export interface JavaScriptOptimizerOptions { + /** + * Enables advanced optimizations in the underlying JavaScript optimizers. + * This currently increases the `terser` passes to 2 and enables the `pure_getters` + * option for `terser`. + */ + advanced?: boolean; + + /** + * An object record of string keys that will be replaced with their respective values when found + * within the code during optimization. + */ + define: Record; + + /** + * Enables the generation of a sourcemap during optimization. + * The output sourcemap will be a full sourcemap containing the merge of the input sourcemap and + * all intermediate sourcemaps. + */ + sourcemap?: boolean; + + /** + * A list of supported browsers that is used for output code. + */ + supportedBrowsers?: string[]; + + /** + * Enables the retention of identifier names and ensures that function and class names are + * present in the output code. + * + * **Note**: in some cases symbols are still renamed to avoid collisions. + */ + keepIdentifierNames: boolean; + + /** + * Enables the retention of original name of classes and functions. + * + * **Note**: this causes increase of bundle size as it causes dead-code elimination to not work fully. + */ + keepNames: boolean; + + /** + * Enables the removal of all license comments from the output code. + */ + removeLicenses?: boolean; +} + +/** + * A Webpack plugin that provides JavaScript optimization capabilities. + * + * The plugin uses both `esbuild` and `terser` to provide both fast and highly-optimized + * code output. `esbuild` is used as an initial pass to remove the majority of unused code + * as well as shorten identifiers. `terser` is then used as a secondary pass to apply + * optimizations not yet implemented by `esbuild`. + */ +export class JavaScriptOptimizerPlugin { + private targets: string[] | undefined; + + constructor(private options: JavaScriptOptimizerOptions) { + if (options.supportedBrowsers) { + this.targets = transformSupportedBrowsersToTargets(options.supportedBrowsers); + } + } + + apply(compiler: Compiler) { + const { OriginalSource, SourceMapSource } = compiler.webpack.sources; + + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { + const logger = compilation.getLogger('build-angular.JavaScriptOptimizerPlugin'); + + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, + }, + async (compilationAssets) => { + logger.time('optimize js assets'); + const scriptsToOptimize = []; + const cache = + compilation.options.cache && compilation.getCache('JavaScriptOptimizerPlugin'); + + // Analyze the compilation assets for scripts that require optimization + for (const assetName of Object.keys(compilationAssets)) { + if (!assetName.endsWith('.js')) { + continue; + } + + const scriptAsset = compilation.getAsset(assetName); + // Skip assets that have already been optimized or are verbatim copies (project assets) + if (!scriptAsset || scriptAsset.info.minimized || scriptAsset.info.copied) { + continue; + } + + const { source: scriptAssetSource, name } = scriptAsset; + let cacheItem; + + if (cache) { + const eTag = cache.getLazyHashedEtag(scriptAssetSource); + cacheItem = cache.getItemCache(name, eTag); + const cachedOutput = await cacheItem.getPromise< + { source: sources.Source } | undefined + >(); + + if (cachedOutput) { + logger.debug(`${name} restored from cache`); + compilation.updateAsset(name, cachedOutput.source, (assetInfo) => ({ + ...assetInfo, + minimized: true, + })); + continue; + } + } + + const { source, map } = scriptAssetSource.sourceAndMap(); + scriptsToOptimize.push({ + name: scriptAsset.name, + code: typeof source === 'string' ? source : source.toString(), + map, + cacheItem, + }); + } + + if (scriptsToOptimize.length === 0) { + return; + } + + // Ensure all replacement values are strings which is the expected type for esbuild + let define: Record | undefined; + if (this.options.define) { + define = {}; + for (const [key, value] of Object.entries(this.options.define)) { + define[key] = String(value); + } + } + + // Setup the options used by all worker tasks + const optimizeOptions: OptimizeRequestOptions = { + sourcemap: this.options.sourcemap, + define, + keepNames: this.options.keepNames, + keepIdentifierNames: this.options.keepIdentifierNames, + target: this.targets, + removeLicenses: this.options.removeLicenses, + advanced: this.options.advanced, + // Perform a single native esbuild support check. + // This removes the need for each worker to perform the check which would + // otherwise require spawning a separate process per worker. + alwaysUseWasm: !(await EsbuildExecutor.hasNativeSupport()), + }; + + // Sort scripts so larger scripts start first - worker pool uses a FIFO queue + scriptsToOptimize.sort((a, b) => a.code.length - b.code.length); + + // Initialize the task worker pool + const workerPath = require.resolve('./javascript-optimizer-worker'); + const workerPool = new Piscina({ + filename: workerPath, + maxThreads: MAX_OPTIMIZE_WORKERS, + }); + + // Enqueue script optimization tasks and update compilation assets as the tasks complete + try { + const tasks = []; + for (const { name, code, map, cacheItem } of scriptsToOptimize) { + logger.time(`optimize asset: ${name}`); + + tasks.push( + workerPool + .run({ + asset: { + name, + code, + map, + }, + options: optimizeOptions, + }) + .then( + ({ code, name, map }) => { + const optimizedAsset = map + ? new SourceMapSource(code, name, map) + : new OriginalSource(code, name); + compilation.updateAsset(name, optimizedAsset, (assetInfo) => ({ + ...assetInfo, + minimized: true, + })); + + logger.timeEnd(`optimize asset: ${name}`); + + return cacheItem?.storePromise({ + source: optimizedAsset, + }); + }, + (error) => { + addError( + compilation, + `Optimization error [${name}]: ${error.stack || error.message}`, + ); + }, + ), + ); + } + + await Promise.all(tasks); + } finally { + void workerPool.destroy(); + } + + logger.timeEnd('optimize js assets'); + }, + ); + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-worker.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-worker.ts new file mode 100644 index 000000000000..240519f33218 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-worker.ts @@ -0,0 +1,220 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import remapping from '@ampproject/remapping'; +import type { TransformResult } from 'esbuild'; +import { minify } from 'terser'; +import { EsbuildExecutor } from './esbuild-executor'; + +/** + * The options to use when optimizing. + */ +export interface OptimizeRequestOptions { + /** + * Controls advanced optimizations. + * Currently these are only terser related: + * * terser compress passes are set to 2 + * * terser pure_getters option is enabled + */ + advanced?: boolean; + /** + * Specifies the string tokens that should be replaced with a defined value. + */ + define?: Record; + /** + * Controls whether class, function, and variable names should be left intact + * throughout the output code. + */ + keepIdentifierNames: boolean; + + /** + * Controls whether to retain the original name of classes and functions. + */ + keepNames: boolean; + /** + * Controls whether license text is removed from the output code. + * Within the CLI, this option is linked to the license extraction functionality. + */ + removeLicenses?: boolean; + /** + * Controls whether source maps should be generated. + */ + sourcemap?: boolean; + /** + * Specifies the list of supported esbuild targets. + * @see: https://esbuild.github.io/api/#target + */ + target?: string[]; + /** + * Controls whether esbuild should only use the WASM-variant instead of trying to + * use the native variant. Some platforms may not support the native-variant and + * this option allows one support test to be conducted prior to all the workers starting. + */ + alwaysUseWasm: boolean; +} + +/** + * A request to optimize JavaScript using the supplied options. + */ +interface OptimizeRequest { + /** + * The options to use when optimizing. + */ + options: OptimizeRequestOptions; + + /** + * The JavaScript asset to optimize. + */ + asset: { + /** + * The name of the JavaScript asset (typically the filename). + */ + name: string; + /** + * The source content of the JavaScript asset. + */ + code: string; + /** + * The source map of the JavaScript asset, if available. + * This map is merged with all intermediate source maps during optimization. + */ + map: object; + }; +} + +/** + * The cached esbuild executor. + * This will automatically use the native or WASM version based on platform and availability + * with the native version given priority due to its superior performance. + */ +let esbuild: EsbuildExecutor | undefined; + +/** + * Handles optimization requests sent from the main thread via the `JavaScriptOptimizerPlugin`. + */ +export default async function ({ asset, options }: OptimizeRequest) { + // esbuild is used as a first pass + const esbuildResult = await optimizeWithEsbuild(asset.code, asset.name, options); + + // terser is used as a second pass + const terserResult = await optimizeWithTerser( + asset.name, + esbuildResult.code, + options.sourcemap, + options.advanced, + ); + + // Merge intermediate sourcemaps with input sourcemap if enabled + let fullSourcemap; + if (options.sourcemap) { + const partialSourcemaps = []; + + if (esbuildResult.map) { + partialSourcemaps.unshift(JSON.parse(esbuildResult.map)); + } + + if (terserResult.map) { + partialSourcemaps.unshift(terserResult.map); + } + + if (asset.map) { + partialSourcemaps.push(asset.map); + } + + fullSourcemap = remapping(partialSourcemaps, () => null); + } + + return { name: asset.name, code: terserResult.code, map: fullSourcemap }; +} + +/** + * Optimizes a JavaScript asset using esbuild. + * + * @param content The JavaScript asset source content to optimize. + * @param name The name of the JavaScript asset. Used to generate source maps. + * @param options The optimization request options to apply to the content. + * @returns A promise that resolves with the optimized code, source map, and any warnings. + */ +async function optimizeWithEsbuild( + content: string, + name: string, + options: OptimizeRequest['options'], +): Promise { + if (!esbuild) { + esbuild = new EsbuildExecutor(options.alwaysUseWasm); + } + + return esbuild.transform(content, { + minifyIdentifiers: !options.keepIdentifierNames, + minifySyntax: true, + // NOTE: Disabling whitespace ensures unused pure annotations are kept + minifyWhitespace: false, + pure: ['forwardRef'], + legalComments: options.removeLicenses ? 'none' : 'inline', + sourcefile: name, + sourcemap: options.sourcemap && 'external', + define: options.define, + // This option should always be disabled for browser builds as we don't rely on `.name` + // and causes deadcode to be retained which makes `NG_BUILD_MANGLE` unusable to investigate tree-shaking issues. + // We enable `keepNames` only for server builds as Domino relies on `.name`. + // Once we no longer rely on Domino for SSR we should be able to remove this. + keepNames: options.keepNames, + target: options.target, + }); +} + +/** + * Optimizes a JavaScript asset using terser. + * + * @param name The name of the JavaScript asset. Used to generate source maps. + * @param code The JavaScript asset source content to optimize. + * @param sourcemaps If true, generate an output source map for the optimized code. + * @param advanced Controls advanced optimizations. + * @returns A promise that resolves with the optimized code and source map. + */ +async function optimizeWithTerser( + name: string, + code: string, + sourcemaps: boolean | undefined, + advanced: boolean | undefined, +): Promise<{ code: string; map?: object }> { + const result = await minify( + { [name]: code }, + { + compress: { + passes: advanced ? 2 : 1, + pure_getters: advanced, + }, + // Set to ES2015 to prevent higher level features from being introduced when browserslist + // contains older browsers. The build system requires browsers to support ES2015 at a minimum. + ecma: 2015, + // esbuild in the first pass is used to minify identifiers instead of mangle here + mangle: false, + // esbuild in the first pass is used to minify function names + keep_fnames: true, + format: { + // ASCII output is enabled here as well to prevent terser from converting back to UTF-8 + ascii_only: true, + wrap_func_args: false, + }, + sourceMap: + sourcemaps && + ({ + asObject: true, + // typings don't include asObject option + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any), + }, + ); + + if (!result.code) { + throw new Error('Terser failed for unknown reason.'); + } + + return { code: result.code, map: result.map as object }; +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/json-stats-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/json-stats-plugin.ts new file mode 100644 index 000000000000..6de412de69a2 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/json-stats-plugin.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { createWriteStream, promises as fsPromises } from 'fs'; +import { dirname } from 'path'; +import { Compiler } from 'webpack'; +import { assertIsError } from '../../utils/error'; + +import { addError } from '../../utils/webpack-diagnostics'; + +export class JsonStatsPlugin { + constructor(private readonly statsOutputPath: string) {} + + apply(compiler: Compiler) { + compiler.hooks.done.tapPromise('angular-json-stats', async (stats) => { + const { stringifyStream } = await import('@discoveryjs/json-ext'); + const data = stats.toJson('verbose'); + + try { + await fsPromises.mkdir(dirname(this.statsOutputPath), { recursive: true }); + await new Promise((resolve, reject) => + stringifyStream(data) + .pipe(createWriteStream(this.statsOutputPath)) + .on('close', resolve) + .on('error', reject), + ); + } catch (error) { + assertIsError(error); + addError( + stats.compilation, + `Unable to write stats file: ${error.message || 'unknown error'}`, + ); + } + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-context.html b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-context.html index 1f91cd461a97..587278156358 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-context.html +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-context.html @@ -5,40 +5,40 @@ Reloaded before every execution run. --> + + + + + + + - - - - - - - - - - - + - - - - - %SCRIPTS% - - - - - + + + + %SCRIPTS% + + + + - - + + diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-debug.html b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-debug.html index 44dbff5898f4..fed09ab2a369 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-debug.html +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-debug.html @@ -1,46 +1,46 @@ - + + + %X_UA_COMPATIBLE% + Karma DEBUG RUNNER + + + + + + - -%X_UA_COMPATIBLE% - Karma DEBUG RUNNER - - - - - - - - - - - + + - - - - - %SCRIPTS% - - - - - + + + + %SCRIPTS% + + + + - - + + diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-webpack-failure-cb.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-webpack-failure-cb.ts deleted file mode 100644 index b4c02479bd6e..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-webpack-failure-cb.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable -// TODO: cleanup this file, it's copied as is from Angular CLI. - -// Force Webpack to throw compilation errors. Useful with karma-webpack when in single-run mode. -// Workaround for https://github.com/webpack-contrib/karma-webpack/issues/66 - -export class KarmaWebpackFailureCb { - constructor(private callback: (error: string | undefined, errors: string[]) => void) { } - - apply(compiler: any): void { - compiler.hooks.done.tap('KarmaWebpackFailureCb', (stats: any) => { - if (stats.compilation.errors.length > 0) { - this.callback(undefined, stats.compilation.errors.map((error: any) => error.message? error.message : error.toString())); - } - }); - } -} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma.ts index 1c77fab9f80c..a2e7fc4b8a56 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma.ts @@ -1,36 +1,25 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable + +/* eslint-disable */ // TODO: cleanup this file, it's copied as is from Angular CLI. import * as http from 'http'; import * as path from 'path'; -import * as glob from 'glob'; -import * as webpack from 'webpack'; -const webpackDevMiddleware = require('webpack-dev-middleware'); +import webpack from 'webpack'; +import webpackDevMiddleware from 'webpack-dev-middleware'; -import { KarmaWebpackFailureCb } from './karma-webpack-failure-cb'; import { statsErrorsToString } from '../../utils/stats'; -import { getWebpackStatsConfig } from '../../configs/stats'; import { createConsoleLogger } from '@angular-devkit/core/node'; import { logging } from '@angular-devkit/core'; -import { WebpackTestOptions } from '../../../utils/build-options'; +import { BuildOptions } from '../../../utils/build-options'; import { normalizeSourceMaps } from '../../../utils/index'; -/** - * Enumerate needed (but not require/imported) dependencies from this file - * to let the dependency validator know they are used. - * - * require('source-map-support') - * require('karma-source-map-support') - */ - - const KARMA_APPLICATION_PATH = '_karma_webpack_'; - +const KARMA_APPLICATION_PATH = '_karma_webpack_'; let blocked: any[] = []; let isBlocked = false; @@ -38,36 +27,14 @@ let webpackMiddleware: any; let successCb: () => void; let failureCb: () => void; -// Add files to the Karma files array. -function addKarmaFiles(files: any[], newFiles: any[], prepend = false) { - const defaults = { - included: true, - served: true, - watched: true - }; - - const processedFiles = newFiles - // Remove globs that do not match any files, otherwise Karma will show a warning for these. - .filter(file => glob.sync(file.pattern, { nodir: true }).length != 0) - // Fill in pattern properties with defaults. - .map(file => ({ ...defaults, ...file })); - - // It's important to not replace the array, because - // karma already has a reference to the existing array. - if (prepend) { - files.unshift(...processedFiles); - } else { - files.push(...processedFiles); - } -} - const init: any = (config: any, emitter: any) => { if (!config.buildWebpack) { - throw new Error(`The '@angular-devkit/build-angular/plugins/karma' karma plugin is meant to` + - ` be used from within Angular CLI and will not work correctly outside of it.` - ) + throw new Error( + `The '@angular-devkit/build-angular/plugins/karma' karma plugin is meant to` + + ` be used from within Angular CLI and will not work correctly outside of it.`, + ); } - const options = config.buildWebpack.options as WebpackTestOptions; + const options = config.buildWebpack.options as BuildOptions; const logger: logging.Logger = config.buildWebpack.logger || createConsoleLogger(); successCb = config.buildWebpack.successCb; failureCb = config.buildWebpack.failureCb; @@ -82,37 +49,25 @@ const init: any = (config: any, emitter: any) => { const smsPath = path.dirname(require.resolve('source-map-support')); const ksmsPath = path.dirname(require.resolve('karma-source-map-support')); - addKarmaFiles(config.files, [ - { pattern: path.join(smsPath, 'browser-source-map-support.js'), watched: false }, - { pattern: path.join(ksmsPath, 'client.js'), watched: false } - ], true); + config.files.unshift( + { + pattern: path.join(smsPath, 'browser-source-map-support.js'), + included: true, + served: true, + watched: false, + }, + { pattern: path.join(ksmsPath, 'client.js'), included: true, served: true, watched: false }, + ); } config.reporters.unshift('@angular-devkit/build-angular--event-reporter'); // When using code-coverage, auto-add karma-coverage. - if (options.codeCoverage) { - config.plugins = config.plugins || []; - config.reporters = config.reporters || []; - const {plugins, reporters} = config; - const hasCoveragePlugin = plugins.some(isPlugin('karma-coverage', 'reporter:coverage')); - const hasIstanbulPlugin = plugins.some(isPlugin('karma-coverage-istanbul-reporter', 'reporter:coverage-istanbul')); - const hasCoverageReporter = reporters.includes('coverage'); - const hasIstanbulReporter = reporters.includes('coverage-istanbul'); - if (hasCoveragePlugin && !hasCoverageReporter) { - reporters.push('coverage'); - } else if (hasIstanbulPlugin && !hasIstanbulReporter) { - // coverage-istanbul is deprecated in favor of karma-coverage - reporters.push('coverage-istanbul'); - } else if (!hasCoveragePlugin && !hasIstanbulPlugin) { - throw new Error('karma-coverage must be installed in order to run code coverage.'); - } - - if (hasIstanbulPlugin) { - logger.warn(`'karma-coverage-istanbul-reporter' usage has been deprecated since version 11.\n` + - `Please install 'karma-coverage' and update 'karma.conf.js.' ` + - 'For more info, see https://github.com/karma-runner/karma-coverage/blob/master/README.md'); - } + if ( + options.codeCoverage && + !config.reporters.some((r: string) => r === 'coverage' || r === 'coverage-istanbul') + ) { + config.reporters.push('coverage'); } // Add webpack config. @@ -123,21 +78,9 @@ const init: any = (config: any, emitter: any) => { publicPath: `/${KARMA_APPLICATION_PATH}/`, }; - const compilationErrorCb = (error: string | undefined, errors: string[]) => { - // Notify potential listeners of the compile error - emitter.emit('compile_error', errors); - - // Finish Karma run early in case of compilation error. - emitter.emit('run_complete', [], { exitCode: 1 }); - - // Unblock any karma requests (potentially started using `karma run`) - unblock(); - } - webpackConfig.plugins.push(new KarmaWebpackFailureCb(compilationErrorCb)); - // Use existing config if any. - config.webpack = Object.assign(webpackConfig, config.webpack); - config.webpackMiddleware = Object.assign(webpackMiddlewareConfig, config.webpackMiddleware); + config.webpack = { ...webpackConfig, ...config.webpack }; + config.webpackMiddleware = { ...webpackMiddlewareConfig, ...config.webpackMiddleware }; // Our custom context and debug files list the webpack bundles directly instead of using // the karma files array. @@ -150,15 +93,13 @@ const init: any = (config: any, emitter: any) => { config.middleware = config.middleware || []; config.middleware.push('@angular-devkit/build-angular--fallback'); - // The webpack tier owns the watch behavior so we want to force it in the config. - webpackConfig.watch = !config.singleRun; if (config.singleRun) { // There's no option to turn off file watching in webpack-dev-server, but // we can override the file watcher instead. webpackConfig.plugins.unshift({ - apply: (compiler: any) => { // tslint:disable-line:no-any + apply: (compiler: any) => { compiler.hooks.afterEnvironment.tap('karma', () => { - compiler.watchFileSystem = { watch: () => { } }; + compiler.watchFileSystem = { watch: () => {} }; }); }, }); @@ -167,59 +108,80 @@ const init: any = (config: any, emitter: any) => { webpackConfig.output.path = `/${KARMA_APPLICATION_PATH}/`; webpackConfig.output.publicPath = `/${KARMA_APPLICATION_PATH}/`; - let compiler; - try { - compiler = webpack(webpackConfig); - } catch (e) { - logger.error(e.stack || e) - if (e.details) { - logger.error(e.details) + const compiler = webpack(webpackConfig, (error, stats) => { + if (error) { + throw error; } - throw e; - } - function handler(callback?: () => void) { - isBlocked = true; + if (stats?.hasErrors()) { + // Only generate needed JSON stats and when needed. + const statsJson = stats?.toJson({ + all: false, + children: true, + errors: true, + warnings: true, + }); + + logger.error(statsErrorsToString(statsJson, { colors: true })); + + if (config.singleRun) { + // Notify potential listeners of the compile error. + emitter.emit('load_error'); + } - if (typeof callback === 'function') { - callback(); + // Finish Karma run early in case of compilation error. + emitter.emit('run_complete', [], { exitCode: 1 }); + + // Emit a failure build event if there are compilation errors. + failureCb(); } + }); + + function handler(callback?: () => void): void { + isBlocked = true; + callback?.(); } compiler.hooks.invalid.tap('karma', () => handler()); - compiler.hooks.watchRun.tapAsync('karma', (_: any, callback: () => void) => handler(callback)); - compiler.hooks.run.tapAsync('karma', (_: any, callback: () => void) => handler(callback)); - function unblock(){ + webpackMiddleware = webpackDevMiddleware(compiler, webpackMiddlewareConfig); + emitter.on('exit', (done: any) => { + webpackMiddleware.close(); + compiler.close(() => done()); + }); + + function unblock() { isBlocked = false; blocked.forEach((cb) => cb()); blocked = []; } let lastCompilationHash: string | undefined; - const statsConfig = getWebpackStatsConfig(); - compiler.hooks.done.tap('karma', (stats) => { - if (stats.hasErrors()) { - // Print compilation errors. - logger.error(statsErrorsToString(stats.compilation, statsConfig)); - lastCompilationHash = undefined; - // Emit a failure build event if there are compilation errors. - failureCb(); - } else if (stats.hash != lastCompilationHash) { - // Refresh karma only when there are no webpack errors, and if the compilation changed. - lastCompilationHash = stats.hash; - emitter.refreshFiles(); - } - unblock(); - }); + let isFirstRun = true; + + return new Promise((resolve) => { + compiler.hooks.done.tap('karma', (stats) => { + if (isFirstRun) { + // This is needed to block Karma from launching browsers before Webpack writes the assets in memory. + // See the below: + // https://github.com/karma-runner/karma-chrome-launcher/issues/154#issuecomment-986661937 + // https://github.com/angular/angular-cli/issues/22495 + isFirstRun = false; + resolve(); + } - webpackMiddleware = new webpackDevMiddleware(compiler, webpackMiddlewareConfig); + if (stats.hasErrors()) { + lastCompilationHash = undefined; + } else if (stats.hash != lastCompilationHash) { + // Refresh karma only when there are no webpack errors, and if the compilation changed. + lastCompilationHash = stats.hash; + emitter.refreshFiles(); + } - emitter.on('exit', (done: any) => { - webpackMiddleware.close(); - done(); + unblock(); + }); }); }; @@ -244,12 +206,12 @@ function requestBlocker() { // browser log, because it is an utility reporter, // unless it's alone in the "reporters" option and base reporter is used. function muteDuplicateReporterLogging(context: any, config: any) { - context.writeCommonMsg = function () { }; + context.writeCommonMsg = () => {}; const reporterName = '@angular/cli'; const hasTrailingReporters = config.reporters.slice(-1).pop() !== reporterName; if (hasTrailingReporters) { - context.writeCommonMsg = function () { }; + context.writeCommonMsg = () => {}; } } @@ -265,7 +227,7 @@ const eventReporter: any = function (this: any, baseReporterDecorator: any, conf } else { failureCb(); } - } + }; // avoid duplicate failure message this.specFailure = () => {}; @@ -306,9 +268,8 @@ function fallbackMiddleware() { const alwaysServe = [ `/${KARMA_APPLICATION_PATH}/runtime.js`, `/${KARMA_APPLICATION_PATH}/polyfills.js`, - `/${KARMA_APPLICATION_PATH}/polyfills-es5.js`, `/${KARMA_APPLICATION_PATH}/scripts.js`, - `/${KARMA_APPLICATION_PATH}/styles.js`, + `/${KARMA_APPLICATION_PATH}/styles.css`, `/${KARMA_APPLICATION_PATH}/vendor.js`, ]; if (request.url && alwaysServe.includes(request.url)) { @@ -324,36 +285,10 @@ function fallbackMiddleware() { }; } -/** - * Returns a function that returns true if the plugin identifier matches the - * `moduleId` or `pluginName`. A plugin identifier can be either a string or - * an object according to https://karma-runner.github.io/5.2/config/plugins.html - * @param moduleId name of the node module (e.g. karma-coverage) - * @param pluginName name of the karma plugin (e.g. reporter:coverage) - */ -function isPlugin(moduleId: string, pluginName: string) { - return (plugin: string|{}): boolean => { - if (typeof plugin === 'string') { - if (!plugin.includes('*')) { - return plugin === moduleId; - } - const regexp = new RegExp(`^${plugin.replace('*', '.*')}`); - if (regexp.test(moduleId)) { - try { - require.resolve(moduleId); - return true; - } catch {} - } - return false; - } - return pluginName in plugin; - } -} - module.exports = { 'framework:@angular-devkit/build-angular': ['factory', init], 'reporter:@angular-devkit/build-angular--sourcemap-reporter': ['type', sourceMapReporter], 'reporter:@angular-devkit/build-angular--event-reporter': ['type', eventReporter], 'middleware:@angular-devkit/build-angular--blocker': ['factory', requestBlocker], - 'middleware:@angular-devkit/build-angular--fallback': ['factory', fallbackMiddleware] + 'middleware:@angular-devkit/build-angular--fallback': ['factory', fallbackMiddleware], }; diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/named-chunks-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/named-chunks-plugin.ts new file mode 100644 index 000000000000..c2d2bd5e0a5f --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/named-chunks-plugin.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { AsyncDependenciesBlock, Chunk, Compiler, Template, dependencies } from 'webpack'; + +// `ImportDependency` is not part of Webpack's depenencies typings. +const ImportDependency: typeof dependencies.ModuleDependency = require('webpack/lib/dependencies/ImportDependency'); + +const PLUGIN_NAME = 'named-chunks-plugin'; + +/** + * Webpack will not populate the chunk `name` property unless `webpackChunkName` magic comment is used. + * This however will also effect the filename which is not desired when using `deterministic` chunkIds. + * This plugin will populate the chunk `name` which is mainly used so that users can set bundle budgets on lazy chunks. + */ +export class NamedChunksPlugin { + apply(compiler: Compiler) { + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { + compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk) => { + if (chunk.name) { + return; + } + + if ([...chunk.files.values()].every((f) => f.endsWith('.css'))) { + // If all chunk files are CSS files skip. + // This happens when using `import('./styles.css')` in a lazy loaded module. + return undefined; + } + + const name = this.generateName(chunk); + if (name) { + chunk.name = name; + } + }); + }); + } + + private generateName(chunk: Chunk): string | undefined { + for (const group of chunk.groupsIterable) { + const [block] = group.getBlocks(); + if (!(block instanceof AsyncDependenciesBlock)) { + continue; + } + + if (block.groupOptions.name) { + // Ignore groups which have been named already. + return undefined; + } + + for (const dependency of block.dependencies) { + if (dependency instanceof ImportDependency) { + return Template.toPath(dependency.request); + } + } + } + + return undefined; + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/occurrences-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/occurrences-plugin.ts new file mode 100644 index 000000000000..56ba37f8fe16 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/occurrences-plugin.ts @@ -0,0 +1,98 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { Compiler } from 'webpack'; + +const PLUGIN_NAME = 'angular-occurrences-plugin'; + +export interface OccurrencesPluginOptions { + aot?: boolean; + scriptsOptimization?: boolean; +} + +export class OccurrencesPlugin { + constructor(private options: OccurrencesPluginOptions) {} + + apply(compiler: Compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ANALYSE, + }, + async (compilationAssets) => { + for (const assetName of Object.keys(compilationAssets)) { + if (!assetName.endsWith('.js')) { + continue; + } + + const scriptAsset = compilation.getAsset(assetName); + if (!scriptAsset || scriptAsset.source.size() <= 0) { + continue; + } + + const src = scriptAsset.source.source().toString('utf-8'); + + let ngComponentCount = 0; + + if (!this.options.aot) { + // Count the number of `Component({` strings (case sensitive), which happens in __decorate(). + ngComponentCount += this.countOccurrences(src, 'Component({'); + } + + if (this.options.scriptsOptimization) { + // for ascii_only true + ngComponentCount += this.countOccurrences(src, '.\\u0275cmp', false); + } else { + // For Ivy we just count ɵcmp.src + ngComponentCount += this.countOccurrences(src, '.ɵcmp', true); + } + + compilation.updateAsset( + assetName, + (s) => s, + (assetInfo) => ({ + ...assetInfo, + ngComponentCount, + }), + ); + } + }, + ); + }); + } + + private countOccurrences(source: string, match: string, wordBreak = false): number { + let count = 0; + + // We condition here so branch prediction happens out of the loop, not in it. + if (wordBreak) { + const re = /\w/; + for (let pos = source.lastIndexOf(match); pos >= 0; pos = source.lastIndexOf(match, pos)) { + if (!(re.test(source[pos - 1] || '') || re.test(source[pos + match.length] || ''))) { + count++; // 1 match, AH! AH! AH! 2 matches, AH! AH! AH! + } + + pos -= match.length; + if (pos < 0) { + break; + } + } + } else { + for (let pos = source.lastIndexOf(match); pos >= 0; pos = source.lastIndexOf(match, pos)) { + count++; // 1 match, AH! AH! AH! 2 matches, AH! AH! AH! + pos -= match.length; + if (pos < 0) { + break; + } + } + } + + return count; + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/optimize-css-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/optimize-css-webpack-plugin.ts deleted file mode 100644 index 132766a0412f..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/optimize-css-webpack-plugin.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as cssNano from 'cssnano'; -import { ProcessOptions, Result } from 'postcss'; -import { Compilation, Compiler, sources } from 'webpack'; -import { addWarning } from '../../utils/webpack-diagnostics'; - -export interface OptimizeCssWebpackPluginOptions { - sourceMap: boolean; - test: (file: string) => boolean; -} - -const PLUGIN_NAME = 'optimize-css-webpack-plugin'; - -function hook( - compiler: Compiler, - action: (compilation: Compilation, assetsURI: string[]) => Promise, -) { - compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { - compilation.hooks.processAssets.tapPromise({ - name: PLUGIN_NAME, - stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, - }, - assets => action(compilation, Object.keys(assets))); - }); -} - -export class OptimizeCssWebpackPlugin { - private readonly _options: OptimizeCssWebpackPluginOptions; - - constructor(options: Partial) { - this._options = { - sourceMap: false, - test: file => file.endsWith('.css'), - ...options, - }; - } - - apply(compiler: Compiler): void { - hook(compiler, (compilation: Compilation, assetsURI: string[]) => { - const files = [...compilation.additionalChunkAssets, ...assetsURI]; - - const actions = files - .filter(file => this._options.test(file)) - .map(async file => { - const asset = compilation.assets[file]; - if (!asset) { - return; - } - - let content: string | Buffer; - // tslint:disable-next-line: no-any - let map: any; - if (this._options.sourceMap && asset.sourceAndMap) { - const sourceAndMap = asset.sourceAndMap({}); - content = sourceAndMap.source; - map = sourceAndMap.map; - } else { - content = asset.source(); - } - - if (typeof content !== 'string') { - content = content.toString(); - } - - if (content.length === 0) { - return; - } - - const cssNanoOptions: cssNano.CssNanoOptions = { - preset: ['default', { - // Disable SVG optimizations, as this can cause optimizations which are not compatible in all browsers. - svgo: false, - // Disable `calc` optimizations, due to several issues. #16910, #16875, #17890 - calc: false, - }], - }; - - const postCssOptions: ProcessOptions = { - from: file, - map: map && { annotation: false, prev: map }, - }; - - const output = await new Promise((resolve, reject) => { - // @types/cssnano are not up to date with version 5. - // tslint:disable-next-line: no-any - (cssNano as any)(cssNanoOptions).process(content, postCssOptions) - .then(resolve) - .catch((err: Error) => reject(new Error(`${file} ${err.message}`))); - }); - - for (const { text } of output.warnings()) { - addWarning(compilation, text); - } - - let newSource; - if (output.map) { - newSource = new sources.SourceMapSource( - output.css, - file, - // tslint:disable-next-line: no-any - output.map.toString() as any, - content, - map, - ); - } else { - newSource = new sources.RawSource(output.css); - } - - compilation.assets[file] = newSource; - }); - - return Promise.all(actions).then(() => {}); - }); - } -} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/postcss-cli-resources.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/postcss-cli-resources.ts index 9294fde4ea65..f130d6c10e75 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/postcss-cli-resources.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/postcss-cli-resources.ts @@ -1,19 +1,20 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { interpolateName } from 'loader-utils'; import * as path from 'path'; import { Declaration, Plugin } from 'postcss'; import * as url from 'url'; -import * as webpack from 'webpack'; +import { assertIsError } from '../../utils/error'; function wrapUrl(url: string): string { let wrappedUrl; - const hasSingleQuotes = url.indexOf('\'') >= 0; + const hasSingleQuotes = url.indexOf("'") >= 0; if (hasSingleQuotes) { wrappedUrl = `"${url}"`; @@ -32,8 +33,7 @@ export interface PostcssCliResourcesOptions { /** CSS is extracted to a `.css` or is embedded in a `.js` file. */ extracted?: boolean; filename: (resourcePath: string) => string; - // tslint:disable-next-line: no-any - loader: any; + loader: import('webpack').LoaderContext; emitFile: boolean; } @@ -51,7 +51,7 @@ async function resolve( export const postcss = true; -export default function(options?: PostcssCliResourcesOptions): Plugin { +export default function (options?: PostcssCliResourcesOptions): Plugin { if (!options) { throw new Error('No options were specified to "postcss-cli-resources".'); } @@ -78,7 +78,7 @@ export default function(options?: PostcssCliResourcesOptions): Plugin { // If starts with a caret, remove and return remainder // this supports bypassing asset processing if (inputUrl.startsWith('^')) { - return inputUrl.substr(1); + return inputUrl.slice(1); } const cacheKey = path.resolve(context, inputUrl); @@ -88,37 +88,36 @@ export default function(options?: PostcssCliResourcesOptions): Plugin { } if (inputUrl.startsWith('~')) { - inputUrl = inputUrl.substr(1); + inputUrl = inputUrl.slice(1); } const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, '/')); - const resolver = (file: string, base: string) => new Promise((resolve, reject) => { - loader.resolve(base, decodeURI(file), (err: Error, result: string) => { - if (err) { - reject(err); - - return; - } - resolve(result); + const resolver = (file: string, base: string) => + new Promise((resolve, reject) => { + loader.resolve(base, decodeURI(file), (err, result) => { + if (err) { + reject(err); + + return; + } + resolve(result as string); + }); }); - }); const result = await resolve(pathname as string, context, resolver); return new Promise((resolve, reject) => { - loader.fs.readFile(result, (err: Error, content: Buffer) => { + loader.fs.readFile(result, (err, content) => { if (err) { reject(err); return; } - let outputPath = interpolateName( - { resourcePath: result }, - filename(result), - { content, context: loader.context || loader.rootContext }, - ) - .replace(/\\|\//g, '-'); + let outputPath = interpolateName({ resourcePath: result }, filename(result), { + content, + context: loader.context || loader.rootContext, + }).replace(/\\|\//g, '-'); if (resourcesOutputPath) { outputPath = path.posix.join(resourcesOutputPath, outputPath); @@ -126,7 +125,8 @@ export default function(options?: PostcssCliResourcesOptions): Plugin { loader.addDependency(result); if (emitFile) { - loader.emitFile(outputPath, content, undefined); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + loader.emitFile(outputPath, content!, undefined, { sourceFilename: result }); } let outputUrl = outputPath.replace(/\\/g, '/'); @@ -155,7 +155,7 @@ export default function(options?: PostcssCliResourcesOptions): Plugin { } const value = decl.value; - const urlRegex = /url\(\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*\)/g; + const urlRegex = /url(https://github.com/?:\(\s*([%27%22]?))(.*?)(?:\1\s*\))/g; const segments: string[] = []; let match; @@ -164,16 +164,16 @@ export default function(options?: PostcssCliResourcesOptions): Plugin { // We want to load it relative to the file that imports const inputFile = decl.source && decl.source.input.file; - const context = inputFile && path.dirname(inputFile) || loader.context; + const context = (inputFile && path.dirname(inputFile)) || loader.context; - // tslint:disable-next-line:no-conditional-assignment - while (match = urlRegex.exec(value)) { - const originalUrl = match[1] || match[2] || match[3]; + while ((match = urlRegex.exec(value))) { + const originalUrl = match[2]; let processedUrl; try { processedUrl = await process(originalUrl, context, resourceCache); } catch (err) { - loader.emitError(decl.error(err.message, { word: originalUrl }).toString()); + assertIsError(err); + loader.emitError(decl.error(err.message, { word: originalUrl })); continue; } diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/progress-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/progress-plugin.ts new file mode 100644 index 000000000000..0c3c2f33d411 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/progress-plugin.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { ProgressPlugin as WebpackProgressPlugin } from 'webpack'; +import { Spinner } from '../../utils/spinner'; + +export class ProgressPlugin extends WebpackProgressPlugin { + constructor(platform: 'server' | 'browser') { + const platformCapitalFirst = platform.replace(/^\w/, (s) => s.toUpperCase()); + const spinner = new Spinner(); + spinner.start(`Generating ${platform} application bundles (phase: setup)...`); + + super({ + handler: (percentage: number, message: string) => { + const phase = message ? ` (phase: ${message})` : ''; + spinner.text = `Generating ${platform} application bundles${phase}...`; + + switch (percentage) { + case 1: + if (spinner.isSpinning) { + spinner.succeed(`${platformCapitalFirst} application bundle generation complete.`); + } + break; + case 0: + if (!spinner.isSpinning) { + spinner.start(); + } + break; + } + }, + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/remove-hash-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/remove-hash-plugin.ts index b1fcee81627d..74c6a1807a48 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/remove-hash-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/remove-hash-plugin.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Compiler } from 'webpack'; import { HashFormat } from '../utils/helpers'; @@ -14,20 +15,17 @@ export interface RemoveHashPluginOptions { } export class RemoveHashPlugin { - - constructor(private options: RemoveHashPluginOptions) { } + constructor(private options: RemoveHashPluginOptions) {} apply(compiler: Compiler): void { - compiler.hooks.compilation.tap('remove-hash-plugin', compilation => { + compiler.hooks.compilation.tap('remove-hash-plugin', (compilation) => { const assetPath = (path: string, data: { chunk?: { name: string } }) => { const chunkName = data.chunk?.name; const { chunkNames, hashFormat } = this.options; if (chunkName && chunkNames?.includes(chunkName)) { // Replace hash formats with empty strings. - return path - .replace(hashFormat.chunk, '') - .replace(hashFormat.extract, ''); + return path.replace(hashFormat.chunk, '').replace(hashFormat.extract, ''); } return path; diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts index 294fbb7c168d..7c477fcf8d19 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -9,8 +9,16 @@ import { interpolateName } from 'loader-utils'; import * as path from 'path'; import { Chunk, Compilation, Compiler, sources as webpackSources } from 'webpack'; +import { assertIsError } from '../../utils/error'; +import { addError } from '../../utils/webpack-diagnostics'; const Entrypoint = require('webpack/lib/Entrypoint'); + +/** + * The name of the plugin provided to Webpack when tapping Webpack compiler hooks. + */ +const PLUGIN_NAME = 'scripts-webpack-plugin'; + export interface ScriptsWebpackPluginOptions { name: string; sourceMap?: boolean; @@ -29,13 +37,13 @@ function addDependencies(compilation: Compilation, scripts: string[]): void { compilation.fileDependencies.add(script); } } + export class ScriptsWebpackPlugin { private _lastBuildTime?: number; private _cachedOutput?: ScriptOutput; - constructor(private options: ScriptsWebpackPluginOptions) { } + constructor(private options: ScriptsWebpackPluginOptions) {} - // tslint:disable-next-line: no-any async shouldSkip(compilation: Compilation, scripts: string[]): Promise { if (this._lastBuildTime == undefined) { this._lastBuildTime = Date.now(); @@ -52,7 +60,7 @@ export class ScriptsWebpackPlugin { return; } - resolve((entry && typeof entry !== 'string') ? entry.safeTime : undefined); + resolve(entry && typeof entry !== 'string' ? entry.safeTime : undefined); }); }); @@ -66,7 +74,11 @@ export class ScriptsWebpackPlugin { return true; } - private _insertOutput(compilation: Compilation, { filename, source }: ScriptOutput, cached = false) { + private _insertOutput( + compilation: Compilation, + { filename, source }: ScriptOutput, + cached = false, + ) { const chunk = new Chunk(this.options.name); chunk.rendered = !cached; chunk.id = this.options.name; @@ -79,35 +91,54 @@ export class ScriptsWebpackPlugin { compilation.entrypoints.set(this.options.name, entrypoint); compilation.chunks.add(chunk); - // tslint:disable-next-line: no-any - compilation.assets[filename] = source as any; + compilation.assets[filename] = source; compilation.hooks.chunkAsset.call(chunk, filename); } apply(compiler: Compiler): void { - if (!this.options.scripts || this.options.scripts.length === 0) { + if (this.options.scripts.length === 0) { return; } - const scripts = this.options.scripts - .filter(script => !!script) - .map(script => path.resolve(this.options.basePath || '', script)); + const resolver = compiler.resolverFactory.get('normal', { + preferRelative: true, + useSyncFileSystemCalls: true, + fileSystem: compiler.inputFileSystem, + }); - compiler.hooks.thisCompilation.tap('scripts-webpack-plugin', compilation => { - compilation.hooks.additionalAssets.tapPromise('scripts-webpack-plugin', async () => { - if (await this.shouldSkip(compilation, scripts)) { - if (this._cachedOutput) { - this._insertOutput(compilation, this._cachedOutput, true); - } + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { + const scripts: string[] = []; - addDependencies(compilation, scripts); + for (const script of this.options.scripts) { + try { + const resolvedPath = resolver.resolveSync({}, this.options.basePath, script); + if (resolvedPath) { + scripts.push(resolvedPath); + } else { + addError(compilation, `Cannot resolve '${script}'.`); + } + } catch (error) { + assertIsError(error); + addError(compilation, error.message); + } + } - return; + compilation.hooks.additionalAssets.tapPromise(PLUGIN_NAME, async () => { + if (await this.shouldSkip(compilation, scripts)) { + if (this._cachedOutput) { + this._insertOutput(compilation, this._cachedOutput, true); } - const sourceGetters = scripts.map(fullPath => { - return new Promise((resolve, reject) => { - compilation.inputFileSystem.readFile(fullPath, (err?: Error, data?: string | Buffer) => { + addDependencies(compilation, scripts); + + return; + } + + const sourceGetters = scripts.map((fullPath) => { + return new Promise((resolve, reject) => { + compilation.inputFileSystem.readFile( + fullPath, + (err?: Error | null, data?: string | Buffer) => { if (err) { reject(err); @@ -130,29 +161,45 @@ export class ScriptsWebpackPlugin { } resolve(source); - }); - }); + }, + ); }); + }); - const sources = await Promise.all(sourceGetters); - const concatSource = new webpackSources.ConcatSource(); - sources.forEach(source => { - concatSource.add(source); - concatSource.add('\n;'); - }); + const sources = await Promise.all(sourceGetters); + const concatSource = new webpackSources.ConcatSource(); + sources.forEach((source) => { + concatSource.add(source); + concatSource.add('\n;'); + }); - const combinedSource = new webpackSources.CachedSource(concatSource); - const filename = interpolateName( - { resourcePath: 'scripts.js' }, - this.options.filename as string, - { content: combinedSource.source() }, - ); + const combinedSource = new webpackSources.CachedSource(concatSource); - const output = { filename, source: combinedSource }; - this._insertOutput(compilation, output); - this._cachedOutput = output; - addDependencies(compilation, scripts); - }); + const output = { filename: this.options.filename, source: combinedSource }; + this._insertOutput(compilation, output); + this._cachedOutput = output; + addDependencies(compilation, scripts); + }); + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, + }, + async () => { + const assetName = this.options.filename; + const asset = compilation.getAsset(assetName); + if (asset) { + const interpolatedFilename = interpolateName( + { resourcePath: 'scripts.js' }, + assetName, + { content: asset.source.source() }, + ); + if (assetName !== interpolatedFilename) { + compilation.renameAsset(assetName, interpolatedFilename); + } + } + }, + ); }); } } diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/service-worker-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/service-worker-plugin.ts new file mode 100644 index 000000000000..a42a6fa89db9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/service-worker-plugin.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { Compiler } from 'webpack'; +import { augmentAppWithServiceWorker } from '../../utils/service-worker'; + +export interface ServiceWorkerPluginOptions { + projectRoot: string; + root: string; + baseHref?: string; + ngswConfigPath?: string; +} + +export class ServiceWorkerPlugin { + constructor(private readonly options: ServiceWorkerPluginOptions) {} + + apply(compiler: Compiler) { + compiler.hooks.done.tapPromise('angular-service-worker', async (stats) => { + if (stats.hasErrors()) { + // Don't generate a service worker if the compilation has errors. + // When there are errors some files will not be emitted which would cause other errors down the line such as readdir failures. + return; + } + + const { projectRoot, root, baseHref = '', ngswConfigPath } = this.options; + const { compilation } = stats; + // We use the output path from the compilation instead of build options since during + // localization the output path is modified to a temp directory. + // See: https://github.com/angular/angular-cli/blob/7e64b1537d54fadb650559214fbb12707324cd75/packages/angular_devkit/build_angular/src/utils/i18n-options.ts#L251-L252 + const outputPath = compilation.outputOptions.path; + + if (!outputPath) { + throw new Error('Compilation output path cannot be empty.'); + } + + try { + await augmentAppWithServiceWorker( + projectRoot, + root, + outputPath, + baseHref, + ngswConfigPath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (compiler.inputFileSystem as any).promises, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (compiler.outputFileSystem as any).promises, + ); + } catch (error) { + compilation.errors.push( + new compilation.compiler.webpack.WebpackError( + `Failed to generate service worker - ${error instanceof Error ? error.message : error}`, + ), + ); + } + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/single-test-transform.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/single-test-transform.ts deleted file mode 100644 index da69e3bba2d5..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/single-test-transform.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { logging, tags } from '@angular-devkit/core'; -import { getOptions } from 'loader-utils'; -import { extname } from 'path'; - -export interface SingleTestTransformLoaderOptions { - /* list of paths relative to the entry-point */ - files?: string[]; - logger?: logging.Logger; -} - -export const SingleTestTransformLoader = __filename; - -/** - * This loader transforms the default test file to only run tests - * for some specs instead of all specs. - * It works by replacing the known content of the auto-generated test file: - * const context = require.context('./', true, /\.spec\.ts$/); - * context.keys().map(context); - * with: - * const context = { keys: () => ({ map: (_a) => { } }) }; - * context.keys().map(context); - * So that it does nothing. - * Then it adds import statements for each file in the files options - * array to import them directly, and thus run the tests there. - */ -// tslint:disable-next-line: no-any -export default function loader(this: any, source: string): string { - const { files = [], logger = console } = getOptions(this) as SingleTestTransformLoaderOptions; - // signal the user that expected content is not present. - if (!source.includes('require.context(')) { - logger.error(tags.stripIndent - `The 'include' option requires that the 'main' file for tests includes the below line: - const context = require.context('./', true, /\.spec\.ts$/); - Arguments passed to require.context are not strict and can be changed.`); - - return source; - } - - const targettedImports = files - .map(path => `require('./${path.replace('.' + extname(path), '')}');`) - .join('\n'); - - const mockedRequireContext = 'Object.assign(() => { }, { keys: () => [], resolve: () => undefined });\n'; - source = source.replace(/require\.context\(.*/, mockedRequireContext + targettedImports); - - return source; -} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts new file mode 100644 index 000000000000..c364c2459820 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import assert from 'assert'; +import type { Compilation, Compiler } from 'webpack'; +import { assertIsError } from '../../utils/error'; +import { addError } from '../../utils/webpack-diagnostics'; + +export interface StylesWebpackPluginOptions { + preserveSymlinks?: boolean; + root: string; + entryPoints: Record; +} + +/** + * The name of the plugin provided to Webpack when tapping Webpack compiler hooks. + */ +const PLUGIN_NAME = 'styles-webpack-plugin'; + +export class StylesWebpackPlugin { + private compilation: Compilation | undefined; + + constructor(private readonly options: StylesWebpackPluginOptions) {} + + apply(compiler: Compiler): void { + const { entryPoints, preserveSymlinks, root } = this.options; + const resolver = compiler.resolverFactory.get('global-styles', { + conditionNames: ['sass', 'less', 'style'], + mainFields: ['sass', 'less', 'style', 'main', '...'], + extensions: ['.scss', '.sass', '.less', '.css'], + restrictions: [/\.((le|sa|sc|c)ss)$/i], + preferRelative: true, + useSyncFileSystemCalls: true, + symlinks: !preserveSymlinks, + fileSystem: compiler.inputFileSystem, + }); + + const webpackOptions = compiler.options; + compiler.hooks.environment.tap(PLUGIN_NAME, () => { + const entry = + typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry; + + webpackOptions.entry = async () => { + const entrypoints = await entry; + + for (const [bundleName, paths] of Object.entries(entryPoints)) { + entrypoints[bundleName] ??= {}; + const entryImport = (entrypoints[bundleName].import ??= []); + + for (const path of paths) { + try { + const resolvedPath = resolver.resolveSync({}, root, path); + if (resolvedPath) { + entryImport.push(`${resolvedPath}?ngGlobalStyle`); + } else { + assert(this.compilation, 'Compilation cannot be undefined.'); + addError(this.compilation, `Cannot resolve '${path}'.`); + } + } catch (error) { + assert(this.compilation, 'Compilation cannot be undefined.'); + assertIsError(error); + addError(this.compilation, error.message); + } + } + } + + return entrypoints; + }; + }); + + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { + this.compilation = compilation; + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/suppress-entry-chunks-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/suppress-entry-chunks-webpack-plugin.ts index 882506509ed6..64a8f3b2ce1e 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/suppress-entry-chunks-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/suppress-entry-chunks-webpack-plugin.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -21,7 +21,6 @@ export class SuppressExtractedTextChunksWebpackPlugin { // Only chunks with a css asset should have JavaScript assets removed let hasCssFile = false; - // chunk.files is an Array in Webpack 4 and a Set in Webpack 5 for (const file of chunk.files) { if (file.endsWith('.css')) { hasCssFile = true; @@ -35,53 +34,20 @@ export class SuppressExtractedTextChunksWebpackPlugin { // Only chunks with all CSS entry dependencies should have JavaScript assets removed let cssOnly = false; - // The any cast is used for default Webpack 4 type compatibility - // tslint:disable-next-line: no-any - const entryModules = (compilation as any).chunkGraph?.getChunkEntryModulesIterable(chunk); - if (entryModules) { - // Webpack 5 - for (const module of entryModules) { - cssOnly = module.dependencies.every( - (dependency: {}) => dependency.constructor.name === 'CssDependency', - ); + const entryModules = compilation.chunkGraph.getChunkEntryModulesIterable(chunk); + for (const module of entryModules) { + cssOnly = module.dependencies.every( + (dependency: {}) => dependency.constructor.name === 'CssDependency', + ); - if (!cssOnly) { - break; - } - } - } else { - // Webpack 4 - for (const module of chunk.modulesIterable as Iterable<{ dependencies: {}[] }>) { - cssOnly = module.dependencies.every((dependency) => { - const name = dependency.constructor.name; - - return ( - name === 'CssDependency' || - name === 'SingleEntryDependency' || - name === 'MultiEntryDependency' || - name === 'HarmonyCompatibilityDependency' || - name === 'HarmonyExportHeaderDependency' || - name === 'HarmonyInitDependency' - ); - }); - - if (!cssOnly) { - break; - } + if (!cssOnly) { + break; } } if (cssOnly) { - if (Array.isArray(chunk.files)) { - // Webpack 4 - (chunk.files as string[]) = chunk.files.filter((file) => file !== filename); - delete compilation.assets[filename]; - } else { - // Webpack 5 - // Casting is used for default Webpack 4 type compatibility - ((chunk.files as unknown) as Set).delete(filename); - ((compilation as unknown) as { deleteAsset(file: string): void }).deleteAsset(filename); - } + chunk.files.delete(filename); + compilation.deleteAsset(filename); } }); }); diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/transfer-size-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/transfer-size-plugin.ts new file mode 100644 index 000000000000..461a08967eec --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/transfer-size-plugin.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { promisify } from 'util'; +import { Compiler } from 'webpack'; +import { brotliCompress } from 'zlib'; +import { addWarning } from '../../utils/webpack-diagnostics'; + +const brotliCompressAsync = promisify(brotliCompress); + +const PLUGIN_NAME = 'angular-transfer-size-estimator'; + +export class TransferSizePlugin { + constructor() {} + + apply(compiler: Compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ANALYSE, + }, + async (compilationAssets) => { + const actions = []; + for (const assetName of Object.keys(compilationAssets)) { + if (!assetName.endsWith('.js') && !assetName.endsWith('.css')) { + continue; + } + + const scriptAsset = compilation.getAsset(assetName); + if (!scriptAsset || scriptAsset.source.size() <= 0) { + continue; + } + + actions.push( + brotliCompressAsync(scriptAsset.source.source()) + .then((result) => { + compilation.updateAsset( + assetName, + (s) => s, + (assetInfo) => ({ + ...assetInfo, + estimatedTransferSize: result.length, + }), + ); + }) + .catch((error) => { + addWarning( + compilation, + `Unable to calculate estimated transfer size for '${assetName}'. Reason: ${error.message}`, + ); + }), + ); + } + + await Promise.all(actions); + }, + ); + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/typescript.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/typescript.ts new file mode 100644 index 000000000000..0d9b3714cdd2 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/typescript.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { CompilerOptions } from '@angular/compiler-cli'; +import { AngularWebpackPlugin } from '@ngtools/webpack'; +import { ScriptTarget } from 'typescript'; +import { WebpackConfigOptions } from '../../utils/build-options'; + +export function createIvyPlugin( + wco: WebpackConfigOptions, + aot: boolean, + tsconfig: string, +): AngularWebpackPlugin { + const { buildOptions, tsConfig } = wco; + const optimize = buildOptions.optimization.scripts; + + const compilerOptions: CompilerOptions = { + sourceMap: buildOptions.sourceMap.scripts, + declaration: false, + declarationMap: false, + }; + + if (tsConfig.options.target === undefined || tsConfig.options.target < ScriptTarget.ES2022) { + tsConfig.options.target = ScriptTarget.ES2022; + // If 'useDefineForClassFields' is already defined in the users project leave the value as is. + // Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995 + // which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well. + tsConfig.options.useDefineForClassFields ??= false; + + wco.logger.warn( + 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' + + '"false" respectively by the Angular CLI. To control ECMA version and features use the Browerslist configuration. ' + + 'For more information, see https://angular.io/guide/build#configuring-browser-compatibility', + ); + } + + if (buildOptions.preserveSymlinks !== undefined) { + compilerOptions.preserveSymlinks = buildOptions.preserveSymlinks; + } + + const fileReplacements: Record = {}; + if (buildOptions.fileReplacements) { + for (const replacement of buildOptions.fileReplacements) { + fileReplacements[replacement.replace] = replacement.with; + } + } + + return new AngularWebpackPlugin({ + tsconfig, + compilerOptions, + fileReplacements, + jitMode: !aot, + emitNgModuleScope: !optimize, + inlineStyleFileExtension: buildOptions.inlineStyleLanguage ?? 'css', + }); +} diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/watch-files-logs-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/watch-files-logs-plugin.ts new file mode 100644 index 000000000000..a064c8680f58 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/watch-files-logs-plugin.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import type { Compiler } from 'webpack'; + +const PLUGIN_NAME = 'angular.watch-files-logs-plugin'; + +export class WatchFilesLogsPlugin { + apply(compiler: Compiler) { + compiler.hooks.watchRun.tap(PLUGIN_NAME, ({ modifiedFiles, removedFiles }) => { + compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { + const logger = compilation.getLogger(PLUGIN_NAME); + if (modifiedFiles?.size) { + logger.log(`Modified files:\n${[...modifiedFiles].join('\n')}\n`); + } + + if (removedFiles?.size) { + logger.log(`Removed files:\n${[...removedFiles].join('\n')}\n`); + } + }); + }); + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts index 4023b41d5177..101af93a7b91 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks.ts @@ -1,12 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import { StatsChunk, StatsCompilation } from 'webpack'; import { NormalizedEntryPoint } from './helpers'; -import { JsonChunkStats, JsonCompilationStats } from './stats'; /** * Webpack stats may incorrectly mark extra entry points `initial` chunks, when @@ -15,41 +16,39 @@ import { JsonChunkStats, JsonCompilationStats } from './stats'; * whereever necessary. Does not modify {@param webpackStats}. */ export function markAsyncChunksNonInitial( - webpackStats: JsonCompilationStats, + webpackStats: StatsCompilation, extraEntryPoints: NormalizedEntryPoint[], -): JsonChunkStats[] { - const {chunks = [], entrypoints: entryPoints = {}} = webpackStats; +): StatsChunk[] { + const { chunks = [], entrypoints: entryPoints = {} } = webpackStats; // Find all Webpack chunk IDs not injected into the main bundle. We don't have // to worry about transitive dependencies because extra entry points cannot be // depended upon in Webpack, thus any extra entry point with `inject: false`, // **cannot** be loaded in main bundle. - const asyncEntryPoints = extraEntryPoints.filter((entryPoint) => !entryPoint.inject); - const asyncChunkIds = flatMap(asyncEntryPoints, - (entryPoint) => entryPoints[entryPoint.bundleName].chunks); + const asyncChunkIds = extraEntryPoints + .filter((entryPoint) => !entryPoint.inject && entryPoints[entryPoint.bundleName]) + .flatMap((entryPoint) => + entryPoints[entryPoint.bundleName].chunks?.filter((n) => n !== 'runtime'), + ); // Find chunks for each ID. const asyncChunks = asyncChunkIds.map((chunkId) => { - const chunk = chunks.find((chunk) => chunk.id === chunkId); - if (!chunk) { - throw new Error(`Failed to find chunk (${chunkId}) in set:\n${ - JSON.stringify(chunks)}`); - } + const chunk = chunks.find((chunk) => chunk.id === chunkId); + if (!chunk) { + throw new Error(`Failed to find chunk (${chunkId}) in set:\n${JSON.stringify(chunks)}`); + } - return chunk; - }) - // All Webpack chunks are dependent on `runtime`, which is never an async - // entry point, simply ignore this one. - .filter((chunk) => chunk.names.indexOf('runtime') === -1); + return chunk; + }); // A chunk is considered `initial` only if Webpack already belives it to be initial // and the application developer did not mark it async via an extra entry point. - return chunks.map((chunk) => ({ - ...chunk, - initial: chunk.initial && !asyncChunks.find((asyncChunk) => asyncChunk === chunk), - })); -} - -function flatMap(list: T[], mapper: (item: T, index: number, array: T[]) => R[]): R[] { - return ([] as R[]).concat(...list.map(mapper)); + return chunks.map((chunk) => { + return asyncChunks.find((asyncChunk) => asyncChunk === chunk) + ? { + ...chunk, + initial: false, + } + : chunk; + }); } diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts b/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts deleted file mode 100644 index f6b9629380e1..000000000000 --- a/packages/angular_devkit/build_angular/src/webpack/utils/async-chunks_spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as webpack from 'webpack'; -import { markAsyncChunksNonInitial } from './async-chunks'; - -describe('async-chunks', () => { - describe('markAsyncChunksNonInitial()', () => { - it('sets `initial: false` for all extra entry points loaded asynchronously', () => { - const chunks = [ - { - id: 0, - names: ['first'], - initial: true, - files: [], - }, - { - id: 1, - names: ['second'], - initial: true, - files: [], - }, - { - id: 'third', // IDs can be strings too. - names: ['third'], - initial: true, - files: [], - }, - ]; - const entrypoints = { - first: { - chunks: [0], - }, - second: { - chunks: [1], - }, - third: { - chunks: ['third'], - }, - }; - const webpackStats = { chunks, entrypoints }; - - const extraEntryPoints = [ - { - bundleName: 'first', - inject: false, // Loaded asynchronously. - input: 'first.css', - }, - { - bundleName: 'second', - inject: true, - input: 'second.js', - }, - { - bundleName: 'third', - inject: false, // Loaded asynchronously. - input: 'third.js', - }, - ]; - - const newChunks = markAsyncChunksNonInitial(webpackStats, extraEntryPoints); - - expect(newChunks).toEqual([ - { - id: 0, - names: ['first'], - initial: false, // No longer initial because it was marked async. - files: [], - }, - { - id: 1, - names: ['second'], - initial: true, - files: [], - }, - { - id: 'third', - names: ['third'], - initial: false, // No longer initial because it was marked async. - files: [], - }, - ]); - }); - - it('ignores runtime dependency of async chunks', () => { - const chunks = [ - { - id: 0, - names: ['asyncStuff'], - initial: true, - files: [], - }, - { - id: 1, - names: ['runtime'], - initial: true, - files: [], - }, - ]; - const entrypoints = { - asyncStuff: { - chunks: [0, 1], // Includes runtime as a dependency. - }, - }; - const webpackStats = { chunks, entrypoints }; - - const extraEntryPoints = [ - { - bundleName: 'asyncStuff', - inject: false, // Loaded asynchronously. - input: 'asyncStuff.js', - }, - ]; - - const newChunks = markAsyncChunksNonInitial(webpackStats, extraEntryPoints); - - expect(newChunks).toEqual([ - { - id: 0, - names: ['asyncStuff'], - initial: false, // No longer initial because it was marked async. - files: [], - }, - { - id: 1, - names: ['runtime'], - initial: true, // Still initial, even though its a dependency. - files: [], - }, - ]); - }); - }); -}); diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts b/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts index 832a2bc15a49..445b00273af2 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts @@ -1,16 +1,24 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { basename, normalize } from '@angular-devkit/core'; +import type { ObjectPattern } from 'copy-webpack-plugin'; +import { createHash } from 'crypto'; +import glob from 'glob'; import * as path from 'path'; -import { ScriptTarget } from 'typescript'; -import { Configuration, SourceMapDevToolPlugin } from 'webpack'; -import { ExtraEntryPoint, ExtraEntryPointClass } from '../../browser/schema'; +import type { Configuration, WebpackOptionsNormalized } from 'webpack'; +import { + AssetPatternClass, + OutputHashing, + ScriptElement, + StyleElement, +} from '../../builders/browser/schema'; +import { WebpackConfigOptions } from '../../utils/build-options'; +import { VERSION } from '../../utils/package-version'; export interface HashFormat { chunk: string; @@ -19,34 +27,51 @@ export interface HashFormat { script: string; } -export function getOutputHashFormat(option: string, length = 20): HashFormat { - const hashFormats: { [option: string]: HashFormat } = { - none: { chunk: '', extract: '', file: '', script: '' }, - media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' }, - bundles: { - chunk: `.[chunkhash:${length}]`, - extract: `.[contenthash:${length}]`, - file: '', - script: `.[hash:${length}]`, - }, - all: { - chunk: `.[chunkhash:${length}]`, - extract: `.[contenthash:${length}]`, - file: `.[hash:${length}]`, - script: `.[hash:${length}]`, - }, - }; +export type WebpackStatsOptions = Exclude; - return hashFormats[option] || hashFormats['none']; +export function getOutputHashFormat(outputHashing = OutputHashing.None, length = 20): HashFormat { + const hashTemplate = `.[contenthash:${length}]`; + + switch (outputHashing) { + case 'media': + return { + chunk: '', + extract: '', + file: hashTemplate, + script: '', + }; + case 'bundles': + return { + chunk: hashTemplate, + extract: hashTemplate, + file: '', + script: hashTemplate, + }; + case 'all': + return { + chunk: hashTemplate, + extract: hashTemplate, + file: hashTemplate, + script: hashTemplate, + }; + case 'none': + default: + return { + chunk: '', + extract: '', + file: '', + script: '', + }; + } } -export type NormalizedEntryPoint = Required; +export type NormalizedEntryPoint = Required>; export function normalizeExtraEntryPoints( - extraEntryPoints: ExtraEntryPoint[], + extraEntryPoints: (ScriptElement | StyleElement)[], defaultBundleName: string, ): NormalizedEntryPoint[] { - return extraEntryPoints.map(entry => { + return extraEntryPoints.map((entry) => { if (typeof entry === 'string') { return { input: entry, inject: true, bundleName: defaultBundleName }; } @@ -57,9 +82,7 @@ export function normalizeExtraEntryPoints( bundleName = entry.bundleName; } else if (!inject) { // Lazy entry points use the file name as bundle name. - bundleName = basename( - normalize(entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '')), - ); + bundleName = path.parse(entry.input).name; } else { bundleName = defaultBundleName; } @@ -68,63 +91,6 @@ export function normalizeExtraEntryPoints( }); } -export function getSourceMapDevTool( - scriptsSourceMap: boolean | undefined, - stylesSourceMap: boolean | undefined, - hiddenSourceMap = false, - inlineSourceMap = false, -): SourceMapDevToolPlugin { - const include = []; - if (scriptsSourceMap) { - include.push(/js$/); - } - - if (stylesSourceMap) { - include.push(/css$/); - } - - return new SourceMapDevToolPlugin({ - filename: inlineSourceMap ? undefined : '[file].map', - include, - // We want to set sourceRoot to `webpack:///` for non - // inline sourcemaps as otherwise paths to sourcemaps will be broken in browser - // `webpack:///` is needed for Visual Studio breakpoints to work properly as currently - // there is no way to set the 'webRoot' - sourceRoot: 'webpack:///', - moduleFilenameTemplate: '[resource-path]', - append: hiddenSourceMap ? false : undefined, - }); -} - -/** - * Returns an ES version file suffix to differentiate between various builds. - */ -export function getEsVersionForFileName( - scriptTarget: ScriptTarget | undefined, - esVersionInFileName = false, -): string { - if (!esVersionInFileName || scriptTarget === undefined) { - return ''; - } - - if (scriptTarget === ScriptTarget.ESNext) { - return '-esnext'; - } - - return '-' + ScriptTarget[scriptTarget].toLowerCase(); -} - -export function isPolyfillsEntry(name: string): boolean { - return name === 'polyfills' || name === 'polyfills-es5'; -} - -export function getWatchOptions(poll: number | undefined): Configuration['watchOptions'] { - return { - poll, - ignored: poll === undefined ? undefined : 'node_modules/**', - }; -} - export function assetNameTemplateFactory(hashFormat: HashFormat): (resourcePath: string) => string { const visitedFiles = new Map(); @@ -151,3 +117,205 @@ export function assetNameTemplateFactory(hashFormat: HashFormat): (resourcePath: return '[path][name].[ext]'; }; } + +export function getInstrumentationExcludedPaths( + root: string, + excludedPaths: string[], +): Set { + const excluded = new Set(); + + for (const excludeGlob of excludedPaths) { + glob + .sync(excludeGlob, { nodir: true, cwd: root, root, nomount: true }) + .forEach((p) => excluded.add(path.join(root, p))); + } + + return excluded; +} + +export function normalizeGlobalStyles(styleEntrypoints: StyleElement[]): { + entryPoints: Record; + noInjectNames: string[]; +} { + const entryPoints: Record = {}; + const noInjectNames: string[] = []; + + if (styleEntrypoints.length === 0) { + return { entryPoints, noInjectNames }; + } + + for (const style of normalizeExtraEntryPoints(styleEntrypoints, 'styles')) { + // Add style entry points. + entryPoints[style.bundleName] ??= []; + entryPoints[style.bundleName].push(style.input); + + // Add non injected styles to the list. + if (!style.inject) { + noInjectNames.push(style.bundleName); + } + } + + return { entryPoints, noInjectNames }; +} + +export function getCacheSettings( + wco: WebpackConfigOptions, + angularVersion: string, +): WebpackOptionsNormalized['cache'] { + const { enabled, path: cacheDirectory } = wco.buildOptions.cache; + if (enabled) { + return { + type: 'filesystem', + profile: wco.buildOptions.verbose, + cacheDirectory: path.join(cacheDirectory, 'angular-webpack'), + maxMemoryGenerations: 1, + // We use the versions and build options as the cache name. The Webpack configurations are too + // dynamic and shared among different build types: test, build and serve. + // None of which are "named". + name: createHash('sha1') + .update(angularVersion) + .update(VERSION) + .update(wco.projectRoot) + .update(JSON.stringify(wco.tsConfig)) + .update( + JSON.stringify({ + ...wco.buildOptions, + // Needed because outputPath changes on every build when using i18n extraction + // https://github.com/angular/angular-cli/blob/736a5f89deaca85f487b78aec9ff66d4118ceb6a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts#L264-L265 + outputPath: undefined, + }), + ) + .digest('hex'), + }; + } + + if (wco.buildOptions.watch) { + return { + type: 'memory', + maxGenerations: 1, + }; + } + + return false; +} + +export function globalScriptsByBundleName( + scripts: ScriptElement[], +): { bundleName: string; inject: boolean; paths: string[] }[] { + return normalizeExtraEntryPoints(scripts, 'scripts').reduce( + (prev: { bundleName: string; paths: string[]; inject: boolean }[], curr) => { + const { bundleName, inject, input } = curr; + + const existingEntry = prev.find((el) => el.bundleName === bundleName); + if (existingEntry) { + if (existingEntry.inject && !inject) { + // All entries have to be lazy for the bundle to be lazy. + throw new Error(`The ${bundleName} bundle is mixing injected and non-injected scripts.`); + } + + existingEntry.paths.push(input); + } else { + prev.push({ + bundleName, + inject, + paths: [input], + }); + } + + return prev; + }, + [], + ); +} + +export function assetPatterns(root: string, assets: AssetPatternClass[]) { + return assets.map((asset: AssetPatternClass, index: number): ObjectPattern => { + // Resolve input paths relative to workspace root and add slash at the end. + // eslint-disable-next-line prefer-const + let { input, output, ignore = [], glob } = asset; + input = path.resolve(root, input).replace(/\\/g, '/'); + input = input.endsWith('/') ? input : input + '/'; + output = output.endsWith('/') ? output : output + '/'; + + if (output.startsWith('..')) { + throw new Error('An asset cannot be written to a location outside of the output path.'); + } + + return { + context: input, + // Now we remove starting slash to make Webpack place it from the output root. + to: output.replace(/^\//, ''), + from: glob, + noErrorOnMissing: true, + force: true, + globOptions: { + dot: true, + followSymbolicLinks: !!asset.followSymlinks, + ignore: [ + '.gitkeep', + '**/.DS_Store', + '**/Thumbs.db', + // Negate patterns needs to be absolute because copy-webpack-plugin uses absolute globs which + // causes negate patterns not to match. + // See: https://github.com/webpack-contrib/copy-webpack-plugin/issues/498#issuecomment-639327909 + ...ignore, + ].map((i) => path.posix.join(input, i)), + }, + priority: index, + }; + }); +} + +export function getStatsOptions(verbose = false): WebpackStatsOptions { + const webpackOutputOptions: WebpackStatsOptions = { + all: false, // Fallback value for stats options when an option is not defined. It has precedence over local webpack defaults. + colors: true, + hash: true, // required by custom stat output + timings: true, // required by custom stat output + chunks: true, // required by custom stat output + builtAt: true, // required by custom stat output + warnings: true, + errors: true, + assets: true, // required by custom stat output + cachedAssets: true, // required for bundle size calculators + + // Needed for markAsyncChunksNonInitial. + ids: true, + entrypoints: true, + }; + + const verboseWebpackOutputOptions: WebpackStatsOptions = { + // The verbose output will most likely be piped to a file, so colors just mess it up. + colors: false, + usedExports: true, + optimizationBailout: true, + reasons: true, + children: true, + assets: true, + version: true, + chunkModules: true, + errorDetails: true, + errorStack: true, + moduleTrace: true, + logging: 'verbose', + modulesSpace: Infinity, + }; + + return verbose + ? { ...webpackOutputOptions, ...verboseWebpackOutputOptions } + : webpackOutputOptions; +} + +/** + * @param root the workspace root + * @returns `true` when `@angular/platform-server` is installed. + */ +export function isPlatformServerInstalled(root: string): boolean { + try { + require.resolve('@angular/platform-server', { paths: [root] }); + + return true; + } catch { + return false; + } +} diff --git a/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts b/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts index 79cb2c1073ba..3e3a64407316 100644 --- a/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts +++ b/packages/angular_devkit/build_angular/src/webpack/utils/stats.ts @@ -1,43 +1,23 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable -// TODO: cleanup this file, it's copied as is from Angular CLI. -import { logging, tags } from '@angular-devkit/core'; + import { WebpackLoggingCallback } from '@angular-devkit/build-webpack'; +import { logging, tags } from '@angular-devkit/core'; +import assert from 'assert'; import * as path from 'path'; -import * as textTable from 'text-table'; +import textTable from 'text-table'; +import { Configuration, StatsCompilation } from 'webpack'; +import { Schema as BrowserBuilderOptions } from '../../builders/browser/schema'; +import { normalizeOptimization } from '../../utils'; +import { BudgetCalculatorResult } from '../../utils/bundle-calculator'; import { colors as ansiColors, removeColor } from '../../utils/color'; -import { Configuration } from 'webpack'; - -export interface JsonAssetStats { - name: string; - size: number; -} - -export interface JsonChunkStats { - id: number | string; - initial?: boolean; - files: string[]; - names: string[]; -} - -export interface JsonEntrypointStats { - chunks: (number | string)[]; -} - -export interface JsonCompilationStats { - assets?: JsonAssetStats[]; - chunks?: JsonChunkStats[]; - entrypoints?: Record; - outputPath?: string; - warnings?: ({ message: string } | string)[]; - errors?: ({ message: string } | string)[]; -} +import { markAsyncChunksNonInitial } from './async-chunks'; +import { WebpackStatsOptions, getStatsOptions, normalizeExtraEntryPoints } from './helpers'; export function formatSize(size: number): string { if (size <= 0) { @@ -49,85 +29,131 @@ export function formatSize(size: number): string { const roundedSize = size / Math.pow(1024, index); // bytes don't have a fraction const fractionDigits = index === 0 ? 0 : 2; + return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`; } -export type BundleStatsData = [files: string, names: string, size: number | string]; - -export type ChunkType = 'modern' | 'legacy' | 'unknown'; - +export type BundleStatsData = [ + files: string, + names: string, + rawSize: number | string, + estimatedTransferSize: number | string, +]; export interface BundleStats { initial: boolean; stats: BundleStatsData; - chunkType: ChunkType; -}; - -export function generateBundleStats( - info: { - size?: number; - files: string[]; - names?: string[]; - entry?: boolean; - initial?: boolean; - rendered?: boolean; - chunkType?: ChunkType, - }, -): BundleStats { - const size = typeof info.size === 'number' ? info.size : '-'; - const files = info.files.filter(f => !f.endsWith('.map')).map(f => path.basename(f)).join(', '); +} + +function getBuildDuration(webpackStats: StatsCompilation): number { + assert(webpackStats.builtAt, 'buildAt cannot be undefined'); + assert(webpackStats.time, 'time cannot be undefined'); + + return Date.now() - webpackStats.builtAt + webpackStats.time; +} + +function generateBundleStats(info: { + rawSize?: number; + estimatedTransferSize?: number; + files?: string[]; + names?: string[]; + initial?: boolean; + rendered?: boolean; +}): BundleStats { + const rawSize = typeof info.rawSize === 'number' ? info.rawSize : '-'; + const estimatedTransferSize = + typeof info.estimatedTransferSize === 'number' ? info.estimatedTransferSize : '-'; + const files = + info.files + ?.filter((f) => !f.endsWith('.map')) + .map((f) => path.basename(f)) + .join(', ') ?? ''; const names = info.names?.length ? info.names.join(', ') : '-'; - const initial = !!(info.entry || info.initial); - const chunkType = info.chunkType || 'unknown'; + const initial = !!info.initial; return { - chunkType, initial, - stats: [files, names, size], - } + stats: [files, names, rawSize, estimatedTransferSize], + }; } -function generateBuildStatsTable(data: BundleStats[], colors: boolean, showTotalSize: boolean): string { - const g = (x: string) => colors ? ansiColors.greenBright(x) : x; - const c = (x: string) => colors ? ansiColors.cyanBright(x) : x; - const bold = (x: string) => colors ? ansiColors.bold(x) : x; - const dim = (x: string) => colors ? ansiColors.dim(x) : x; +function generateBuildStatsTable( + data: BundleStats[], + colors: boolean, + showTotalSize: boolean, + showEstimatedTransferSize: boolean, + budgetFailures?: BudgetCalculatorResult[], +): string { + const g = (x: string) => (colors ? ansiColors.greenBright(x) : x); + const c = (x: string) => (colors ? ansiColors.cyanBright(x) : x); + const r = (x: string) => (colors ? ansiColors.redBright(x) : x); + const y = (x: string) => (colors ? ansiColors.yellowBright(x) : x); + const bold = (x: string) => (colors ? ansiColors.bold(x) : x); + const dim = (x: string) => (colors ? ansiColors.dim(x) : x); + + const getSizeColor = (name: string, file?: string, defaultColor = c) => { + const severity = budgets.get(name) || (file && budgets.get(file)); + switch (severity) { + case 'warning': + return y; + case 'error': + return r; + default: + return defaultColor; + } + }; const changedEntryChunksStats: BundleStatsData[] = []; const changedLazyChunksStats: BundleStatsData[] = []; - let initialModernTotalSize = 0; - let initialLegacyTotalSize = 0; - let modernFileSuffix: string | undefined; + let initialTotalRawSize = 0; + let initialTotalEstimatedTransferSize; - for (const { initial, stats, chunkType } of data) { - const [files, names, size] = stats; + const budgets = new Map(); + if (budgetFailures) { + for (const { label, severity } of budgetFailures) { + // In some cases a file can have multiple budget failures. + // Favor error. + if (label && (!budgets.has(label) || budgets.get(label) === 'warning')) { + budgets.set(label, severity); + } + } + } - const data: BundleStatsData = [ - g(files), - names, - c(typeof size === 'number' ? formatSize(size) : size), - ]; + for (const { initial, stats } of data) { + const [files, names, rawSize, estimatedTransferSize] = stats; + const getRawSizeColor = getSizeColor(names, files); + let data: BundleStatsData; + + if (showEstimatedTransferSize) { + data = [ + g(files), + names, + getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), + c( + typeof estimatedTransferSize === 'number' + ? formatSize(estimatedTransferSize) + : estimatedTransferSize, + ), + ]; + } else { + data = [ + g(files), + names, + getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), + '', + ]; + } if (initial) { changedEntryChunksStats.push(data); - - if (typeof size === 'number') { - switch (chunkType) { - case 'modern': - initialModernTotalSize += size; - if (!modernFileSuffix) { - const match = files.match(/-(es20\d{2}|esnext)/); - modernFileSuffix = match?.[1].toString().toUpperCase(); - } - break; - case 'legacy': - initialLegacyTotalSize += size; - break; - default: - initialModernTotalSize += size; - initialLegacyTotalSize += size; - break; + if (typeof rawSize === 'number') { + initialTotalRawSize += rawSize; + } + if (showEstimatedTransferSize && typeof estimatedTransferSize === 'number') { + if (initialTotalEstimatedTransferSize === undefined) { + initialTotalEstimatedTransferSize = 0; } + initialTotalEstimatedTransferSize += estimatedTransferSize; } } else { changedLazyChunksStats.push(data); @@ -135,24 +161,35 @@ function generateBuildStatsTable(data: BundleStats[], colors: boolean, showTotal } const bundleInfo: (string | number)[][] = []; + const baseTitles = ['Names', 'Raw Size']; + const tableAlign: ('l' | 'r')[] = ['l', 'l', 'r']; + + if (showEstimatedTransferSize) { + baseTitles.push('Estimated Transfer Size'); + tableAlign.push('r'); + } // Entry chunks if (changedEntryChunksStats.length) { - bundleInfo.push( - ['Initial Chunk Files', 'Names', 'Size'].map(bold), - ...changedEntryChunksStats, - ); + bundleInfo.push(['Initial Chunk Files', ...baseTitles].map(bold), ...changedEntryChunksStats); if (showTotalSize) { bundleInfo.push([]); - if (initialModernTotalSize === initialLegacyTotalSize) { - bundleInfo.push([' ', 'Initial Total', formatSize(initialModernTotalSize)].map(bold)); - } else { - bundleInfo.push( - [' ', 'Initial ES5 Total', formatSize(initialLegacyTotalSize)].map(bold), - [' ', `Initial ${modernFileSuffix} Total`, formatSize(initialModernTotalSize)].map(bold), + + const initialSizeTotalColor = getSizeColor('bundle initial', undefined, (x) => x); + const totalSizeElements = [ + ' ', + 'Initial Total', + initialSizeTotalColor(formatSize(initialTotalRawSize)), + ]; + if (showEstimatedTransferSize) { + totalSizeElements.push( + typeof initialTotalEstimatedTransferSize === 'number' + ? formatSize(initialTotalEstimatedTransferSize) + : '-', ); } + bundleInfo.push(totalSizeElements.map(bold)); } } @@ -163,42 +200,79 @@ function generateBuildStatsTable(data: BundleStats[], colors: boolean, showTotal // Lazy chunks if (changedLazyChunksStats.length) { - bundleInfo.push( - ['Lazy Chunk Files', 'Names', 'Size'].map(bold), - ...changedLazyChunksStats, - ); + bundleInfo.push(['Lazy Chunk Files', ...baseTitles].map(bold), ...changedLazyChunksStats); } return textTable(bundleInfo, { hsep: dim(' | '), - stringLength: s => removeColor(s).length, - align: ['l', 'l', 'r'], + stringLength: (s) => removeColor(s).length, + align: tableAlign, }); } function generateBuildStats(hash: string, time: number, colors: boolean): string { - const w = (x: string) => colors ? ansiColors.bold.white(x) : x; + const w = (x: string) => (colors ? ansiColors.bold.white(x) : x); + return `Build at: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`; } -function statsToString(json: any, statsConfig: any, bundleState?: BundleStats[]): string { +// We use this cache because we can have multiple builders running in the same process, +// where each builder has different output path. + +// Ideally, we should create the logging callback as a factory, but that would need a refactoring. +const runsCache = new Set(); + +function statsToString( + json: StatsCompilation, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + statsConfig: any, + budgetFailures?: BudgetCalculatorResult[], +): string { + if (!json.chunks?.length) { + return ''; + } + const colors = statsConfig.colors; - const rs = (x: string) => colors ? ansiColors.reset(x) : x; + const rs = (x: string) => (colors ? ansiColors.reset(x) : x); - const changedChunksStats: BundleStats[] = bundleState ?? []; + const changedChunksStats: BundleStats[] = []; let unchangedChunkNumber = 0; - if (!bundleState?.length) { - for (const chunk of json.chunks) { - if (!chunk.rendered) { - continue; - } + let hasEstimatedTransferSizes = false; + + const isFirstRun = !runsCache.has(json.outputPath || ''); + + for (const chunk of json.chunks) { + // During first build we want to display unchanged chunks + // but unchanged cached chunks are always marked as not rendered. + if (!isFirstRun && !chunk.rendered) { + continue; + } + + const assets = json.assets?.filter((asset) => chunk.files?.includes(asset.name)); + let rawSize = 0; + let estimatedTransferSize; + if (assets) { + for (const asset of assets) { + if (asset.name.endsWith('.map')) { + continue; + } - const assets = json.assets.filter((asset: any) => chunk.files.includes(asset.name)); - const summedSize = assets.filter((asset: any) => !asset.name.endsWith(".map")).reduce((total: number, asset: any) => { return total + asset.size }, 0); - changedChunksStats.push(generateBundleStats({ ...chunk, size: summedSize })); + rawSize += asset.size; + + if (typeof asset.info.estimatedTransferSize === 'number') { + if (estimatedTransferSize === undefined) { + estimatedTransferSize = 0; + hasEstimatedTransferSizes = true; + } + estimatedTransferSize += asset.info.estimatedTransferSize; + } + } } - unchangedChunkNumber = json.chunks.length - changedChunksStats.length; + changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize })); } + unchangedChunkNumber = json.chunks.length - changedChunksStats.length; + + runsCache.add(json.outputPath || ''); // Sort chunks by size in descending order changedChunksStats.sort((a, b) => { @@ -213,64 +287,73 @@ function statsToString(json: any, statsConfig: any, bundleState?: BundleStats[]) return 0; }); - const statsTable = generateBuildStatsTable(changedChunksStats, colors, unchangedChunkNumber === 0); + const statsTable = generateBuildStatsTable( + changedChunksStats, + colors, + unchangedChunkNumber === 0, + hasEstimatedTransferSizes, + budgetFailures, + ); // In some cases we do things outside of webpack context // Such us index generation, service worker augmentation etc... // This will correct the time and include these. - const time = (Date.now() - json.builtAt) + json.time; + + const time = getBuildDuration(json); if (unchangedChunkNumber > 0) { - return '\n' + rs(tags.stripIndents` + return ( + '\n' + + rs(tags.stripIndents` ${statsTable} ${unchangedChunkNumber} unchanged chunks - ${generateBuildStats(json.hash, time, colors)} - `); + ${generateBuildStats(json.hash || '', time, colors)} + `) + ); } else { - return '\n' + rs(tags.stripIndents` + return ( + '\n' + + rs(tags.stripIndents` ${statsTable} - ${generateBuildStats(json.hash, time, colors)} - `); + ${generateBuildStats(json.hash || '', time, colors)} + `) + ); } } -export const IGNORE_WARNINGS = [ - // Webpack 5+ has no facility to disable this warning. - // System.import is used in @angular/core for deprecated string-form lazy routes - /System.import\(\) is deprecated and will be removed soon/i, - // https://github.com/webpack-contrib/source-map-loader/blob/b2de4249c7431dd8432da607e08f0f65e9d64219/src/index.js#L83 - /Failed to parse source map from/, -]; -interface WebpackDiagnostic { - message: string; - file?: string; - moduleName?: string; - loc?: string; -} - -export function statsWarningsToString(json: any, statsConfig: any): string { +export function statsWarningsToString( + json: StatsCompilation, + statsConfig: WebpackStatsOptions, +): string { const colors = statsConfig.colors; - const c = (x: string) => colors ? ansiColors.reset.cyan(x) : x; - const y = (x: string) => colors ? ansiColors.reset.yellow(x) : x; - const yb = (x: string) => colors ? ansiColors.reset.yellowBright(x) : x; + const c = (x: string) => (colors ? ansiColors.reset.cyan(x) : x); + const y = (x: string) => (colors ? ansiColors.reset.yellow(x) : x); + const yb = (x: string) => (colors ? ansiColors.reset.yellowBright(x) : x); - const warnings = [...json.warnings]; + const warnings = json.warnings ? [...json.warnings] : []; if (json.children) { - warnings.push(...json.children - .map((c: any) => c.warnings) - .reduce((a: string[], b: string[]) => [...a, ...b], []) - ); + warnings.push(...json.children.map((c) => c.warnings ?? []).reduce((a, b) => [...a, ...b], [])); } let output = ''; - for (const warning of warnings as (string | WebpackDiagnostic)[]) { + for (const warning of warnings) { if (typeof warning === 'string') { output += yb(`Warning: ${warning}\n\n`); } else { - const file = warning.file || warning.moduleName; + let file = warning.file || warning.moduleName; + // Clean up warning paths + // Ex: ./src/app/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js.... + // to ./src/app/styles.scss.webpack + if (file && !statsConfig.errorDetails) { + const webpackPathIndex = file.indexOf('.webpack['); + if (webpackPathIndex !== -1) { + file = file.substring(0, webpackPathIndex); + } + } + if (file) { output += c(file); if (warning.loc) { @@ -285,33 +368,39 @@ export function statsWarningsToString(json: any, statsConfig: any): string { } } - if (output) { - return '\n' + output; - } - - return ''; + return output ? '\n' + output : output; } -export function statsErrorsToString(json: any, statsConfig: any): string { +export function statsErrorsToString( + json: StatsCompilation, + statsConfig: WebpackStatsOptions, +): string { const colors = statsConfig.colors; - const c = (x: string) => colors ? ansiColors.reset.cyan(x) : x; - const yb = (x: string) => colors ? ansiColors.reset.yellowBright(x) : x; - const r = (x: string) => colors ? ansiColors.reset.redBright(x) : x; + const c = (x: string) => (colors ? ansiColors.reset.cyan(x) : x); + const yb = (x: string) => (colors ? ansiColors.reset.yellowBright(x) : x); + const r = (x: string) => (colors ? ansiColors.reset.redBright(x) : x); - const errors = [...json.errors]; + const errors = json.errors ? [...json.errors] : []; if (json.children) { - errors.push(...json.children - .map((c: any) => c.errors) - .reduce((a: string[], b: string[]) => [...a, ...b], []) - ); + errors.push(...json.children.map((c) => c?.errors || []).reduce((a, b) => [...a, ...b], [])); } let output = ''; - for (const error of errors as (string | WebpackDiagnostic)[]) { + for (const error of errors) { if (typeof error === 'string') { output += r(`Error: ${error}\n\n`); } else { - const file = error.file || error.moduleName; + let file = error.file || error.moduleName; + // Clean up error paths + // Ex: ./src/app/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js.... + // to ./src/app/styles.scss.webpack + if (file && !statsConfig.errorDetails) { + const webpackPathIndex = file.indexOf('.webpack['); + if (webpackPathIndex !== -1) { + file = file.substring(0, webpackPathIndex); + } + } + if (file) { output += c(file); if (error.loc) { @@ -319,63 +408,144 @@ export function statsErrorsToString(json: any, statsConfig: any): string { } output += ' - '; } - if (!/^error/i.test(error.message)) { + + // In most cases webpack will add stack traces to error messages. + // This below cleans up the error from stacks. + // See: https://github.com/webpack/webpack/issues/15980 + const message = statsConfig.errorStack + ? error.message + : /[\s\S]+?(?=\n+\s+at\s)/.exec(error.message)?.[0] ?? error.message; + + if (!/^error/i.test(message)) { output += r('Error: '); } - output += `${error.message}\n\n`; + output += `${message}\n\n`; } } - if (output) { - return '\n' + output; - } - - return ''; + return output ? '\n' + output : output; } -export function statsHasErrors(json: any): boolean { - return json.errors.length || !!json.children?.some((c: any) => c.errors.length); +export function statsHasErrors(json: StatsCompilation): boolean { + return !!(json.errors?.length || json.children?.some((c) => c.errors?.length)); } -export function statsHasWarnings(json: any): boolean { - return json.warnings.length || !!json.children?.some((c: any) => c.warnings.length); +export function statsHasWarnings(json: StatsCompilation): boolean { + return !!(json.warnings?.length || json.children?.some((c) => c.warnings?.length)); } export function createWebpackLoggingCallback( - verbose: boolean, + options: BrowserBuilderOptions, logger: logging.LoggerApi, ): WebpackLoggingCallback { + const { verbose = false, scripts = [], styles = [] } = options; + const extraEntryPoints = [ + ...normalizeExtraEntryPoints(styles, 'styles'), + ...normalizeExtraEntryPoints(scripts, 'scripts'), + ]; + return (stats, config) => { if (verbose) { logger.info(stats.toString(config.stats)); } - webpackStatsLogger( - logger, - stats.toJson({ - errors: true, - warnings: true, - builtAt: true, - assets: true, - chunks: true, - }) as JsonCompilationStats, - config, - ); + const rawStats = stats.toJson(getStatsOptions(false)); + const webpackStats = { + ...rawStats, + chunks: markAsyncChunksNonInitial(rawStats, extraEntryPoints), + }; + + webpackStatsLogger(logger, webpackStats, config); + }; +} + +export interface BuildEventStats { + aot: boolean; + optimization: boolean; + allChunksCount: number; + lazyChunksCount: number; + initialChunksCount: number; + changedChunksCount?: number; + durationInMs: number; + cssSizeInBytes: number; + jsSizeInBytes: number; + ngComponentCount: number; +} + +export function generateBuildEventStats( + webpackStats: StatsCompilation, + browserBuilderOptions: BrowserBuilderOptions, +): BuildEventStats { + const { chunks = [], assets = [] } = webpackStats; + + let jsSizeInBytes = 0; + let cssSizeInBytes = 0; + let initialChunksCount = 0; + let ngComponentCount = 0; + let changedChunksCount = 0; + + const allChunksCount = chunks.length; + const isFirstRun = !runsCache.has(webpackStats.outputPath || ''); + + const chunkFiles = new Set(); + for (const chunk of chunks) { + if (!isFirstRun && chunk.rendered) { + changedChunksCount++; + } + + if (chunk.initial) { + initialChunksCount++; + } + + for (const file of chunk.files ?? []) { + chunkFiles.add(file); + } + } + + for (const asset of assets) { + if (asset.name.endsWith('.map') || !chunkFiles.has(asset.name)) { + continue; + } + + if (asset.name.endsWith('.js')) { + jsSizeInBytes += asset.size; + ngComponentCount += asset.info.ngComponentCount ?? 0; + } else if (asset.name.endsWith('.css')) { + cssSizeInBytes += asset.size; + } + } + + return { + optimization: !!normalizeOptimization(browserBuilderOptions.optimization).scripts, + aot: browserBuilderOptions.aot !== false, + allChunksCount, + lazyChunksCount: allChunksCount - initialChunksCount, + initialChunksCount, + changedChunksCount, + durationInMs: getBuildDuration(webpackStats), + cssSizeInBytes, + jsSizeInBytes, + ngComponentCount, }; } export function webpackStatsLogger( logger: logging.LoggerApi, - json: JsonCompilationStats, + json: StatsCompilation, config: Configuration, - bundleStats?: BundleStats[], + budgetFailures?: BudgetCalculatorResult[], ): void { - logger.info(statsToString(json, config.stats, bundleStats)); + logger.info(statsToString(json, config.stats, budgetFailures)); + + if (typeof config.stats !== 'object') { + throw new Error('Invalid Webpack stats configuration.'); + } if (statsHasWarnings(json)) { logger.warn(statsWarningsToString(json, config.stats)); } + if (statsHasErrors(json)) { logger.error(statsErrorsToString(json, config.stats)); } -}; +} diff --git a/packages/angular_devkit/build_angular/test/build-browser-features/.browserslistrc b/packages/angular_devkit/build_angular/test/build-browser-features/.browserslistrc deleted file mode 100644 index 7fd7c3b8783f..000000000000 --- a/packages/angular_devkit/build_angular/test/build-browser-features/.browserslistrc +++ /dev/null @@ -1,4 +0,0 @@ -# We want to run tests large with ever green browser so that -# we never trigger differential loading as this will slow down the tests. - -last 2 Chrome versions \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/README.md b/packages/angular_devkit/build_angular/test/hello-world-app/README.md index fa1c5ca7d3f1..563afe4d4160 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/README.md +++ b/packages/angular_devkit/build_angular/test/hello-world-app/README.md @@ -24,4 +24,4 @@ Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protrac ## Further help -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/main/README.md). diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/angular.json b/packages/angular_devkit/build_angular/test/hello-world-app/angular.json index 060a1593f6a0..ffb889dc2573 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/angular.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/angular.json @@ -2,9 +2,12 @@ "$schema": "../../../../packages/angular_devkit/core/src/workspace/workspace-schema.json", "version": 1, "newProjectRoot": "./projects", - "cli": {}, + "cli": { + "cache": { + "enabled": false + } + }, "schematics": {}, - "targets": {}, "projects": { "app": { "root": "src", @@ -22,6 +25,13 @@ "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", "progress": false, + "sourceMap": false, + "aot": false, + "vendorChunk": true, + "buildOptimizer": false, + "optimization": false, + "extractLicenses": false, + "namedChunks": true, "assets": [ "src/favicon.ico", "src/assets" @@ -33,16 +43,9 @@ }, "configurations": { "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], "optimization": true, "outputHashing": "all", "sourceMap": false, - "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, @@ -67,7 +70,10 @@ "outputPath": "dist-server", "main": "src/main.server.ts", "tsConfig": "src/tsconfig.server.json", - "progress": false + "progress": false, + "sourceMap": true, + "optimization": false, + "extractLicenses": false } }, "app-shell": { @@ -105,8 +111,8 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "karma.conf.js", "browsers": "ChromeHeadlessCI", @@ -123,27 +129,6 @@ "src/assets" ] } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "lint-test": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "src/tsconfig.spec.json", - "exclude": [ - "**/node_modules/**" - ] - } } } }, @@ -158,15 +143,6 @@ "devServerTarget": "app:serve", "webdriverUpdate": false } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "e2e/tsconfig.e2e.json", - "exclude": [ - "**/node_modules/**" - ] - } } } } diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.e2e-spec.ts b/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.e2e-spec.ts index 390560bd37dc..6cfa9f72cb21 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.e2e-spec.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.e2e-spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { AppPage } from './app.po'; describe('hello-world-app App', () => { diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.po.ts b/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.po.ts index a38be4b23cde..dcbf96793adc 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.po.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/e2e/app.po.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { browser, by, element } from 'protractor'; export class AppPage { diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/karma.conf.js b/packages/angular_devkit/build_angular/test/hello-world-app/karma.conf.js index 1b6b10b70e7c..1fa1906a712b 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/karma.conf.js +++ b/packages/angular_devkit/build_angular/test/hello-world-app/karma.conf.js @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html @@ -32,7 +33,6 @@ module.exports = function(config) { dir: path.join(__dirname, './coverage'), subdir: '.', reporters: [ - {type: 'html'}, {type: 'lcov'}, ], }, diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/protractor.conf.js b/packages/angular_devkit/build_angular/test/hello-world-app/protractor.conf.js index 58a5d0170c76..9b57663994c9 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/protractor.conf.js +++ b/packages/angular_devkit/build_angular/test/hello-world-app/protractor.conf.js @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.spec.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.spec.ts index d3dde56aacc1..56f6ecbf22fa 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.spec.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.ts index 7ea162bf0946..aef2940f181f 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.component.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Component } from '@angular/core'; @Component({ diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.module.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.module.ts index c61881ebb3eb..09f4f6fa82bd 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.module.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.module.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.server.module.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.server.module.ts index bb56e2795c22..cddaa2d485ce 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.server.module.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/app/app.server.module.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/environments/environment.prod.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/environments/environment.prod.ts deleted file mode 100644 index 585b70e8c518..000000000000 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/environments/environment.prod.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export const environment = { - production: true, -}; diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/environments/environment.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/environments/environment.ts deleted file mode 100644 index 7a2b5a1e7ce5..000000000000 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/environments/environment.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// The file contents for the current environment will overwrite these during build. -// The build system defaults to the dev environment which uses `environment.ts`, but if you do -// `ng build --env=prod` then `environment.prod.ts` will be used instead. -// The list of which env maps to which file can be found in `.angular-cli.json`. - -export const environment = { - production: false, -}; diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/main.server.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/main.server.ts index 340dffcdf657..4448f40d8a49 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/main.server.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/main.server.ts @@ -1,18 +1,9 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import '@angular/localize/init'; -import { enableProdMode } from '@angular/core'; - -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} export { AppServerModule } from './app/app.server.module'; -export { renderModule, renderModuleFactory } from '@angular/platform-server'; diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/main.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/main.ts index cb8269cc1ad4..2a16fe2683e5 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/main.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/main.ts @@ -1,19 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { enableProdMode } from '@angular/core'; + import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} platformBrowserDynamic() .bootstrapModule(AppModule) diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/polyfills.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/polyfills.ts index 345f7b9d8005..f530c61c1167 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/polyfills.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/polyfills.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. @@ -35,9 +36,6 @@ */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -import '@angular/localize/init'; - /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/test.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/test.ts deleted file mode 100644 index 6355082bbf2a..000000000000 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting, -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: { - context(path: string, deep?: boolean, filter?: RegExp): { - keys(): string[]; - (id: string): T; - }; -}; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json index f761e8b2c4fc..a76843b689fb 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": [] + "types": ["@angular/localize"] }, "files": [ "main.ts", diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.server.json b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.server.json index 62dc009f4ba5..314d4221c240 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.server.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.server.json @@ -4,12 +4,9 @@ "outDir": "../dist-server", "target": "es2016", "baseUrl": "./", - "types": [] + "types": ["@angular/localize"] }, "files": [ "main.server.ts" - ], - "angularCompilerOptions": { - "entryModule": "app/app.server.module#AppServerModule" - } + ] } diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json index dba226c49414..a42459f71db5 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/tsconfig.spec.json @@ -3,11 +3,11 @@ "compilerOptions": { "outDir": "../out-tsc/spec", "types": [ - "jasmine" + "jasmine", + "@angular/localize" ] }, "files": [ - "test.ts", "polyfills.ts" ], "include": [ diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/src/typings.d.ts b/packages/angular_devkit/build_angular/test/hello-world-app/src/typings.d.ts index fe59b01a3b02..9ba8f77b9c67 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/src/typings.d.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-app/src/typings.d.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + /* SystemJS module definition */ declare var module: NodeModule; interface NodeModule { diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json b/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json index 956e76c7636f..26bec6bf178a 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json +++ b/packages/angular_devkit/build_angular/test/hello-world-app/tsconfig.json @@ -8,13 +8,14 @@ "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "target": "es2017", - "module": "es2020", + "target": "es2022", + "module": "es2022", + "useDefineForClassFields": false, "typeRoots": [ "node_modules/@types" ], "lib": [ - "es2017", + "es2022", "dom" ] }, diff --git a/packages/angular_devkit/build_angular/test/hello-world-app/tslint.json b/packages/angular_devkit/build_angular/test/hello-world-app/tslint.json deleted file mode 100644 index 1dcd993d00c2..000000000000 --- a/packages/angular_devkit/build_angular/test/hello-world-app/tslint.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "extends": "tslint:recommended", - "rulesDirectory": [ - "codelyzer" - ], - "rules": { - "align": { - "options": [ - "parameters", - "statements" - ] - }, - "array-type": false, - "arrow-return-shorthand": true, - "curly": true, - "deprecation": { - "severity": "warning" - }, - "eofline": true, - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "import-spacing": true, - "indent": { - "options": [ - "spaces" - ] - }, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "quotemark": [ - true, - "single" - ], - "semicolon": { - "options": [ - "always" - ] - }, - "space-before-function-paren": { - "options": { - "anonymous": "never", - "asyncArrow": "always", - "constructor": "never", - "method": "never", - "named": "never" - } - }, - "typedef-whitespace": { - "options": [ - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - }, - { - "call-signature": "onespace", - "index-signature": "onespace", - "parameter": "onespace", - "property-declaration": "onespace", - "variable-declaration": "onespace" - } - ] - }, - "whitespace": { - "options": [ - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type", - "check-typecast" - ] - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - } -} diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/.gitignore b/packages/angular_devkit/build_angular/test/hello-world-lib/.gitignore index 54bfd2001e64..e0406551d1ea 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/.gitignore +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/.gitignore @@ -25,6 +25,7 @@ !.vscode/extensions.json # misc +/.angular/cache /.sass-cache /connect.lock /coverage diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/angular.json b/packages/angular_devkit/build_angular/test/hello-world-lib/angular.json index 111714c6f08a..9925e3d08f14 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/angular.json +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/angular.json @@ -1,6 +1,11 @@ { "$schema": "../../../../packages/angular_devkit/core/src/workspace/workspace-schema.json", "version": 1, + "cli": { + "cache": { + "enabled": false + } + }, "projects": { "lib": { "root": "projects/lib", diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/karma.conf.js b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/karma.conf.js index 80b89d8439ac..a0e81b921f61 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/karma.conf.js +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/karma.conf.js @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/package.json b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/package.json index 5917d86fecca..290796b86da7 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/package.json +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/package.json @@ -2,7 +2,7 @@ "name": "lib", "version": "0.0.1", "peerDependencies": { - "@angular/common": "^11.0.0", - "@angular/core": "^11.0.0" + "@angular/common": "^14.0.0", + "@angular/core": "^14.0.0" } } \ No newline at end of file diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.spec.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.spec.ts index 2694b8dea5a2..3757410c6510 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.spec.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LibComponent } from './lib.component'; diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.ts index 3fcbd0a37c78..345514700ee8 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.component.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Component, OnInit } from '@angular/core'; @Component({ diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.module.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.module.ts index 4db2feed5ae3..22f2b46c3e66 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.module.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.module.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { NgModule } from '@angular/core'; import { LibComponent } from './lib.component'; import { LibService } from './lib.service'; diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.spec.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.spec.ts index a808deee36e3..480f827b6999 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.spec.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { TestBed, inject } from '@angular/core/testing'; import { LibService } from './lib.service'; diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.ts index e8be2fbdc25e..c4745e811a63 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/lib/lib.service.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Injectable } from '@angular/core'; @Injectable() diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/public-api.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/public-api.ts index d3a2dc0db030..8b8ca08454c2 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/public-api.ts +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/public-api.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + /* * Public API Surface of lib */ diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/test.ts b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/test.ts deleted file mode 100644 index 798ae95022a0..000000000000 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/src/test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js'; -import 'zone.js/testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -declare const require: { - context(path: string, deep?: boolean, filter?: RegExp): { - keys(): string[]; - (id: string): T; - }; -}; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.lib.json b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.lib.json index 63dd9b693892..fe12470400e2 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.lib.json +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.lib.json @@ -1,18 +1,12 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2015", "declaration": true, "declarationMap": true, "inlineSources": true, - "types": [], - "lib": [ - "dom", - "es2018" - ] + "types": [] }, "exclude": [ - "src/test.ts", "**/*.spec.ts" ] } diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.spec.json b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.spec.json index f1c08bf277d3..eae9b9ae9c29 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.spec.json +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/projects/lib/tsconfig.spec.json @@ -6,9 +6,6 @@ "jasmine" ] }, - "files": [ - "src/test.ts" - ], "include": [ "**/*.spec.ts", "**/*.d.ts" diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/tsconfig.json b/packages/angular_devkit/build_angular/test/hello-world-lib/tsconfig.json index 2c5d470a7258..26bfbc225506 100644 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/tsconfig.json +++ b/packages/angular_devkit/build_angular/test/hello-world-lib/tsconfig.json @@ -8,12 +8,13 @@ "moduleResolution": "node", "experimentalDecorators": true, "target": "es2015", - "module": "es2020", + "module": "es2022", + "useDefineForClassFields": false, "typeRoots": [ "node_modules/@types" ], "lib": [ - "es2017", + "es2022", "dom" ] }, diff --git a/packages/angular_devkit/build_angular/test/hello-world-lib/tslint.json b/packages/angular_devkit/build_angular/test/hello-world-lib/tslint.json deleted file mode 100644 index 1dcd993d00c2..000000000000 --- a/packages/angular_devkit/build_angular/test/hello-world-lib/tslint.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "extends": "tslint:recommended", - "rulesDirectory": [ - "codelyzer" - ], - "rules": { - "align": { - "options": [ - "parameters", - "statements" - ] - }, - "array-type": false, - "arrow-return-shorthand": true, - "curly": true, - "deprecation": { - "severity": "warning" - }, - "eofline": true, - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "import-spacing": true, - "indent": { - "options": [ - "spaces" - ] - }, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "quotemark": [ - true, - "single" - ], - "semicolon": { - "options": [ - "always" - ] - }, - "space-before-function-paren": { - "options": { - "anonymous": "never", - "asyncArrow": "always", - "constructor": "never", - "method": "never", - "named": "never" - } - }, - "typedef-whitespace": { - "options": [ - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - }, - { - "call-signature": "onespace", - "index-signature": "onespace", - "parameter": "onespace", - "property-declaration": "onespace", - "variable-declaration": "onespace" - } - ] - }, - "whitespace": { - "options": [ - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type", - "check-typecast" - ] - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - } -} diff --git a/packages/angular_devkit/build_optimizer/BUILD.bazel b/packages/angular_devkit/build_optimizer/BUILD.bazel deleted file mode 100644 index e787842c8694..000000000000 --- a/packages/angular_devkit/build_optimizer/BUILD.bazel +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright Google Inc. All Rights Reserved. -# -# Use of this source code is governed by an MIT-style license that can be -# found in the LICENSE file at https://angular.io/license - -load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test") -load("//tools:defaults.bzl", "ts_library") - -# @external_begin -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -# @external_end - -licenses(["notice"]) # MIT - -package(default_visibility = ["//visibility:public"]) - -ts_library( - name = "build_optimizer", - srcs = glob( - include = ["src/**/*.ts"], - exclude = [ - # TODO(@filipesilva): shouldn't need to exclude the cli files but can't exclude them - # from jasmine_node_test. - "src/**/cli.ts", - "src/**/*_spec.ts", - "src/**/*_benchmark.ts", - ], - ), - data = [ - "package.json", - "webpack-loader/package.json", - ], - module_name = "@angular-devkit/build-optimizer", - module_root = "src/index.d.ts", - deps = [ - "@npm//@types/node", - "@npm//source-map", - "@npm//tslib", - "@npm//typescript", - "@npm//webpack", - ], -) - -ts_library( - name = "build_optimizer_test_lib", - testonly = True, - srcs = glob(["src/**/*_spec.ts"]), - # @external_begin - deps = [ - ":build_optimizer", - "//packages/angular_devkit/core", - "@npm//source-map", - "@npm//typescript", - ], - # @external_end -) - -jasmine_node_test( - name = "build_optimizer_test", - srcs = [":build_optimizer_test_lib"], - deps = [ - "@npm//jasmine", - "@npm//source-map", - ], -) - -# @external_begin -pkg_npm( - name = "npm_package", - deps = [ - ":build_optimizer", - ], -) - -pkg_tar( - name = "npm_package_archive", - srcs = [":npm_package"], - extension = "tar.gz", - strip_prefix = "./npm_package", - tags = ["manual"], -) -# @external_end diff --git a/packages/angular_devkit/build_optimizer/README.md b/packages/angular_devkit/build_optimizer/README.md deleted file mode 100644 index 6b97c1b3588d..000000000000 --- a/packages/angular_devkit/build_optimizer/README.md +++ /dev/null @@ -1,215 +0,0 @@ -# Angular Build Optimizer - -Angular Build Optimizer contains Angular optimizations applicable to JavaScript code as a TypeScript transform pipeline. - - -## Available optimizations - -Transformations applied depend on file content: - -- [Class fold](#class-fold), [Scrub file](#scrub-file) and [Prefix functions](#prefix-functions): applied to Angular apps and libraries. -- [Import tslib](#import-tslib): applied when TypeScript helpers are found. - -Some of these optimizations add `/*@__PURE__*/` comments. -These are used by JS optimization tools to identify pure functions that can potentially be dropped. - - -### Class fold - -Static properties are folded into ES5 classes: - -```typescript -// input -var Clazz = (function () { function Clazz() { } return Clazz; }()); -Clazz.prop = 1; - -// output -var Clazz = (function () { function Clazz() { } Clazz.prop = 1; return Clazz; }()); -``` - - -### Scrub file - -Angular decorators, property decorators and constructor parameters are removed, while leaving non-Angular ones intact. - -```typescript -// input -import { Injectable, Input, Component } from '@angular/core'; -import { NotInjectable, NotComponent, NotInput } from 'another-lib'; -var Clazz = (function () { function Clazz() { } return Clazz; }()); -Clazz.decorators = [{ type: Injectable }, { type: NotInjectable }]; -Clazz.propDecorators = { 'ngIf': [{ type: Input }] }; -Clazz.ctorParameters = function () { return [{type: Injector}]; }; -var ComponentClazz = (function () { - function ComponentClazz() { } - __decorate([ - Input(), - __metadata("design:type", Object) - ], Clazz.prototype, "selected", void 0); - __decorate([ - NotInput(), - __metadata("design:type", Object) - ], Clazz.prototype, "notSelected", void 0); - ComponentClazz = __decorate([ - NotComponent(), - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - ], ComponentClazz); - return ComponentClazz; -}()); - -// output -import { Injectable, Input, Component } from '@angular/core'; -import { NotInjectable, NotComponent } from 'another-lib'; -var Clazz = (function () { function Clazz() { } return Clazz; }()); -Clazz.decorators = [{ type: NotInjectable }]; -var ComponentClazz = (function () { - function ComponentClazz() { } - __decorate([ - NotInput(), - __metadata("design:type", Object) - ], Clazz.prototype, "notSelected", void 0); - ComponentClazz = __decorate([ - NotComponent() - ], ComponentClazz); - return ComponentClazz; -}()); -``` - - -### Prefix functions - -Adds `/*@__PURE__*/` comments to top level downleveled class declarations and instantiation. - -Warning: this transform assumes the file is a pure module. It should not be used with unpure modules. - -```typescript -// input -var Clazz = (function () { function Clazz() { } return Clazz; }()); -var newClazz = new Clazz(); -var newClazzTwo = Clazz(); - -// output -var Clazz = /*@__PURE__*/ (function () { function Clazz() { } return Clazz; }()); -var newClazz = /*@__PURE__*/ new Clazz(); -var newClazzTwo = /*@__PURE__*/ Clazz(); -``` - - -### Prefix Classes - -Adds `/*@__PURE__*/` to downleveled TypeScript classes. - -```typescript -// input -var ReplayEvent = (function () { - function ReplayEvent(time, value) { - this.time = time; - this.value = value; - } - return ReplayEvent; -}()); - -// output -var ReplayEvent = /*@__PURE__*/ (function () { - function ReplayEvent(time, value) { - this.time = time; - this.value = value; - } - return ReplayEvent; -}()); -``` - - -### Import tslib - -TypeScript helpers (`__extends/__decorate/__metadata/__param`) are replaced with `tslib` imports whenever found. - -```typescript -// input -var __extends = (this && this.__extends) || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -}; - -// output -import { __extends } from "tslib"; -``` - -### Wrap enums - -Wrap downleveled TypeScript enums in a function, and adds `/*@__PURE__*/` comment. - -```typescript -// input -var ChangeDetectionStrategy; -(function (ChangeDetectionStrategy) { - ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; - ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; -})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {})); - -// output -var ChangeDetectionStrategy = /*@__PURE__*/ (function () { - var ChangeDetectionStrategy = {}; - ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; - ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; - return ChangeDetectionStrategy; -})(); -``` - - -## Library Usage - -```typescript -import { buildOptimizer } from '@angular-devkit/build-optimizer'; - -const transpiledContent = buildOptimizer({ content: input }).content; -``` - -Available options: -```typescript -export interface BuildOptimizerOptions { - content?: string; - inputFilePath?: string; - outputFilePath?: string; - emitSourceMap?: boolean; - strict?: boolean; - isSideEffectFree?: boolean; -} -``` - - -## Webpack loader usage: - -```typescript -import { BuildOptimizerWebpackPlugin } from '@angular-devkit/build-optimizer'; - -module.exports = { - plugins: [ - new BuildOptimizerWebpackPlugin(), - ] - module: { - rules: [ - { - test: /\.js$/, - loader: '@angular-devkit/build-optimizer/webpack-loader', - options: { - sourceMap: false - } - } - ] - } -} -``` - - -## CLI usage - -```bash -build-optimizer input.js -build-optimizer input.js output.js -``` diff --git a/packages/angular_devkit/build_optimizer/package.json b/packages/angular_devkit/build_optimizer/package.json deleted file mode 100644 index 5bfa929d1189..000000000000 --- a/packages/angular_devkit/build_optimizer/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@angular-devkit/build-optimizer", - "version": "0.0.0", - "description": "Angular Build Optimizer", - "experimental": true, - "main": "src/index.js", - "typings": "src/index.d.ts", - "bin": { - "build-optimizer": "./src/build-optimizer/cli.js" - }, - "dependencies": { - "source-map": "0.7.3", - "tslib": "2.2.0", - "typescript": "4.2.4" - }, - "peerDependencies": { - "webpack": "^5.30.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } - }, - "devDependencies": { - "webpack": "5.32.0" - } -} diff --git a/packages/angular_devkit/build_optimizer/src/_golden-api.ts b/packages/angular_devkit/build_optimizer/src/_golden-api.ts deleted file mode 100644 index 06e63e7a2c84..000000000000 --- a/packages/angular_devkit/build_optimizer/src/_golden-api.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export * from './index'; -export { default } from './build-optimizer/webpack-loader'; diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer.ts deleted file mode 100644 index d1e79fc67abf..000000000000 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { readFileSync } from 'fs'; -import { - TransformJavascriptOptions, - TransformJavascriptOutput, - TransformerFactoryCreator, - transformJavascript, -} from '../helpers/transform-javascript'; -import { getPrefixClassesTransformer, testPrefixClasses } from '../transforms/prefix-classes'; -import { getPrefixFunctionsTransformer } from '../transforms/prefix-functions'; -import { - createScrubFileTransformerFactory, - testScrubFile, -} from '../transforms/scrub-file'; -import { getWrapEnumsTransformer } from '../transforms/wrap-enums'; - - -// Angular packages are known to have no side effects. -const knownSideEffectFreeAngularModules = [ - /[\\/]node_modules[\\/]@angular[\\/]animations[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]common[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]compiler[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]core[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]forms[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]http[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]platform-browser-dynamic[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]platform-browser[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]platform-webworker-dynamic[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]platform-webworker[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]router[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]upgrade[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]material[\\/]/, - /[\\/]node_modules[\\/]@angular[\\/]cdk[\\/]/, - /[\\/]node_modules[\\/]rxjs[\\/]/, -]; - -// Known locations for the source files of @angular/core. -const coreFilesRegex = /[\\/]node_modules[\\/]@angular[\\/]core[\\/][f]?esm2015[\\/]/; - -function isKnownCoreFile(filePath: string) { - return coreFilesRegex.test(filePath); -} - -function isKnownSideEffectFree(filePath: string) { - // rxjs add imports contain intentional side effects - if (/[\\/]node_modules[\\/]rxjs[\\/]add[\\/]/.test(filePath)) { - return false; - } - - return knownSideEffectFreeAngularModules.some((re) => re.test(filePath)); -} - -export interface BuildOptimizerOptions { - content?: string; - originalFilePath?: string; - inputFilePath?: string; - outputFilePath?: string; - emitSourceMap?: boolean; - strict?: boolean; - isSideEffectFree?: boolean; - isAngularCoreFile?: boolean; -} - -export function buildOptimizer(options: BuildOptimizerOptions): TransformJavascriptOutput { - const { inputFilePath } = options; - let { originalFilePath, content, isAngularCoreFile } = options; - - if (!originalFilePath && inputFilePath) { - originalFilePath = inputFilePath; - } - - if (!inputFilePath && content === undefined) { - throw new Error('Either filePath or content must be specified in options.'); - } - - if (content === undefined) { - content = readFileSync(inputFilePath as string, 'UTF-8'); - } - - if (!content) { - return { - content: null, - sourceMap: null, - emitSkipped: true, - }; - } - - if (isAngularCoreFile === undefined) { - isAngularCoreFile = !!originalFilePath && isKnownCoreFile(originalFilePath); - } - - const hasSafeSideEffects = originalFilePath && isKnownSideEffectFree(originalFilePath); - - // Determine which transforms to apply. - const getTransforms: TransformerFactoryCreator[] = []; - - let typeCheck = false; - if (hasSafeSideEffects) { - // Angular modules have known safe side effects - getTransforms.push( - // getPrefixFunctionsTransformer is rather dangerous, apply only to known pure es5 modules. - // It will mark both `require()` calls and `console.log(stuff)` as pure. - // We only apply it to modules known to be side effect free, since we know they are safe. - getPrefixFunctionsTransformer, - ); - typeCheck = true; - } else if (testPrefixClasses(content)) { - // This is only relevant if prefix functions is not used since prefix functions will prefix IIFE wrapped classes. - getTransforms.unshift(getPrefixClassesTransformer); - } - - if (testScrubFile(content)) { - // Always test as these require the type checker - getTransforms.push(createScrubFileTransformerFactory(isAngularCoreFile)); - typeCheck = true; - } - - getTransforms.push(getWrapEnumsTransformer); - - const transformJavascriptOpts: TransformJavascriptOptions = { - content: content, - inputFilePath: options.inputFilePath, - outputFilePath: options.outputFilePath, - emitSourceMap: options.emitSourceMap, - strict: options.strict, - getTransforms, - typeCheck, - }; - - return transformJavascript(transformJavascriptOpts); -} diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts deleted file mode 100644 index e86217806082..000000000000 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts +++ /dev/null @@ -1,347 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -// tslint:disable-next-line:no-implicit-dependencies -import { tags } from '@angular-devkit/core'; -import { RawSourceMap } from 'source-map'; -import { TransformJavascriptOutput } from '../helpers/transform-javascript'; -import { buildOptimizer } from './build-optimizer'; - - -describe('build-optimizer', () => { - const imports = ` - import { __decorate, __metadata } from "tslib"; - import { Injectable, Input, Component } from '@angular/core'; - `; - const clazz = 'var Clazz = (function () { function Clazz() { } return Clazz; }());'; - const decorators = 'Clazz.decorators = [ { type: Injectable } ];'; - - describe('basic functionality', () => { - it('applies scrub-file and prefix-functions to side-effect free modules', () => { - const input = tags.stripIndent` - ${imports} - var ChangeDetectionStrategy; - (function (ChangeDetectionStrategy) { - ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; - ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; - })(ChangeDetectionStrategy || (ChangeDetectionStrategy = {})); - ${clazz} - ${decorators} - Clazz.propDecorators = { 'ngIf': [{ type: Input }] }; - Clazz.ctorParameters = function () { return [{type: Injectable}]; }; - var ComponentClazz = (function () { - function ComponentClazz() { } - __decorate([ - Input(), - __metadata("design:type", Object) - ], Clazz.prototype, "selected", void 0); - ComponentClazz = __decorate([ - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }), - __metadata("design:paramtypes", [Injectable]) - ], ComponentClazz); - return ComponentClazz; - }()); - var RenderType_MdOption = ɵcrt({ encapsulation: 2, styles: styles_MdOption}); - `; - const output = tags.oneLine` - ${imports} - var ChangeDetectionStrategy = /*@__PURE__*/ (function (ChangeDetectionStrategy) { - ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; - ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; - return ChangeDetectionStrategy; - })({}); - var Clazz = /*@__PURE__*/ (function () { function Clazz() { } return Clazz; }()); - var ComponentClazz = /*@__PURE__*/ (function () { - function ComponentClazz() { } - return ComponentClazz; - }()); - var RenderType_MdOption = /*@__PURE__*/ ɵcrt({ encapsulation: 2, styles: styles_MdOption }); - `; - - // Check Angular 4/5 and unix/windows paths. - const inputPaths = [ - '/node_modules/@angular/core/fesm2015/core.js', - '/node_modules/@angular/core/esm2015/core.js', - '\\node_modules\\@angular\\core\\fesm2015\\core.js', - '\\node_modules\\@angular\\core\\esm2015\\core.js', - ]; - - inputPaths.forEach((inputFilePath) => { - const boOutput = buildOptimizer({ content: input, inputFilePath }); - expect(tags.oneLine`${boOutput.content}`).toEqual(output); - expect(boOutput.emitSkipped).toEqual(false); - }); - }); - - it('should not add pure comments to tslib helpers', () => { - const input = tags.stripIndent` - class LanguageState { - } - - LanguageState.ctorParameters = () => [ - { type: TranslateService }, - { type: undefined, decorators: [{ type: Inject, args: [LANGUAGE_CONFIG,] }] } - ]; - - __decorate([ - Action(CheckLanguage), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", void 0) - ], LanguageState.prototype, "checkLanguage", null); - `; - - const output = tags.oneLine` - let LanguageState = /*@__PURE__*/ (() => { - class LanguageState { - } - - __decorate([ - Action(CheckLanguage), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", void 0) - ], LanguageState.prototype, "checkLanguage", null); - return LanguageState; - })(); - `; - - const boOutput = buildOptimizer({ content: input, isSideEffectFree: true }); - expect(tags.oneLine`${boOutput.content}`).toEqual(output); - expect(boOutput.emitSkipped).toEqual(false); - }); - - it('should not add pure comments to tslib helpers with $ and number suffix', () => { - const input = tags.stripIndent` - class LanguageState { - } - - LanguageState.ctorParameters = () => [ - { type: TranslateService }, - { type: undefined, decorators: [{ type: Inject, args: [LANGUAGE_CONFIG,] }] } - ]; - - __decorate$1([ - Action(CheckLanguage), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", void 0) - ], LanguageState.prototype, "checkLanguage", null); - `; - - const output = tags.oneLine` - let LanguageState = /*@__PURE__*/ (() => { - class LanguageState { - } - - __decorate$1([ - Action(CheckLanguage), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", void 0) - ], LanguageState.prototype, "checkLanguage", null); - return LanguageState; - })(); - `; - - const boOutput = buildOptimizer({ content: input, isSideEffectFree: true }); - expect(tags.oneLine`${boOutput.content}`).toEqual(output); - expect(boOutput.emitSkipped).toEqual(false); - }); - - it('should not wrap classes which had all static properties dropped in IIFE', () => { - const classDeclaration = tags.oneLine` - import { Injectable } from '@angular/core'; - - class Platform { - constructor(_doc) { - } - init() { - } - } - `; - const input = tags.oneLine` - ${classDeclaration} - - Platform.decorators = [ - { type: Injectable } - ]; - - /** @nocollapse */ - Platform.ctorParameters = () => [ - { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT] }] } - ]; - `; - - const boOutput = buildOptimizer({ content: input, isSideEffectFree: true }); - expect(tags.oneLine`${boOutput.content}`).toEqual(classDeclaration); - expect(boOutput.emitSkipped).toEqual(false); - }); - }); - - - describe('resilience', () => { - it('doesn\'t process files with invalid syntax by default', () => { - const input = tags.oneLine` - ))))invalid syntax - ${clazz} - Clazz.decorators = [ { type: Injectable } ]; - `; - - const boOutput = buildOptimizer({ content: input }); - expect(boOutput.content).toBeFalsy(); - expect(boOutput.emitSkipped).toEqual(true); - }); - - it('throws on files with invalid syntax in strict mode', () => { - const input = tags.oneLine` - ))))invalid syntax - ${clazz} - Clazz.decorators = [ { type: Injectable } ]; - `; - - expect(() => buildOptimizer({ content: input, strict: true })).toThrow(); - }); - - // TODO: re-enable this test when updating to TypeScript >2.9.1. - // The `prefix-classes` tests will also need to be adjusted. - // See https://github.com/angular/devkit/pull/998#issuecomment-393867606 for more info. - xit(`doesn't exceed call stack size when type checking very big classes`, () => { - // BigClass with a thousand methods. - // Clazz is included with ctorParameters to trigger transforms with type checking. - const input = ` - var BigClass = /** @class */ (function () { - function BigClass() { - } - ${Array.from(new Array(1000)).map((_v, i) => - `BigClass.prototype.method${i} = function () { return this.myVar; };`, - ).join('\n')} - return BigClass; - }()); - ${clazz} - Clazz.ctorParameters = function () { return []; }; - `; - - let boOutput: TransformJavascriptOutput; - expect(() => { - boOutput = buildOptimizer({ content: input }); - expect(boOutput.emitSkipped).toEqual(false); - }).not.toThrow(); - }); - }); - - describe('known side effect free modules', () => { - // This statement is considered pure by getPrefixFunctionsTransformer on known side effect free - // modules. - const input = 'console.log(42);'; - const output = '/*@__PURE__*/ console.log(42);'; - - it('should process known side effect free modules', () => { - const inputFilePath = '/node_modules/@angular/core/@angular/core.es5.js'; - const boOutput = buildOptimizer({ content: input, inputFilePath }); - expect(boOutput.content).toContain(output); - expect(boOutput.emitSkipped).toEqual(false); - }); - - it('should not process modules which are not in the list of known side effect free modules', - () => { - const inputFilePath = '/node_modules/other-package/core.es5.js'; - const boOutput = buildOptimizer({ content: input, inputFilePath }); - expect(boOutput.emitSkipped).toEqual(true); - }); - - it('should not process umd modules which are not in the list of known side effect free modules', - () => { - const inputFilePath = '/node_modules/other_lib/index.js'; - const boOutput = buildOptimizer({ content: input, inputFilePath }); - expect(boOutput.emitSkipped).toEqual(true); - }); - }); - - describe('sourcemaps', () => { - const transformableInput = tags.oneLine` - ${imports} - ${clazz} - ${decorators} - `; - - it('doesn\'t produce sourcemaps by default', () => { - expect(buildOptimizer({ content: transformableInput }).sourceMap).toBeFalsy(); - }); - - it('produces sourcemaps', () => { - expect(buildOptimizer( - { content: transformableInput, emitSourceMap: true }, - ).sourceMap).toBeTruthy(); - }); - - // TODO: re-enable this test, it was temporarily disabled as part of - // https://github.com/angular/devkit/pull/842 - xit('doesn\'t produce sourcemaps when emitting was skipped', () => { - const ignoredInput = tags.oneLine` - var Clazz = (function () { function Clazz() { } return Clazz; }()); - `; - const invalidInput = tags.oneLine` - ))))invalid syntax - ${clazz} - Clazz.decorators = [ { type: Injectable } ]; - `; - - const ignoredOutput = buildOptimizer({ content: ignoredInput, emitSourceMap: true }); - expect(ignoredOutput.emitSkipped).toBeTruthy(); - expect(ignoredOutput.sourceMap).toBeFalsy(); - - const invalidOutput = buildOptimizer({ content: invalidInput, emitSourceMap: true }); - expect(invalidOutput.emitSkipped).toBeTruthy(); - expect(invalidOutput.sourceMap).toBeFalsy(); - }); - - it('emits sources content', () => { - const sourceMap = buildOptimizer( - { content: transformableInput, emitSourceMap: true }, - ).sourceMap as RawSourceMap; - const sourceContent = sourceMap.sourcesContent as string[]; - expect(sourceContent[0]).toEqual(transformableInput); - }); - - it('uses empty strings if inputFilePath and outputFilePath is not provided', () => { - const { content, sourceMap } = buildOptimizer( - { content: transformableInput, emitSourceMap: true }); - - if (!sourceMap) { - throw new Error('sourceMap was not generated.'); - } - expect(sourceMap.file).toEqual(''); - expect(sourceMap.sources[0]).toEqual(''); - expect(content).not.toContain(`sourceMappingURL`); - }); - - it('uses inputFilePath and outputFilePath if provided', () => { - const inputFilePath = '/path/to/file.js'; - const outputFilePath = '/path/to/file.bo.js'; - const { content, sourceMap } = buildOptimizer({ - content: transformableInput, - emitSourceMap: true, - inputFilePath, - outputFilePath, - }); - - if (!sourceMap) { - throw new Error('sourceMap was not generated.'); - } - expect(sourceMap.file).toEqual(outputFilePath); - expect(sourceMap.sources[0]).toEqual(inputFilePath); - expect(content).toContain(`sourceMappingURL=${outputFilePath}.map`); - }); - }); - -}); diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/cli.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/cli.ts deleted file mode 100644 index f09f53defddc..000000000000 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/cli.ts +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { writeFileSync } from 'fs'; -import { join } from 'path'; -import { buildOptimizer } from './build-optimizer'; - -// tslint:disable: no-console -if (process.argv.length < 3 || process.argv.length > 4) { - throw new Error(` - build-optimizer should be called with either one or two arguments: - - build-optimizer input.js - build-optimizer input.js output.js - `); -} - -const currentDir = process.cwd(); - -const inputFile = process.argv[2]; -const tsOrJsRegExp = /\.(j|t)s$/; - -if (!inputFile.match(tsOrJsRegExp)) { - throw new Error(`Input file must be .js or .ts.`); -} - -// Use provided output file, or add the .bo suffix before the extension. -const outputFile = process.argv[3] || inputFile.replace(tsOrJsRegExp, subStr => `.bo${subStr}`); - -const boOutput = buildOptimizer({ - inputFilePath: join(currentDir, inputFile), - outputFilePath: join(currentDir, outputFile), - emitSourceMap: true, -}); - -if (boOutput.emitSkipped) { - console.log('Nothing to emit.'); -} else { - writeFileSync(join(currentDir, outputFile), boOutput.content); - writeFileSync(join(currentDir, `${outputFile}.map`), JSON.stringify(boOutput.sourceMap)); - console.log('Emitted:'); - console.log(` ${outputFile}`); - console.log(` ${outputFile}.map`); -} diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.ts deleted file mode 100644 index cb661c66abbe..000000000000 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** - * @fileoverview This adapts the buildOptimizer to run over each file as it is - * processed by Rollup. We must do this since buildOptimizer expects to see the - * ESModules in the input sources, and therefore cannot run on the rollup output - */ - -import * as path from 'path'; -import { RawSourceMap } from 'source-map'; -import { buildOptimizer } from './build-optimizer'; - -const DEBUG = false; - -export interface Options { - sideEffectFreeModules?: string[]; - angularCoreModules?: string[]; -} - -export default function optimizer(options: Options) { - // Normalize paths for comparison. - if (options.sideEffectFreeModules) { - options.sideEffectFreeModules = options.sideEffectFreeModules.map(p => p.replace(/\\/g, '/')); - } - - return { - name: 'build-optimizer', - transform: (content: string, id: string): { code: string; map: RawSourceMap } | null => { - const normalizedId = id.replace(/\\/g, '/'); - const isSideEffectFree = - options.sideEffectFreeModules && - options.sideEffectFreeModules.some(m => normalizedId.indexOf(m) >= 0); - const isAngularCoreFile = - options.angularCoreModules && - options.angularCoreModules.some(m => normalizedId.indexOf(m) >= 0); - const { content: code, sourceMap: map } = buildOptimizer({ - content, - inputFilePath: id, - emitSourceMap: true, - isSideEffectFree, - isAngularCoreFile, - }); - if (!code) { - if (DEBUG) { - // tslint:disable-next-line: no-console - console.error( - 'no transforms produced by buildOptimizer for ' + path.relative(process.cwd(), id), - ); - } - - return null; - } - if (!map) { - throw new Error('no sourcemap produced by buildOptimizer'); - } - - return { code, map }; - }, - }; -} diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/webpack-loader.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/webpack-loader.ts deleted file mode 100644 index 1fc6fcdb423c..000000000000 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/webpack-loader.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { RawSourceMap } from 'source-map'; -import { sources } from 'webpack'; -import { buildOptimizer } from './build-optimizer'; - -interface BuildOptimizerLoaderOptions { - sourceMap: boolean; -} - -export const buildOptimizerLoaderPath = __filename; - -const alwaysProcess = (path: string) => path.endsWith('.ts') || path.endsWith('.tsx'); - -export default function buildOptimizerLoader( - // Webpack 5 does not provide a LoaderContext type - this: { - resourcePath: string; - _module: { factoryMeta: { skipBuildOptimizer?: boolean; sideEffectFree?: boolean } }; - cacheable(): void; - callback(error?: Error | null, content?: string, sourceMap?: unknown): void; - getOptions(): unknown; - }, - content: string, - previousSourceMap: RawSourceMap, -) { - this.cacheable(); - - const skipBuildOptimizer = - this._module && this._module.factoryMeta && this._module.factoryMeta.skipBuildOptimizer; - - if (!alwaysProcess(this.resourcePath) && skipBuildOptimizer) { - // Skip loading processing this file with Build Optimizer if we determined in - // BuildOptimizerWebpackPlugin that we shouldn't. - this.callback(null, content, previousSourceMap); - - return; - } - - const options = (this.getOptions() || {}) as BuildOptimizerLoaderOptions; - - const boOutput = buildOptimizer({ - content, - originalFilePath: this.resourcePath, - inputFilePath: this.resourcePath, - outputFilePath: this.resourcePath, - emitSourceMap: options.sourceMap, - isSideEffectFree: - this._module && this._module.factoryMeta && this._module.factoryMeta.sideEffectFree, - }); - - if (boOutput.emitSkipped || boOutput.content === null) { - this.callback(null, content, previousSourceMap); - - return; - } - - const intermediateSourceMap = boOutput.sourceMap; - let newContent = boOutput.content; - - let newSourceMap; - - if (options.sourceMap && intermediateSourceMap) { - // Webpack doesn't need sourceMappingURL since we pass them on explicitely. - newContent = newContent.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''); - - if (previousSourceMap) { - // Use http://sokra.github.io/source-map-visualization/ to validate sourcemaps make sense. - newSourceMap = new sources.SourceMapSource( - newContent, - this.resourcePath, - intermediateSourceMap, - content, - previousSourceMap, - true, - ).map(); - } else { - // Otherwise just return our generated sourcemap. - newSourceMap = intermediateSourceMap; - } - } - - this.callback(null, newContent, newSourceMap); -} diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/webpack-plugin.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/webpack-plugin.ts deleted file mode 100644 index 29aa1ffbbc7f..000000000000 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/webpack-plugin.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import type { Compiler, Module } from 'webpack'; - -interface ModuleData { - resourceResolveData?: { descriptionFileData?: { typings?: string } }; -} - -export class BuildOptimizerWebpackPlugin { - apply(compiler: Compiler) { - compiler.hooks.normalModuleFactory.tap('BuildOptimizerWebpackPlugin', nmf => { - // tslint:disable-next-line: no-any - nmf.hooks.module.tap('BuildOptimizerWebpackPlugin', (module: Module, data: ModuleData) => { - if (data.resourceResolveData?.descriptionFileData) { - // Only TS packages should use Build Optimizer. - // Notes: - // - a TS package might not have defined typings but still use .d.ts files next to their - // .js files. We don't cover that case because the Angular Package Format (APF) calls for - // using the Typings field and Build Optimizer is geared towards APF. Maybe we could - // provide configuration options to the plugin to cover that case if there's demand. - // - a JS-only package that also happens to provides typings will also be flagged by this - // check. Not sure there's a good way to skip those. - const skipBuildOptimizer = !data.resourceResolveData.descriptionFileData.typings; - module.factoryMeta = { ...module.factoryMeta, skipBuildOptimizer }; - } - - return module; - }); - }); - } -} diff --git a/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts b/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts deleted file mode 100644 index d035dda72612..000000000000 --- a/packages/angular_devkit/build_optimizer/src/helpers/ast-utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as tslib from 'tslib'; -import * as ts from 'typescript'; - -const pureFunctionComment = '@__PURE__'; - -// We include only exports that start with '__' because tslib helpers -// all start with a suffix of two underscores. -const tslibHelpers = new Set(Object.keys(tslib).filter(h => h.startsWith('__'))); - -// Find all nodes from the AST in the subtree of node of SyntaxKind kind. -export function collectDeepNodes(node: ts.Node, kind: ts.SyntaxKind): T[] { - const nodes: T[] = []; - const helper = (child: ts.Node) => { - if (child.kind === kind) { - nodes.push(child as T); - } - ts.forEachChild(child, helper); - }; - ts.forEachChild(node, helper); - - return nodes; -} - -export function addPureComment(node: T): T { - return ts.addSyntheticLeadingComment( - node, - ts.SyntaxKind.MultiLineCommentTrivia, - pureFunctionComment, - false, - ); -} - -export function hasPureComment(node: ts.Node): boolean { - if (!node) { - return false; - } - - const leadingComment = ts.getSyntheticLeadingComments(node); - - return !!leadingComment && leadingComment.some(comment => comment.text === pureFunctionComment); -} - -export function isHelperName(name: string): boolean { - return tslibHelpers.has(name); -} - -/** - * In FESM's when not using `importHelpers` there might be multiple in the same file. - @example - ``` - var __decorate$1 = ''; - var __decorate$2 = ''; - ``` - * @returns Helper name without the '$' and number suffix or `undefined` if it's not a helper. - */ -export function getCleanHelperName(name: string): string | undefined { - const parts = name.split('$'); - const cleanName = parts[0]; - - if (parts.length > 2 || (parts.length === 2 && isNaN(+parts[1]))) { - return undefined; - } - - return isHelperName(cleanName) ? cleanName : undefined; -} diff --git a/packages/angular_devkit/build_optimizer/src/helpers/transform-javascript.ts b/packages/angular_devkit/build_optimizer/src/helpers/transform-javascript.ts deleted file mode 100644 index 91628bee2acf..000000000000 --- a/packages/angular_devkit/build_optimizer/src/helpers/transform-javascript.ts +++ /dev/null @@ -1,221 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { RawSourceMap } from 'source-map'; -import * as ts from 'typescript'; - -export type TransformerFactoryCreator = ( - program?: ts.Program, -) => ts.TransformerFactory; - -export interface TransformJavascriptOptions { - content: string; - inputFilePath?: string; - outputFilePath?: string; - emitSourceMap?: boolean; - strict?: boolean; - typeCheck?: boolean; - getTransforms: TransformerFactoryCreator[]; -} - -export interface TransformJavascriptOutput { - content: string | null; - sourceMap: RawSourceMap | null; - emitSkipped: boolean; -} - -interface DiagnosticSourceFile extends ts.SourceFile { - readonly parseDiagnostics?: ReadonlyArray; -} - -function validateDiagnostics(diagnostics: ReadonlyArray, strict?: boolean): boolean { - // Print error diagnostics. - - const hasError = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error); - if (hasError) { - // Throw only if we're in strict mode, otherwise return original content. - if (strict) { - const errorMessages = ts.formatDiagnostics(diagnostics, { - getCurrentDirectory: () => ts.sys.getCurrentDirectory(), - getNewLine: () => ts.sys.newLine, - getCanonicalFileName: (f: string) => f, - }); - - throw new Error(` - TS failed with the following error messages: - - ${errorMessages} - `); - } else { - return false; - } - } - - return true; -} - -export function transformJavascript( - options: TransformJavascriptOptions, -): TransformJavascriptOutput { - - const { - content, - getTransforms, - emitSourceMap, - inputFilePath, - outputFilePath, - strict, - } = options; - - // Bail if there's no transform to do. - if (getTransforms.length === 0) { - return { - content: null, - sourceMap: null, - emitSkipped: true, - }; - } - - const allowFastPath = options.typeCheck === false && !emitSourceMap; - const outputs = new Map(); - const tempFilename = 'bo-default-file.js'; - const tempSourceFile = ts.createSourceFile( - tempFilename, - content, - ts.ScriptTarget.Latest, - allowFastPath, - ); - const parseDiagnostics = (tempSourceFile as DiagnosticSourceFile).parseDiagnostics; - - const tsOptions: ts.CompilerOptions = { - // We target latest so that there is no downleveling. - target: ts.ScriptTarget.Latest, - isolatedModules: true, - suppressOutputPathCheck: true, - allowNonTsExtensions: true, - noLib: true, - noResolve: true, - sourceMap: emitSourceMap, - inlineSources: emitSourceMap, - inlineSourceMap: false, - }; - - if (allowFastPath && parseDiagnostics) { - if (!validateDiagnostics(parseDiagnostics, strict)) { - return { - content: null, - sourceMap: null, - emitSkipped: true, - }; - } - - // All fast path transformers do not use a program - const transforms = getTransforms.map((getTf) => getTf(/* program */ undefined)); - - const result = ts.transform(tempSourceFile, transforms, tsOptions); - if (result.transformed.length === 0 || result.transformed[0] === tempSourceFile) { - return { - content: null, - sourceMap: null, - emitSkipped: true, - }; - } - - const printer = ts.createPrinter( - undefined, - { - onEmitNode: result.emitNodeWithNotification, - substituteNode: result.substituteNode, - }, - ); - - const output = printer.printFile(result.transformed[0]); - - result.dispose(); - - return { - content: output, - sourceMap: null, - emitSkipped: false, - }; - } - - const host: ts.CompilerHost = { - getSourceFile: (fileName) => { - if (fileName !== tempFilename) { - throw new Error(`File ${fileName} does not have a sourceFile.`); - } - - return tempSourceFile; - }, - getDefaultLibFileName: () => 'lib.d.ts', - getCurrentDirectory: () => '', - getDirectories: () => [], - getCanonicalFileName: (fileName) => fileName, - useCaseSensitiveFileNames: () => true, - getNewLine: () => '\n', - fileExists: (fileName) => fileName === tempFilename, - readFile: (_fileName) => '', - writeFile: (fileName, text) => outputs.set(fileName, text), - }; - - const program = ts.createProgram([tempFilename], tsOptions, host); - - const diagnostics = program.getSyntacticDiagnostics(tempSourceFile); - if (!validateDiagnostics(diagnostics, strict)) { - return { - content: null, - sourceMap: null, - emitSkipped: true, - }; - } - - // We need the checker inside transforms. - const transforms = getTransforms.map((getTf) => getTf(program)); - - program.emit(undefined, undefined, undefined, undefined, { before: transforms, after: [] }); - - let transformedContent = outputs.get(tempFilename); - - if (!transformedContent) { - return { - content: null, - sourceMap: null, - emitSkipped: true, - }; - } - - let sourceMap: RawSourceMap | null = null; - const tsSourceMap = outputs.get(`${tempFilename}.map`); - - if (emitSourceMap && tsSourceMap) { - const urlRegExp = /^\/\/# sourceMappingURL=[^\r\n]*/gm; - sourceMap = JSON.parse(tsSourceMap) as RawSourceMap; - // Fix sourcemaps file references. - if (outputFilePath) { - sourceMap.file = outputFilePath; - transformedContent = transformedContent.replace(urlRegExp, - `//# sourceMappingURL=${sourceMap.file}.map\n`); - if (inputFilePath) { - sourceMap.sources = [inputFilePath]; - } else { - sourceMap.sources = ['']; - } - } else { - // TODO: figure out if we should inline sources here. - transformedContent = transformedContent.replace(urlRegExp, ''); - sourceMap.file = ''; - sourceMap.sources = ['']; - } - } - - return { - content: transformedContent, - sourceMap, - emitSkipped: false, - }; -} diff --git a/packages/angular_devkit/build_optimizer/src/index.ts b/packages/angular_devkit/build_optimizer/src/index.ts deleted file mode 100644 index 4d4c11ab9c79..000000000000 --- a/packages/angular_devkit/build_optimizer/src/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as ts from 'typescript'; -import { createScrubFileTransformerFactory } from './transforms/scrub-file'; - -export { - default as buildOptimizerLoader, - buildOptimizerLoaderPath, -} from './build-optimizer/webpack-loader'; -export { BuildOptimizerWebpackPlugin } from './build-optimizer/webpack-plugin'; -export { buildOptimizer } from './build-optimizer/build-optimizer'; - -export { transformJavascript } from './helpers/transform-javascript'; - -export { getPrefixClassesTransformer } from './transforms/prefix-classes'; -export { getPrefixFunctionsTransformer } from './transforms/prefix-functions'; -export { getWrapEnumsTransformer } from './transforms/wrap-enums'; - -export function getScrubFileTransformer( - program?: ts.Program, -): ts.TransformerFactory { - return createScrubFileTransformerFactory(false)(program); -} - -export function getScrubFileTransformerForCore( - program?: ts.Program, -): ts.TransformerFactory { - return createScrubFileTransformerFactory(true)(program); -} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts deleted file mode 100644 index 77d01ab12491..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as ts from 'typescript'; -import { addPureComment } from '../helpers/ast-utils'; - -export function testPrefixClasses(content: string) { - const exportVarSetter = /(?:export )?(?:var|const)\s+(?:\S+)\s*=\s*/; - const multiLineComment = /\s*(?:\/\*[\s\S]*?\*\/)?\s*/; - const newLine = /\s*\r?\n\s*/; - - const regexes = [ - [ - /^/, - exportVarSetter, multiLineComment, - /\(/, multiLineComment, - /\s*function \(\) {/, newLine, - multiLineComment, - /function (?:\S+)\([^\)]*\) \{/, newLine, - ], - [ - /^/, - exportVarSetter, multiLineComment, - /\(/, multiLineComment, - /\s*function \(_super\) {/, newLine, - /\S*\.?__extends\(\S+, _super\);/, - ], - ].map(arr => new RegExp(arr.map(x => x.source).join(''), 'm')); - - return regexes.some((regex) => regex.test(content)); -} - -const superParameterName = '_super'; -const extendsHelperName = '__extends'; - -export function getPrefixClassesTransformer(): ts.TransformerFactory { - return (context: ts.TransformationContext): ts.Transformer => { - const transformer: ts.Transformer = (sf: ts.SourceFile) => { - const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult => { - - // Add pure comment to downleveled classes. - if (ts.isVariableStatement(node) && isDownleveledClass(node)) { - const varDecl = node.declarationList.declarations[0]; - const varInitializer = varDecl.initializer as ts.Expression; - - // Update node with the pure comment before the variable declaration initializer. - const newNode = ts.updateVariableStatement( - node, - node.modifiers, - ts.updateVariableDeclarationList( - node.declarationList, - [ - ts.updateVariableDeclaration( - varDecl, - varDecl.name, - varDecl.type, - addPureComment(varInitializer), - ), - ], - ), - ); - - // Replace node with modified one. - return ts.visitEachChild(newNode, visitor, context); - } - - // Otherwise return node as is. - return ts.visitEachChild(node, visitor, context); - }; - - return ts.visitEachChild(sf, visitor, context); - }; - - return transformer; - }; -} - -// Determine if a node matched the structure of a downleveled TS class. -function isDownleveledClass(node: ts.Node): boolean { - - if (!ts.isVariableStatement(node)) { - return false; - } - - if (node.declarationList.declarations.length !== 1) { - return false; - } - - const variableDeclaration = node.declarationList.declarations[0]; - - if (!ts.isIdentifier(variableDeclaration.name) - || !variableDeclaration.initializer) { - return false; - } - - let potentialClass = variableDeclaration.initializer; - - // TS 2.3 has an unwrapped class IIFE - // TS 2.4 uses a function expression wrapper - // TS 2.5 uses an arrow function wrapper - if (ts.isParenthesizedExpression(potentialClass)) { - potentialClass = potentialClass.expression; - } - - if (!ts.isCallExpression(potentialClass) || potentialClass.arguments.length > 1) { - return false; - } - - let wrapperBody: ts.Block; - if (ts.isFunctionExpression(potentialClass.expression)) { - wrapperBody = potentialClass.expression.body; - } else if (ts.isArrowFunction(potentialClass.expression) - && ts.isBlock(potentialClass.expression.body)) { - wrapperBody = potentialClass.expression.body; - } else { - return false; - } - - if (wrapperBody.statements.length === 0) { - return false; - } - - const functionExpression = potentialClass.expression; - const functionStatements = wrapperBody.statements; - - // need a minimum of two for a function declaration and return statement - if (functionStatements.length < 2) { - return false; - } - - const firstStatement = functionStatements[0]; - - // find return statement - may not be last statement - let returnStatement: ts.ReturnStatement | undefined; - for (let i = functionStatements.length - 1; i > 0; i--) { - if (ts.isReturnStatement(functionStatements[i])) { - returnStatement = functionStatements[i] as ts.ReturnStatement; - break; - } - } - - if (returnStatement == undefined - || returnStatement.expression == undefined - || !ts.isIdentifier(returnStatement.expression)) { - return false; - } - - if (functionExpression.parameters.length === 0) { - // potential non-extended class or wrapped es2015 class - return (ts.isFunctionDeclaration(firstStatement) || ts.isClassDeclaration(firstStatement)) - && firstStatement.name !== undefined - && returnStatement.expression.text === firstStatement.name.text; - } else if (functionExpression.parameters.length !== 1) { - return false; - } - - // Potential extended class - - const functionParameter = functionExpression.parameters[0]; - - if (!ts.isIdentifier(functionParameter.name) - || functionParameter.name.text !== superParameterName) { - return false; - } - - if (functionStatements.length < 3 || !ts.isExpressionStatement(firstStatement)) { - return false; - } - - if (!ts.isCallExpression(firstStatement.expression)) { - return false; - } - - const extendCallExpression = firstStatement.expression; - - let functionName; - if (ts.isIdentifier(extendCallExpression.expression)) { - functionName = extendCallExpression.expression.text; - } else if (ts.isPropertyAccessExpression(extendCallExpression.expression)) { - functionName = extendCallExpression.expression.name.text; - } - - if (!functionName || !functionName.endsWith(extendsHelperName)) { - return false; - } - - if (extendCallExpression.arguments.length === 0) { - return false; - } - - const lastArgument = extendCallExpression.arguments[extendCallExpression.arguments.length - 1]; - - if (!ts.isIdentifier(lastArgument) || lastArgument.text !== functionParameter.name.text) { - return false; - } - - const secondStatement = functionStatements[1]; - - return ts.isFunctionDeclaration(secondStatement) - && secondStatement.name !== undefined - && returnStatement.expression.text === secondStatement.name.text; -} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts deleted file mode 100644 index 21149b28bf00..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts +++ /dev/null @@ -1,361 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -// tslint:disable-next-line:no-implicit-dependencies -import { tags } from '@angular-devkit/core'; -import { transformJavascript } from '../helpers/transform-javascript'; -import { getPrefixClassesTransformer, testPrefixClasses } from './prefix-classes'; - - -const transform = (content: string) => transformJavascript( - { content, getTransforms: [getPrefixClassesTransformer] }).content; - -describe('prefix-classes', () => { - it('prefix TS 2.0 - 2.4 downlevel class', () => { - const input = tags.stripIndent` - var BasicTestCase = (function () { - function BasicTestCase() { - } - return BasicTestCase; - }()); - `; - const output = tags.stripIndent` - var BasicTestCase = /*@__PURE__*/ (function () { - function BasicTestCase() { - } - return BasicTestCase; - }()); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - // NOTE: TS 2.1+ uses a standalone export after the variable statement - it('prefix TS 2.0 exported downlevel class with ES2015 modules', () => { - const input = tags.stripIndent` - export var OuterSubscriber = (function (_super) { - __extends(OuterSubscriber, _super); - function OuterSubscriber() { - _super.apply(this, arguments); - } - return OuterSubscriber; - }()); - `; - const output = tags.stripIndent` - export var OuterSubscriber = /*@__PURE__*/ (function (_super) { - __extends(OuterSubscriber, _super); - function OuterSubscriber() { - _super.apply(this, arguments); - } - return OuterSubscriber; - }()); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.0 downlevel class with extends', () => { - const input = tags.stripIndent` - var ExtendedClass = (function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - _super.apply(this, arguments); - } - return ExtendedClass; - }(StaticTestCase)); - `; - const output = tags.stripIndent` - var ExtendedClass = /*@__PURE__*/ (function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - _super.apply(this, arguments); - } - return ExtendedClass; - }(StaticTestCase)); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.1 - 2.3 downlevel class with static variable', () => { - const input = tags.stripIndent` - var StaticTestCase = (function () { - function StaticTestCase() { - } - return StaticTestCase; - }()); - StaticTestCase.StaticTest = true; - `; - const output = tags.stripIndent` - var StaticTestCase = /*@__PURE__*/ (function () { - function StaticTestCase() { - } - return StaticTestCase; - }()); - StaticTestCase.StaticTest = true; - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.1 - 2.4 downlevel class with extends', () => { - const input = tags.stripIndent` - var ExtendedClass = (function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - return ExtendedClass; - }(StaticTestCase)); - `; - const output = tags.stripIndent` - var ExtendedClass = /*@__PURE__*/ (function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - return ExtendedClass; - }(StaticTestCase)); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.0 & 2.4 downlevel class with static variable', () => { - const input = tags.stripIndent` - var StaticTestCase = (function () { - function StaticTestCase() { - } - StaticTestCase.StaticTest = true; - return StaticTestCase; - }()); - `; - const output = tags.stripIndent` - var StaticTestCase = /*@__PURE__*/ (function () { - function StaticTestCase() { - } - StaticTestCase.StaticTest = true; - return StaticTestCase; - }()); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.5 - 2.6 downlevel class', () => { - const input = tags.stripIndent` - var BasicTestCase = /** @class */ (function () { - function BasicTestCase() { - } - return BasicTestCase; - }()); - `; - const output = tags.stripIndent` - var BasicTestCase = /** @class */ /*@__PURE__*/ (function () { - function BasicTestCase() { - } - return BasicTestCase; - }()); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.5 - 2.6 renamed downlevel class', () => { - const input = tags.stripIndent` - var ComponentFactoryResolver$1 = /** @class */ (function () { - function ComponentFactoryResolver$$1() { - } - return ComponentFactoryResolver$$1; - }()); - `; - const output = tags.stripIndent` - var ComponentFactoryResolver$1 = /** @class */ /*@__PURE__*/ (function () { - function ComponentFactoryResolver$$1() { - } - return ComponentFactoryResolver$$1; - }()); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.5 - 2.6 renamed downlevel class with extends', () => { - const input = tags.stripIndent` - var NgModuleFactory$1 = /** @class */ (function (_super) { - __extends(NgModuleFactory$$1, _super); - function NgModuleFactory$$1(moduleType) { - var _this = _super.call(this) || this; - _this.moduleType = moduleType; - return _this; - } - NgModuleFactory$$1.prototype.create = function (parentInjector) { - return new NgModuleRef$1(this.moduleType, parentInjector); - }; - return NgModuleFactory$$1; - }(NgModuleFactory)); - `; - const output = tags.stripIndent` - var NgModuleFactory$1 = /** @class */ /*@__PURE__*/ (function (_super) { - __extends(NgModuleFactory$$1, _super); - function NgModuleFactory$$1(moduleType) { - var _this = _super.call(this) || this; - _this.moduleType = moduleType; - return _this; - } - NgModuleFactory$$1.prototype.create = function (parentInjector) { - return new NgModuleRef$1(this.moduleType, parentInjector); - }; - return NgModuleFactory$$1; - }(NgModuleFactory)); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.5 - 2.6 downlevel class with static variable', () => { - const input = tags.stripIndent` - var StaticTestCase = /** @class */ (function () { - function StaticTestCase() { - } - StaticTestCase.StaticTest = true; - return StaticTestCase; - }()); - `; - const output = tags.stripIndent` - var StaticTestCase = /** @class */ /*@__PURE__*/ (function () { - function StaticTestCase() { - } - StaticTestCase.StaticTest = true; - return StaticTestCase; - }()); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('prefix TS 2.5 - 2.6 downlevel class with extends', () => { - const input = tags.stripIndent` - var ExtendedClass = /** @class */ (function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - return ExtendedClass; - }(StaticTestCase)); - `; - const output = tags.stripIndent` - var ExtendedClass = /** @class */ /*@__PURE__*/ (function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - return ExtendedClass; - }(StaticTestCase)); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('works with tslib namespace import', () => { - const input = tags.stripIndent` - var BufferSubscriber = /** @class */ (function (_super) { - tslib_1.__extends(BufferSubscriber, _super); - function BufferSubscriber() { - return _super !== null && _super.apply(this, arguments) || this; - } - return BufferSubscriber; - }(OuterSubscriber)); - `; - const output = tags.stripIndent` - var BufferSubscriber = /** @class */ /*@__PURE__*/ (function (_super) { - tslib_1.__extends(BufferSubscriber, _super); - function BufferSubscriber() { - return _super !== null && _super.apply(this, arguments) || this; - } - return BufferSubscriber; - }(OuterSubscriber)); - `; - - expect(testPrefixClasses(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('fixes the RxJS use case (issue #214)', () => { - const input = ` - var ExtendedClass = /*@__PURE__*/ (/*@__PURE__*/ function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - return ExtendedClass; - }(StaticTestCase)); - - /** - * We need this JSDoc comment for affecting ESDoc. - * @ignore - * @extends {Ignored} - */ - var zip_ZipSubscriber = /*@__PURE__*/ (/*@__PURE__*/ function (_super) { - zip___extends(ZipSubscriber, _super); - function ZipSubscriber(destination, project, values) { - if (values === void 0) { - values = Object.create(null); - } - _super.call(this, destination); - this.iterators = []; - this.active = 0; - this.project = (typeof project === 'function') ? project : null; - this.values = values; - } - return ZipSubscriber; - }(Subscriber)); - `; - const output = ` - var ExtendedClass = /*@__PURE__*/ /*@__PURE__*/ ( /*@__PURE__*/function (_super) { - __extends(ExtendedClass, _super); - function ExtendedClass() { - return _super !== null && _super.apply(this, arguments) || this; - } - return ExtendedClass; - }(StaticTestCase)); - - /** - * We need this JSDoc comment for affecting ESDoc. - * @ignore - * @extends {Ignored} - */ - var zip_ZipSubscriber = /*@__PURE__*/ /*@__PURE__*/ ( /*@__PURE__*/function (_super) { - zip___extends(ZipSubscriber, _super); - function ZipSubscriber(destination, project, values) { - if (values === void 0) { - values = Object.create(null); - } - _super.call(this, destination); - this.iterators = []; - this.active = 0; - this.project = (typeof project === 'function') ? project : null; - this.values = values; - } - return ZipSubscriber; - }(Subscriber)); - `; - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); -}); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts deleted file mode 100644 index d058047284f9..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as ts from 'typescript'; -import { addPureComment, getCleanHelperName, hasPureComment } from '../helpers/ast-utils'; - -export function getPrefixFunctionsTransformer(): ts.TransformerFactory { - return (context: ts.TransformationContext): ts.Transformer => { - const transformer: ts.Transformer = (sf: ts.SourceFile) => { - - const topLevelFunctions = findTopLevelFunctions(sf); - - const visitor: ts.Visitor = (node: ts.Node): ts.Node => { - // Add pure function comment to top level functions. - if (topLevelFunctions.has(node)) { - const newNode = addPureComment(node); - - // Replace node with modified one. - return ts.visitEachChild(newNode, visitor, context); - } - - // Otherwise return node as is. - return ts.visitEachChild(node, visitor, context); - }; - - return ts.visitNode(sf, visitor); - }; - - return transformer; - }; -} - -export function findTopLevelFunctions(parentNode: ts.Node): Set { - const topLevelFunctions = new Set(); - - function cb(node: ts.Node) { - // Stop recursing into this branch if it's a definition construct. - // These are function expression, function declaration, class, or arrow function (lambda). - // The body of these constructs will not execute when loading the module, so we don't - // need to mark function calls inside them as pure. - // Class static initializers in ES2015 are an exception we don't cover. They would need similar - // processing as enums to prevent property setting from causing the class to be retained. - if (ts.isFunctionLike(node) - || ts.isClassLike(node) - || ts.isArrowFunction(node) - || ts.isMethodDeclaration(node) - ) { - return; - } - - let noPureComment = !hasPureComment(node); - let innerNode = node; - while (innerNode && ts.isParenthesizedExpression(innerNode)) { - innerNode = innerNode.expression; - noPureComment = noPureComment && !hasPureComment(innerNode); - } - - if (!innerNode) { - return; - } - - if ((ts.isFunctionExpression(innerNode) || ts.isArrowFunction(innerNode)) - && ts.isParenthesizedExpression(node)) { - // pure functions can be wrapped in parentizes - // we should not add pure comments to this sort of syntax. - // example var foo = (() => x) - return; - } - - if (noPureComment) { - if (ts.isNewExpression(innerNode)) { - topLevelFunctions.add(node); - } else if (ts.isCallExpression(innerNode)) { - let expression: ts.Expression = innerNode.expression; - - if (ts.isIdentifier(expression) && getCleanHelperName(expression.text)) { - return; - } - - while (expression && ts.isParenthesizedExpression(expression)) { - expression = expression.expression; - } - - if (expression) { - if (ts.isFunctionExpression(expression)) { - // Skip IIFE's with arguments - // This could be improved to check if there are any references to variables - if (innerNode.arguments.length === 0) { - topLevelFunctions.add(node); - } - } else { - topLevelFunctions.add(node); - } - } - } - } - - ts.forEachChild(innerNode, cb); - } - - ts.forEachChild(parentNode, cb); - - return topLevelFunctions; -} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts deleted file mode 100644 index ec7bb8fd0fdd..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts +++ /dev/null @@ -1,213 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable-next-line:no-implicit-dependencies -import { tags } from '@angular-devkit/core'; -import { transformJavascript } from '../helpers/transform-javascript'; -import { getPrefixFunctionsTransformer } from './prefix-functions'; - - -const transform = (content: string) => transformJavascript( - { content, getTransforms: [getPrefixFunctionsTransformer] }).content; - -describe('prefix-functions', () => { - const clazz = 'var Clazz = (function () { function Clazz() { } return Clazz; }());'; - - describe('pure functions', () => { - it('adds comment to new calls', () => { - const input = tags.stripIndent` - var newClazz = new Clazz(); - `; - const output = tags.stripIndent` - var newClazz = /*@__PURE__*/ new Clazz(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('adds comment to function calls', () => { - const input = tags.stripIndent` - var newClazz = Clazz(); - `; - const output = tags.stripIndent` - var newClazz = /*@__PURE__*/ Clazz(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('adds comment outside of IIFEs', () => { - const input = tags.stripIndent` - ${clazz} - var ClazzTwo = (function () { function Clazz() { } return Clazz; })(); - `; - const output = tags.stripIndent` - var Clazz = /*@__PURE__*/ (function () { function Clazz() { } return Clazz; }()); - var ClazzTwo = /*@__PURE__*/ (function () { function Clazz() { } return Clazz; })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment when inside function declarations or expressions', () => { - const input = tags.stripIndent` - function funcDecl() { - var newClazz = Clazz(); - var newClazzTwo = new Clazz(); - } - - var funcExpr = function () { - var newClazz = Clazz(); - var newClazzTwo = new Clazz(); - }; - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment to downlevel namespaces', () => { - const input = tags.stripIndent` - function MyFunction() { } - - (function (MyFunction) { - function subFunction() { } - MyFunction.subFunction = subFunction; - })(MyFunction || (MyFunctionn = {})); - - export { MyFunction }; - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment when inside class', () => { - const input = tags.stripIndent` - class Foo { - constructor(e) { - super(e); - } - method() { - var newClazz = new Clazz(); - } - } - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment when inside arrow function', () => { - const input = tags.stripIndent` - export const subscribeToArray = (array) => (subscriber) => { - for (let i = 0, len = array.length; i < len && !subscriber.closed; i++) { - subscriber.next(array[i]); - } - if (!subscriber.closed) { - subscriber.complete(); - } - }; - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment when inside object literal method', () => { - const input = tags.stripIndent` - const literal = { - method() { - var newClazz = new Clazz(); - } - }; - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - it('doesn\'t add comment to downlevel arrow function', () => { - const input = tags.stripIndent` - var populate = (function (props, rawData, entity) { - props.forEach(function (prop) { }); - }); - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment inside arrow function', () => { - const input = tags.stripIndent` - const populate = ((props, rawData, entity) => { - props.forEach(x => x); - }); - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t add comment when inside class expression', () => { - const input = tags.stripIndent` - let Foo = class Foo { - constructor() { - this.isExpandedChange = new EventEmitter(); - } - - set isExpanded(value) { - this.isExpandedChange.emit(value); - } - }; - `; - const output = tags.stripIndent` - ${input} - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it(`doesn't add pure comments to tslib helpers`, () => { - const input = tags.stripIndent` - class LanguageState { - - } - - LanguageState.ctorParameters = () => [ - { type: TranslateService }, - { type: undefined, decorators: [{ type: Inject, args: [LANGUAGE_CONFIG,] }] } - ]; - - __decorate([ - Action(CheckLanguage), - __metadata("design:type", Function), - __metadata("design:paramtypes", [Object]), - __metadata("design:returntype", void 0) - ], LanguageState.prototype, "checkLanguage", null); - `; - - const output = input; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); -}); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/scrub-file.ts b/packages/angular_devkit/build_optimizer/src/transforms/scrub-file.ts deleted file mode 100644 index afdbdee7ad01..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/scrub-file.ts +++ /dev/null @@ -1,604 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as ts from 'typescript'; -import { collectDeepNodes } from '../helpers/ast-utils'; - -export function testScrubFile(content: string) { - const markers = [ - 'decorators', - '__decorate', - 'propDecorators', - 'ctorParameters', - 'ɵsetClassMetadata', - ]; - - return markers.some((marker) => content.includes(marker)); -} - -export function createScrubFileTransformerFactory( - isAngularCoreFile: boolean, -): (program?: ts.Program) => ts.TransformerFactory { - return (program) => scrubFileTransformer(program, isAngularCoreFile); -} - -function scrubFileTransformer( - program: ts.Program | undefined, - isAngularCoreFile: boolean, -) { - if (!program) { - throw new Error('scrubFileTransformer requires a TypeScript Program.'); - } - const checker = program.getTypeChecker(); - - return (context: ts.TransformationContext): ts.Transformer => { - const transformer: ts.Transformer = (sf: ts.SourceFile) => { - const ngMetadata = findAngularMetadata(sf, isAngularCoreFile); - const tslibImports = findTslibImports(sf); - - const nodes: ts.Node[] = []; - ts.forEachChild(sf, checkNodeForDecorators); - - function checkNodeForDecorators(node: ts.Node): void { - if (!ts.isExpressionStatement(node)) { - return ts.forEachChild(node, checkNodeForDecorators); - } - - const exprStmt = node as ts.ExpressionStatement; - const iife = getIifeStatement(exprStmt)?.expression; - // Do checks that don't need the typechecker first and bail early. - if (isCtorParamsAssignmentExpression(exprStmt)) { - nodes.push(node); - } else if (iife && isIvyPrivateCallExpression(iife, 'ɵsetClassMetadata')) { - nodes.push(node); - } else if ( - iife && - ts.isBinaryExpression(iife) && - isIvyPrivateCallExpression(iife.right, 'ɵsetClassMetadata') - ) { - nodes.push(node); - } else if (isDecoratorAssignmentExpression(exprStmt)) { - nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker)); - } else if ( - isDecorateAssignmentExpression(exprStmt, tslibImports, checker) || - isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker) - ) { - nodes.push(...pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker)); - } else if (isPropDecoratorAssignmentExpression(exprStmt)) { - nodes.push(...pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker)); - } - } - - const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult => { - // Check if node is a statement to be dropped. - if (nodes.find((n) => n === node)) { - return undefined; - } - - // Otherwise return node as is. - return ts.visitEachChild(node, visitor, context); - }; - - return ts.visitNode(sf, visitor); - }; - - return transformer; - }; -} - -export function expect(node: ts.Node, kind: ts.SyntaxKind): T { - if (node.kind !== kind) { - throw new Error('Invalid node type.'); - } - - return node as T; -} - -function findAngularMetadata(node: ts.Node, isAngularCoreFile: boolean): ts.Node[] { - let specs: ts.Node[] = []; - // Find all specifiers from imports of `@angular/core`. - ts.forEachChild(node, (child) => { - if (child.kind === ts.SyntaxKind.ImportDeclaration) { - const importDecl = child as ts.ImportDeclaration; - if (isAngularCoreImport(importDecl, isAngularCoreFile)) { - specs.push(...collectDeepNodes(importDecl, ts.SyntaxKind.ImportSpecifier)); - } - } - }); - - // If the current module is a Angular core file, we also consider all declarations in it to - // potentially be Angular metadata. - if (isAngularCoreFile) { - const localDecl = findAllDeclarations(node); - specs = specs.concat(localDecl); - } - - return specs; -} - -function findAllDeclarations(node: ts.Node): ts.VariableDeclaration[] { - const nodes: ts.VariableDeclaration[] = []; - ts.forEachChild(node, (child) => { - if (child.kind === ts.SyntaxKind.VariableStatement) { - const vStmt = child as ts.VariableStatement; - vStmt.declarationList.declarations.forEach((decl) => { - if (decl.name.kind !== ts.SyntaxKind.Identifier) { - return; - } - nodes.push(decl); - }); - } - }); - - return nodes; -} - -function isAngularCoreImport(node: ts.ImportDeclaration, isAngularCoreFile: boolean): boolean { - if (!(node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier))) { - return false; - } - const importText = node.moduleSpecifier.text; - - // Imports to `@angular/core` are always core imports. - if (importText === '@angular/core') { - return true; - } - - // Relative imports from a Angular core file are also core imports. - if (isAngularCoreFile && importText.startsWith('.')) { - return true; - } - - return false; -} - -// Check if assignment is `Clazz.decorators = [...];`. -function isDecoratorAssignmentExpression(exprStmt: ts.ExpressionStatement): boolean { - if (!isAssignmentExpressionTo(exprStmt, 'decorators')) { - return false; - } - const expr = exprStmt.expression as ts.BinaryExpression; - if (!ts.isArrayLiteralExpression(expr.right)) { - return false; - } - - return true; -} - -// Check if assignment is `Clazz = __decorate([...], Clazz)`. -function isDecorateAssignmentExpression( - exprStmt: ts.ExpressionStatement, - tslibImports: ts.NamedImports[], - checker: ts.TypeChecker, -): boolean { - if (!ts.isBinaryExpression(exprStmt.expression)) { - return false; - } - const expr = exprStmt.expression; - if (!ts.isIdentifier(expr.left)) { - return false; - } - const classIdent = expr.left; - let callExpr: ts.CallExpression; - - if (ts.isCallExpression(expr.right)) { - callExpr = expr.right; - } else if (ts.isBinaryExpression(expr.right)) { - // `Clazz = Clazz_1 = __decorate([...], Clazz)` can be found when there are static property - // accesses. - const innerExpr = expr.right; - if (!ts.isIdentifier(innerExpr.left) || !ts.isCallExpression(innerExpr.right)) { - return false; - } - callExpr = innerExpr.right; - } else { - return false; - } - - if (!isTslibHelper(callExpr, '__decorate', tslibImports, checker)) { - return false; - } - - if (callExpr.arguments.length !== 2) { - return false; - } - - const classArg = callExpr.arguments[1]; - if (!ts.isIdentifier(classArg)) { - return false; - } - - if (classIdent.text !== classArg.text) { - return false; - } - if (!ts.isArrayLiteralExpression(callExpr.arguments[0])) { - return false; - } - - return true; -} - -// Check if expression is `__decorate([smt, __metadata("design:type", Object)], ...)`. -function isAngularDecoratorExpression( - exprStmt: ts.ExpressionStatement, - ngMetadata: ts.Node[], - tslibImports: ts.NamedImports[], - checker: ts.TypeChecker, -): boolean { - - if (!ts.isCallExpression(exprStmt.expression)) { - return false; - } - const callExpr = exprStmt.expression; - if (!isTslibHelper(callExpr, '__decorate', tslibImports, checker)) { - return false; - } - if (callExpr.arguments.length !== 4) { - return false; - } - const decorateArray = callExpr.arguments[0]; - if (!ts.isArrayLiteralExpression(decorateArray)) { - return false; - } - // Check first array entry for Angular decorators. - if (decorateArray.elements.length === 0 || !ts.isCallExpression(decorateArray.elements[0])) { - return false; - } - - return decorateArray.elements.some(decoratorCall => { - if (!ts.isCallExpression(decoratorCall) || !ts.isIdentifier(decoratorCall.expression)) { - return false; - } - - const decoratorId = decoratorCall.expression; - - return identifierIsMetadata(decoratorId, ngMetadata, checker); - }); -} - -// Check if assignment is `Clazz.propDecorators = [...];`. -function isPropDecoratorAssignmentExpression(exprStmt: ts.ExpressionStatement): boolean { - if (!isAssignmentExpressionTo(exprStmt, 'propDecorators')) { - return false; - } - const expr = exprStmt.expression as ts.BinaryExpression; - if (expr.right.kind !== ts.SyntaxKind.ObjectLiteralExpression) { - return false; - } - - return true; -} - -// Check if assignment is `Clazz.ctorParameters = [...];`. -function isCtorParamsAssignmentExpression(exprStmt: ts.ExpressionStatement): boolean { - if (!isAssignmentExpressionTo(exprStmt, 'ctorParameters')) { - return false; - } - const expr = exprStmt.expression as ts.BinaryExpression; - if (expr.right.kind !== ts.SyntaxKind.FunctionExpression - && expr.right.kind !== ts.SyntaxKind.ArrowFunction - ) { - return false; - } - - return true; -} - -function isAssignmentExpressionTo(exprStmt: ts.ExpressionStatement, name: string) { - if (!ts.isBinaryExpression(exprStmt.expression)) { - return false; - } - const expr = exprStmt.expression; - if (!ts.isPropertyAccessExpression(expr.left)) { - return false; - } - const propAccess = expr.left; - if (propAccess.name.text !== name) { - return false; - } - if (!ts.isIdentifier(propAccess.expression)) { - return false; - } - if (expr.operatorToken.kind !== ts.SyntaxKind.FirstAssignment) { - return false; - } - - return true; -} - -// Each Ivy private call expression is inside an IIFE -function getIifeStatement(exprStmt: ts.ExpressionStatement): null | ts.ExpressionStatement { - const expression = exprStmt.expression; - if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 0) { - return null; - } - - const parenExpr = expression; - if (!ts.isParenthesizedExpression(parenExpr.expression)) { - return null; - } - - const funExpr = parenExpr.expression.expression; - if (!ts.isFunctionExpression(funExpr)) { - return null; - } - - const innerStmts = funExpr.body.statements; - if (innerStmts.length !== 1) { - return null; - } - - const innerExprStmt = innerStmts[0]; - if (!ts.isExpressionStatement(innerExprStmt)) { - return null; - } - - return innerExprStmt; -} - -function isIvyPrivateCallExpression(expression: ts.Expression, name: string) { - // Now we're in the IIFE and have the inner expression statement. We can check if it matches - // a private Ivy call. - if (!ts.isCallExpression(expression)) { - return false; - } - - const propAccExpr = expression.expression; - if (!ts.isPropertyAccessExpression(propAccExpr)) { - return false; - } - - if (propAccExpr.name.text != name) { - return false; - } - - return true; -} - -// Remove Angular decorators from`Clazz.decorators = [...];`, or expression itself if all are -// removed. -function pickDecorationNodesToRemove( - exprStmt: ts.ExpressionStatement, - ngMetadata: ts.Node[], - checker: ts.TypeChecker, -): ts.Node[] { - - const expr = expect(exprStmt.expression, ts.SyntaxKind.BinaryExpression); - const literal = expect(expr.right, - ts.SyntaxKind.ArrayLiteralExpression); - if (!literal.elements.every(elem => ts.isObjectLiteralExpression(elem))) { - return []; - } - const elements = literal.elements as ts.NodeArray; - const ngDecorators = elements.filter((elem) => isAngularDecorator(elem, ngMetadata, checker)); - - return (elements.length > ngDecorators.length) ? ngDecorators : [exprStmt]; -} - -// Remove Angular decorators from `Clazz = __decorate([...], Clazz)`, or expression itself if all -// are removed. -function pickDecorateNodesToRemove( - exprStmt: ts.ExpressionStatement, - tslibImports: ts.NamedImports[], - ngMetadata: ts.Node[], - checker: ts.TypeChecker, -): ts.Node[] { - let callExpr: ts.CallExpression | undefined; - if (ts.isCallExpression(exprStmt.expression)) { - callExpr = exprStmt.expression; - } else if (ts.isBinaryExpression(exprStmt.expression)) { - const expr = exprStmt.expression; - if (ts.isCallExpression(expr.right)) { - callExpr = expr.right; - } else if (ts.isBinaryExpression(expr.right) && ts.isCallExpression(expr.right.right)) { - callExpr = expr.right.right; - } - } - - if (!callExpr) { - return []; - } - - const arrLiteral = expect(callExpr.arguments[0], - ts.SyntaxKind.ArrayLiteralExpression); - - if (!arrLiteral.elements.every((elem) => ts.isCallExpression(elem))) { - return []; - } - const elements = arrLiteral.elements as ts.NodeArray; - const ngDecoratorCalls = elements.filter((el) => { - if (!ts.isIdentifier(el.expression)) { - return false; - } - - return identifierIsMetadata(el.expression, ngMetadata, checker); - }); - - // Remove __metadata calls of type 'design:paramtypes'. - const metadataCalls = elements.filter((el) => { - if (!isTslibHelper(el, '__metadata', tslibImports, checker)) { - return false; - } - - if (el.arguments.length < 2 || !ts.isStringLiteral(el.arguments[0])) { - return false; - } - - return true; - }); - // Remove all __param calls. - const paramCalls = elements.filter((el) => { - if (!isTslibHelper(el, '__param', tslibImports, checker)) { - return false; - } - - if (el.arguments.length !== 2 || !ts.isNumericLiteral(el.arguments[0])) { - return false; - } - - return true; - }); - - if (ngDecoratorCalls.length === 0) { - return []; - } - - const callCount = ngDecoratorCalls.length + metadataCalls.length + paramCalls.length; - - // If all decorators are metadata decorators then return the whole `Class = __decorate([...])'` - // statement so that it is removed in entirety. - // If not then only remove the Angular decorators. - // The metadata and param calls may be used by the non-Angular decorators. - return (elements.length === callCount) ? [exprStmt] : ngDecoratorCalls; -} - -// Remove Angular decorators from`Clazz.propDecorators = [...];`, or expression itself if all -// are removed. -function pickPropDecorationNodesToRemove( - exprStmt: ts.ExpressionStatement, - ngMetadata: ts.Node[], - checker: ts.TypeChecker, -): ts.Node[] { - - const expr = expect(exprStmt.expression, ts.SyntaxKind.BinaryExpression); - const literal = expect(expr.right, - ts.SyntaxKind.ObjectLiteralExpression); - if (!literal.properties.every(elem => ts.isPropertyAssignment(elem) - && ts.isArrayLiteralExpression(elem.initializer))) { - return []; - } - const assignments = literal.properties as ts.NodeArray; - // Consider each assignment individually. Either the whole assignment will be removed or - // a particular decorator within will. - const toRemove = assignments - .map((assign) => { - const decorators = - expect(assign.initializer, - ts.SyntaxKind.ArrayLiteralExpression).elements; - if (!decorators.every((el) => ts.isObjectLiteralExpression(el))) { - return []; - } - const decsToRemove = decorators.filter((expression) => { - const lit = expect(expression, - ts.SyntaxKind.ObjectLiteralExpression); - - return isAngularDecorator(lit, ngMetadata, checker); - }); - if (decsToRemove.length === decorators.length) { - return [assign]; - } - - return decsToRemove; - }) - .reduce((accum, toRm) => accum.concat(toRm), [] as ts.Node[]); - // If every node to be removed is a property assignment (full property's decorators) and - // all properties are accounted for, remove the whole assignment. Otherwise, remove the - // nodes which were marked as safe. - if (toRemove.length === assignments.length && toRemove.every((node) => ts.isPropertyAssignment(node))) { - return [exprStmt]; - } - - return toRemove; -} - -function isAngularDecorator( - literal: ts.ObjectLiteralExpression, - ngMetadata: ts.Node[], - checker: ts.TypeChecker, -): boolean { - - const types = literal.properties.filter(isTypeProperty); - if (types.length !== 1) { - return false; - } - const assign = expect(types[0], ts.SyntaxKind.PropertyAssignment); - if (!ts.isIdentifier(assign.initializer)) { - return false; - } - const id = assign.initializer; - const res = identifierIsMetadata(id, ngMetadata, checker); - - return res; -} - -function isTypeProperty(prop: ts.ObjectLiteralElement): boolean { - if (!ts.isPropertyAssignment(prop)) { - return false; - } - - if (!ts.isIdentifier(prop.name)) { - return false; - } - - return prop.name.text === 'type'; -} - -// Check if an identifier is part of the known Angular Metadata. -function identifierIsMetadata( - id: ts.Identifier, - metadata: ts.Node[], - checker: ts.TypeChecker, -): boolean { - const symbol = checker.getSymbolAtLocation(id); - if (!symbol || !symbol.declarations || !symbol.declarations.length) { - return false; - } - - return symbol - .declarations - .some((spec) => metadata.includes(spec)); -} - -// Find all named imports for `tslib`. -function findTslibImports(node: ts.Node): ts.NamedImports[] { - const imports: ts.NamedImports[] = []; - - ts.forEachChild(node, child => { - if ( - ts.isImportDeclaration(child) && - child.moduleSpecifier && - ts.isStringLiteral(child.moduleSpecifier) && - child.moduleSpecifier.text === 'tslib' && - child.importClause?.namedBindings && - ts.isNamedImports(child.importClause?.namedBindings) - ) { - imports.push(child.importClause.namedBindings); - } - }); - - return imports; -} - -// Check if a function call is a tslib helper. -function isTslibHelper( - callExpr: ts.CallExpression, - helper: string, - tslibImports: ts.NamedImports[], - checker: ts.TypeChecker, -) { - if (!ts.isIdentifier(callExpr.expression) || callExpr.expression.text !== helper) { - return false; - } - - const symbol = checker.getSymbolAtLocation(callExpr.expression); - if (!symbol?.declarations?.length) { - return false; - } - - for (const dec of symbol.declarations) { - if (ts.isImportSpecifier(dec) && tslibImports.some(name => name.elements.includes(dec))) { - return true; - } - - // Handle inline helpers `var __decorate = (this...` - if (ts.isVariableDeclaration(dec)) { - return true; - } - } - - return false; -} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts deleted file mode 100644 index 80c045ef430c..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts +++ /dev/null @@ -1,900 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -// tslint:disable-next-line:no-implicit-dependencies -import { tags } from '@angular-devkit/core'; -import { transformJavascript } from '../helpers/transform-javascript'; -import { - createScrubFileTransformerFactory, - testScrubFile, -} from './scrub-file'; - - -const transform = (content: string) => transformJavascript( - { content, getTransforms: [createScrubFileTransformerFactory(false)], typeCheck: true }).content; -const transformCore = (content: string) => transformJavascript( - { content, getTransforms: [createScrubFileTransformerFactory(true)], typeCheck: true }).content; - -describe('scrub-file', () => { - const clazz = 'var Clazz = (function () { function Clazz() { } return Clazz; }());'; - - describe('decorators', () => { - it('removes top-level Angular decorators', () => { - const output = tags.stripIndent` - import { Injectable } from '@angular/core'; - ${clazz} - `; - const input = tags.stripIndent` - ${output} - Clazz.decorators = [ { type: Injectable } ]; - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes nested Angular decorators', () => { - const output = tags.stripIndent` - import { Injectable } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { Injectable } from '@angular/core'; - var Clazz = (function () { - function Clazz() {} - Clazz.decorators = [ { type: Injectable } ]; - return Clazz; - }()); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t remove non Angular decorators', () => { - const input = tags.stripIndent` - import { Injectable } from 'another-lib'; - ${clazz} - Clazz.decorators = [{ type: Injectable }]; - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); - }); - - it('leaves non-Angular decorators in mixed arrays', () => { - const input = tags.stripIndent` - import { Injectable } from '@angular/core'; - import { NotInjectable } from 'another-lib'; - ${clazz} - Clazz.decorators = [{ type: Injectable }, { type: NotInjectable }]; - `; - const output = tags.stripIndent` - import { Injectable } from '@angular/core'; - import { NotInjectable } from 'another-lib'; - ${clazz} - Clazz.decorators = [{ type: NotInjectable }]; - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('__decorate', () => { - it('removes Angular decorators calls in __decorate', () => { - const output = tags.stripIndent` - import { __decorate } from "tslib"; - import { Component, Injectable } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { __decorate } from "tslib"; - import { Component, Injectable } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - Injectable(), - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - ], Clazz); - return Clazz; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes Angular decorators calls when __decorate is inlined', () => { - const output = tags.stripIndent` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - - import { Component, Injectable } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - `; - - const input = tags.stripIndent` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - - import { Component, Injectable } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - Injectable(), - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - ], Clazz); - return Clazz; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes constructor parameter metadata in __decorate', () => { - const output = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Component, ElementRef } from '@angular/core'; - import { LibService } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Component, ElementRef } from '@angular/core'; - import { LibService } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }), - __metadata("design:paramtypes", [ElementRef, LibService]) - ], Clazz); - return Clazz; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes constructor parameter metadata when static properties are present', () => { - const output = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Injectable } from '@angular/core'; - import { Logger } from 'another-lib'; - var GaService = (function () { - function GaService(logger) { - this.logger = logger; - } - GaService_1 = GaService; - GaService.prototype.initializeGa = function () { - console.log(GaService_1.initializeDelay); - }; - GaService.initializeDelay = 1000; - return GaService; - var GaService_1; - }()); - `; - const input = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Injectable } from '@angular/core'; - import { Logger } from 'another-lib'; - var GaService = (function () { - function GaService(logger) { - this.logger = logger; - } - GaService_1 = GaService; - GaService.prototype.initializeGa = function () { - console.log(GaService_1.initializeDelay); - }; - GaService.initializeDelay = 1000; - GaService = GaService_1 = __decorate([ - Injectable(), - __metadata("design:paramtypes", [Logger]) - ], GaService); - return GaService; - var GaService_1; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes only Angular decorators calls in __decorate', () => { - const output = tags.stripIndent` - import { __decorate } from "tslib"; - import { Component } from '@angular/core'; - import { NotComponent } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - NotComponent() - ], Clazz); - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { __decorate } from "tslib"; - import { Component } from '@angular/core'; - import { NotComponent } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - NotComponent(), - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - ], Clazz); - return Clazz; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('recognizes tslib as well', () => { - const input = tags.stripIndent` - import { __decorate } from "tslib"; - import { Component } from '@angular/core'; - import { NotComponent } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - NotComponent(), - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - ], Clazz); - return Clazz; - }()); - - var Clazz2 = (function () { - function Clazz2() { } - Clazz2 = __decorate([ - NotComponent(), - Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] - }) - ], Clazz2); - return Clazz2; - }()); - `; - const output = tags.stripIndent` - import { __decorate } from "tslib"; - import { Component } from '@angular/core'; - import { NotComponent } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - Clazz = __decorate([ - NotComponent() - ], Clazz); - return Clazz; - }()); - - var Clazz2 = (function () { - function Clazz2() { } - Clazz2 = __decorate([ - NotComponent() - ], Clazz2); - return Clazz2; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('recognizes decorator imports in Angular core', () => { - const input = tags.stripIndent` - import { __decorate } from "tslib"; - import { Injectable } from './di'; - var Console = /** @class */ (function () { - function Console() { - } - Console.prototype.log = function (message) { - console.log(message); - }; - Console.prototype.warn = function (message) { - console.warn(message); - }; - Console = __decorate([ - Injectable() - ], Console); - return Console; - }()); - export { Console }; - `; - const output = tags.stripIndent` - import { __decorate } from "tslib"; - import { Injectable } from './di'; - var Console = /** @class */ (function () { - function Console() { - } - Console.prototype.log = function (message) { - console.log(message); - }; - Console.prototype.warn = function (message) { - console.warn(message); - }; - return Console; - }()); - export { Console }; - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes Angular decorators calls in __decorate when no __metadata is present', () => { - const input = tags.stripIndent` - import { __decorate } from 'tslib'; - import { Component, ElementRef, ContentChild} from '@angular/core'; - - var FooBarComponent = /** @class */ (function () { - function FooBarComponent(elementRef) { - this.elementRef = elementRef; - this.inlineButtons = []; - this.menuButtons = []; - } - FooBarComponent.ctorParameters = function () { return [ - { type: ElementRef } - ]; }; - __decorate([ - ContentChild('heading', { read: ElementRef, static: true }) - ], FooBarComponent.prototype, "buttons", void 0); - FooBarComponent = __decorate([ - Component({ - selector: 'custom-foo-bar', - template: '', - styles: [] - }) - ], FooBarComponent); - return FooBarComponent; - }()); - `; - - const output = tags.stripIndent` - import { __decorate } from 'tslib'; - import { Component, ElementRef, ContentChild } from '@angular/core'; - - var FooBarComponent = /** @class */ (function () { - function FooBarComponent(elementRef) { - this.elementRef = elementRef; - this.inlineButtons = []; - this.menuButtons = []; - } - - return FooBarComponent; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes only Angular decorators calls in __decorate when no __metadata is present', () => { - const input = tags.stripIndent` - import { __decorate } from 'tslib'; - import { Component, ElementRef, ContentChild} from '@angular/core'; - import { NotComponent } from 'another-lib'; - - var FooBarComponent = /** @class */ (function () { - function FooBarComponent(elementRef) { - this.elementRef = elementRef; - this.inlineButtons = []; - this.menuButtons = []; - } - FooBarComponent.ctorParameters = function () { return [ - { type: ElementRef } - ]; }; - __decorate([ - NotComponent(), - ContentChild('heading', { read: ElementRef, static: true }) - ], FooBarComponent.prototype, "buttons", void 0); - FooBarComponent = __decorate([ - NotComponent(), - Component({ - selector: 'custom-foo-bar', - template: '', - styles: [] - }) - ], FooBarComponent); - return FooBarComponent; - }()); - `; - - const output = tags.stripIndent` - import { __decorate } from 'tslib'; - import { Component, ElementRef, ContentChild } from '@angular/core'; - import { NotComponent } from 'another-lib'; - - var FooBarComponent = /** @class */ (function () { - function FooBarComponent(elementRef) { - this.elementRef = elementRef; - this.inlineButtons = []; - this.menuButtons = []; - } - __decorate([ - NotComponent() - ], FooBarComponent.prototype, "buttons", void 0); - - FooBarComponent = __decorate([ NotComponent() ], FooBarComponent); return FooBarComponent; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('__metadata', () => { - it('removes Angular decorators metadata', () => { - const output = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Input, Output, EventEmitter, HostListener } from '@angular/core'; - var Clazz = (function () { - function Clazz() { - this.change = new EventEmitter(); - } - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Input, Output, EventEmitter, HostListener } from '@angular/core'; - import { NotInput } from 'another-lib'; - var Clazz = (function () { - function Clazz() { - this.change = new EventEmitter(); - } - __decorate([ - Input(), - __metadata("design:type", Object) - ], Clazz.prototype, "selected", void 0); - __decorate([ - Output(), - __metadata("design:type", Object) - ], Clazz.prototype, "change", void 0); - __decorate([ - HostListener('document:keydown.escape'), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", void 0) - ], Clazz.prototype, "onKeyDown", null); - return Clazz; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes only Angular decorator metadata', () => { - const output = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Input } from '@angular/core'; - import { NotInput } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - __decorate([ - NotInput(), - __metadata("design:type", Object) - ], Clazz.prototype, "other", void 0); - Clazz.prototype.myMethod = function () { return 'bar'; }; - __decorate([ - myDecorator(), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", void 0) - ], MyClass.prototype, "myMethod", null); - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Input } from '@angular/core'; - import { NotInput } from 'another-lib'; - var Clazz = (function () { - function Clazz() { } - __decorate([ - Input(), - __metadata("design:type", Object) - ], Clazz.prototype, "selected", void 0); - __decorate([ - NotInput(), - __metadata("design:type", Object) - ], Clazz.prototype, "other", void 0); - Clazz.prototype.myMethod = function () { return 'bar'; }; - __decorate([ - myDecorator(), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", void 0) - ], MyClass.prototype, "myMethod", null); - return Clazz; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('recognizes tslib as well', () => { - const input = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Input } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - __decorate([ - Input(), - __metadata("design:type", Object) - ], Clazz.prototype, "selected", void 0); - return Clazz; - }()); - - var Clazz2 = (function () { - function Clazz2() { } - __decorate([ - Input(), - __metadata("design:type", Object) - ], Clazz.prototype, "selected", void 0); - return Clazz2; - }()); - `; - const output = tags.stripIndent` - import { __decorate, __metadata } from "tslib"; - import { Input } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - - var Clazz2 = (function () { - function Clazz2() { } - return Clazz2; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('__param', () => { - it('removes all constructor parameters and their type metadata with only Angular decorators', () => { - const output = tags.stripIndent` - import { Component } from '@angular/core'; - import { __decorate, __param, __metadata } from "tslib"; - var MyClass = /** @class */ (function () { - function MyClass(myParam) { - this.myProp = 'foo'; - } - return MyClass; - }()); - `; - const input = tags.stripIndent` - import { Component } from '@angular/core'; - import { __decorate, __param, __metadata } from "tslib"; - var MyClass = /** @class */ (function () { - function MyClass(myParam) { - this.myProp = 'foo'; - } - MyClass = __decorate([ - Component(), - __param(0, Component()), - __metadata("design:paramtypes", [Number]) - ], MyClass); - return MyClass; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('keeps all constructor parameters and their type metadata with only custom decorators', () => { - const output = tags.stripIndent` - import { __decorate, __param, __metadata } from "tslib"; - var MyClass = /** @class */ (function () { - function MyClass(myParam) { - this.myProp = 'foo'; - } - MyClass = __decorate([ - myDecorator(), - __param(0, myDecorator()), - __metadata("design:paramtypes", [Number]) - ], MyClass); - return MyClass; - }()); - var MyOtherClass = /** @class */ (function () { - function MyOtherClass(myParam) { - this.myProp = 'bar'; - } - MyOtherClass = __decorate([ - __metadata("design:paramtypes", [Number]) - ], MyOtherClass); - return MyOtherClass; - }()); - `; - const input = tags.stripIndent` - import { __decorate, __param, __metadata } from "tslib"; - var MyClass = /** @class */ (function () { - function MyClass(myParam) { - this.myProp = 'foo'; - } - MyClass = __decorate([ - myDecorator(), - __param(0, myDecorator()), - __metadata("design:paramtypes", [Number]) - ], MyClass); - return MyClass; - }()); - var MyOtherClass = /** @class */ (function () { - function MyOtherClass(myParam) { - this.myProp = 'bar'; - } - MyOtherClass = __decorate([ - __metadata("design:paramtypes", [Number]) - ], MyOtherClass); - return MyOtherClass; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('keeps all constructor parameters and their type metadata with custom & Angular decorators', () => { - const output = tags.stripIndent` - import { Component } from '@angular/core'; - import { __decorate, __param, __metadata } from "tslib"; - var MyClass = /** @class */ (function () { - function MyClass(myParam) { - this.myProp = 'foo'; - } - MyClass = __decorate([ - myDecorator(), - __param(0, myDecorator()), - __metadata("design:paramtypes", [Number]) - ], MyClass); - return MyClass; - }()); - `; - const input = tags.stripIndent` - import { Component } from '@angular/core'; - import { __decorate, __param, __metadata } from "tslib"; - var MyClass = /** @class */ (function () { - function MyClass(myParam) { - this.myProp = 'foo'; - } - MyClass = __decorate([ - Component(), - myDecorator(), - __param(0, myDecorator()), - __metadata("design:paramtypes", [Number]) - ], MyClass); - return MyClass; - }()); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('propDecorators', () => { - it('removes top-level Angular propDecorators', () => { - const output = tags.stripIndent` - import { Input } from '@angular/core'; - ${clazz} - `; - const input = tags.stripIndent` - ${output} - Clazz.propDecorators = { 'ngIf': [{ type: Input }] } - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes nested Angular propDecorators', () => { - const output = tags.stripIndent` - import { Input } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { Input } from '@angular/core'; - var Clazz = (function () { - function Clazz() {} - Clazz.propDecorators = { 'ngIf': [{ type: Input }] }; - return Clazz; - }()); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('doesn\'t remove non Angular propDecorators', () => { - const input = tags.stripIndent` - import { Input } from 'another-lib'; - ${clazz} - Clazz.propDecorators = { 'ngIf': [{ type: Input }] }; - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); - }); - - it('leaves non-Angular propDecorators in mixed arrays', () => { - const output = tags.stripIndent` - import { Input } from '@angular/core'; - import { NotInput } from 'another-lib'; - ${clazz} - Clazz.propDecorators = { - 'notNgIf': [{ type: NotInput }] - }; - `; - const input = tags.stripIndent` - import { Input } from '@angular/core'; - import { NotInput } from 'another-lib'; - ${clazz} - Clazz.propDecorators = { - 'ngIf': [{ type: Input }], - 'notNgIf': [{ type: NotInput }] - }; - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('ctorParameters', () => { - it('removes empty constructor parameters', () => { - const output = tags.stripIndent` - ${clazz} - `; - const input = tags.stripIndent` - ${output} - Clazz.ctorParameters = function () { return []; }; - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes non-empty top-level style constructor parameters', () => { - const output = tags.stripIndent` - ${clazz} - `; - const input = tags.stripIndent` - ${clazz} - Clazz.ctorParameters = function () { return [{type: Injector}]; }; - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - - it('removes top-level Angular constructor parameters in es2015', () => { - const output = tags.stripIndent` - class Clazz extends BaseClazz { constructor(e) { super(e); } } - `; - const input = tags.stripIndent` - ${output} - Clazz.ctorParameters = () => [ { type: Injectable } ]; - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes nested constructor parameters', () => { - const output = tags.stripIndent` - import { Injector } from '@angular/core'; - var Clazz = (function () { - function Clazz() { } - return Clazz; - }()); - `; - const input = tags.stripIndent` - import { Injector } from '@angular/core'; - var Clazz = (function () { - function Clazz() {} - Clazz.ctorParameters = function () { return [{type: Injector}]; }; - return Clazz; - }()); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('Ivy', () => { - it('removes ɵsetClassMetadata call with pure annotation', () => { - const output = tags.stripIndent` - import { Component } from '@angular/core'; - ${clazz} - `; - const input = tags.stripIndent` - ${output} - /*@__PURE__*/ (function () { i0.ɵsetClassMetadata(Clazz, [{ - type: Component, - args: [{ - selector: 'app-lazy', - template: 'very lazy', - styles: [] - }] - }], null, null); })(); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('removes ɵsetClassMetadata call', () => { - const output = tags.stripIndent` - import { Component } from '@angular/core'; - ${clazz} - `; - const input = tags.stripIndent` - ${output} - (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵsetClassMetadata(Clazz, [{ - type: Component, - args: [{ - selector: 'app-lazy', - template: 'very lazy', - styles: [] - }] - }], null, null); })(); - `; - - expect(testScrubFile(input)).toBeTruthy(); - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); -}); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts deleted file mode 100644 index 2105cc6b6186..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums.ts +++ /dev/null @@ -1,473 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as ts from 'typescript'; -import { addPureComment } from '../helpers/ast-utils'; - -function isBlockLike(node: ts.Node): node is ts.BlockLike { - return node.kind === ts.SyntaxKind.Block - || node.kind === ts.SyntaxKind.ModuleBlock - || node.kind === ts.SyntaxKind.CaseClause - || node.kind === ts.SyntaxKind.DefaultClause - || node.kind === ts.SyntaxKind.SourceFile; -} - -export function getWrapEnumsTransformer(): ts.TransformerFactory { - return (context: ts.TransformationContext): ts.Transformer => { - const transformer: ts.Transformer = sf => { - const result = visitBlockStatements(sf.statements, context); - - return context.factory.updateSourceFile(sf, ts.setTextRange(result, sf.statements)); - }; - - return transformer; - }; -} - -function visitBlockStatements( - statements: ts.NodeArray, - context: ts.TransformationContext, -): ts.NodeArray { - // copy of statements to modify; lazy initialized - let updatedStatements: Array | undefined; - const nodeFactory = context.factory; - - const visitor: ts.Visitor = (node) => { - if (isBlockLike(node)) { - let result = visitBlockStatements(node.statements, context); - if (result === node.statements) { - return node; - } - result = ts.setTextRange(result, node.statements); - switch (node.kind) { - case ts.SyntaxKind.Block: - return nodeFactory.updateBlock(node, result); - case ts.SyntaxKind.ModuleBlock: - return nodeFactory.updateModuleBlock(node, result); - case ts.SyntaxKind.CaseClause: - return nodeFactory.updateCaseClause(node, node.expression, result); - case ts.SyntaxKind.DefaultClause: - return nodeFactory.updateDefaultClause(node, result); - default: - return node; - } - } else { - return node; - } - }; - - // 'oIndex' is the original statement index; 'uIndex' is the updated statement index - for (let oIndex = 0, uIndex = 0; oIndex < statements.length - 1; oIndex++, uIndex++) { - const currentStatement = statements[oIndex]; - let newStatement: ts.Statement[] | undefined; - let oldStatementsLength = 0; - - // these can't contain an enum declaration - if (currentStatement.kind === ts.SyntaxKind.ImportDeclaration) { - continue; - } - - // enum declarations must: - // * not be last statement - // * be a variable statement - // * have only one declaration - // * have an identifer as a declaration name - - // ClassExpression declarations must: - // * not be last statement - // * be a variable statement - // * have only one declaration - // * have an ClassExpression or BinaryExpression and a right - // of kind ClassExpression as a initializer - if (ts.isVariableStatement(currentStatement) - && currentStatement.declarationList.declarations.length === 1) { - - const variableDeclaration = currentStatement.declarationList.declarations[0]; - const initializer = variableDeclaration.initializer; - if (ts.isIdentifier(variableDeclaration.name)) { - const name = variableDeclaration.name.text; - - if (!initializer) { - const iife = findEnumIife(name, statements[oIndex + 1]); - if (iife) { - // update IIFE and replace variable statement and old IIFE - oldStatementsLength = 2; - newStatement = updateEnumIife( - nodeFactory, - currentStatement, - iife[0], - iife[1], - ); - // skip IIFE statement - oIndex++; - } - } else if ( - ts.isClassExpression(initializer) - || ( - ts.isBinaryExpression(initializer) - && ts.isClassExpression(initializer.right) - ) - ) { - const classStatements = findStatements(name, statements, oIndex); - if (!classStatements) { - continue; - } - - oldStatementsLength = classStatements.length; - newStatement = createWrappedClass( - nodeFactory, - variableDeclaration, - classStatements, - ); - - oIndex += classStatements.length - 1; - } - } - } else if (ts.isClassDeclaration(currentStatement)) { - const name = (currentStatement.name as ts.Identifier).text; - const classStatements = findStatements(name, statements, oIndex); - if (!classStatements) { - continue; - } - - oldStatementsLength = classStatements.length; - newStatement = createWrappedClass( - nodeFactory, - currentStatement, - classStatements, - ); - - oIndex += oldStatementsLength - 1; - } - - if (newStatement && newStatement.length > 0) { - if (!updatedStatements) { - updatedStatements = [...statements]; - } - - updatedStatements.splice(uIndex, oldStatementsLength, ...newStatement); - // When having more than a single new statement - // we need to update the update Index - uIndex += (newStatement ? newStatement.length - 1 : 0); - } - - const result = ts.visitNode(currentStatement, visitor); - if (result !== currentStatement) { - if (!updatedStatements) { - updatedStatements = statements.slice(); - } - updatedStatements[uIndex] = result; - } - } - - // if changes, return updated statements - // otherwise, return original array instance - return updatedStatements ? nodeFactory.createNodeArray(updatedStatements) : statements; -} - -// TS 2.3 enums have statements that are inside a IIFE. -function findEnumIife( - name: string, - statement: ts.Statement, -): [ts.CallExpression, ts.Expression | undefined] | null { - if (!ts.isExpressionStatement(statement)) { - return null; - } - - const expression = statement.expression; - if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 1) { - return null; - } - - const callExpression = expression; - let exportExpression: ts.Expression | undefined; - - if (!ts.isParenthesizedExpression(callExpression.expression)) { - return null; - } - - const functionExpression = callExpression.expression.expression; - if (!ts.isFunctionExpression(functionExpression)) { - return null; - } - - // The name of the parameter can be different than the name of the enum if it was renamed - // due to scope hoisting. - const parameter = functionExpression.parameters[0]; - if (!ts.isIdentifier(parameter.name)) { - return null; - } - const parameterName = parameter.name.text; - - let argument = callExpression.arguments[0]; - if ( - !ts.isBinaryExpression(argument) - || !ts.isIdentifier(argument.left) - || argument.left.text !== name - ) { - return null; - } - - let potentialExport = false; - if (argument.operatorToken.kind === ts.SyntaxKind.FirstAssignment) { - if (ts.isBinaryExpression(argument.right) && argument.right.operatorToken.kind !== ts.SyntaxKind.BarBarToken) { - return null; - } - - potentialExport = true; - argument = argument.right; - } - - if (!ts.isBinaryExpression(argument)) { - return null; - } - - if (argument.operatorToken.kind !== ts.SyntaxKind.BarBarToken) { - return null; - } - - if (potentialExport && !ts.isIdentifier(argument.left)) { - exportExpression = argument.left; - } - - // Go through all the statements and check that all match the name - for (const statement of functionExpression.body.statements) { - if ( - !ts.isExpressionStatement(statement) - || !ts.isBinaryExpression(statement.expression) - || !ts.isElementAccessExpression(statement.expression.left) - ) { - return null; - } - - const leftExpression = statement.expression.left.expression; - if (!ts.isIdentifier(leftExpression) || leftExpression.text !== parameterName) { - return null; - } - } - - return [callExpression, exportExpression]; -} - -function updateHostNode( - nodeFactory: ts.NodeFactory, - hostNode: ts.VariableStatement, - expression: ts.Expression, -): ts.Statement { - // Update existing host node with the pure comment before the variable declaration initializer. - const variableDeclaration = hostNode.declarationList.declarations[0]; - const outerVarStmt = nodeFactory.updateVariableStatement( - hostNode, - hostNode.modifiers, - nodeFactory.updateVariableDeclarationList( - hostNode.declarationList, - [ - nodeFactory.updateVariableDeclaration( - variableDeclaration, - variableDeclaration.name, - variableDeclaration.exclamationToken, - variableDeclaration.type, - expression, - ), - ], - ), - ); - - return outerVarStmt; -} - -/** - * Find enums, class expression or declaration statements. - * - * The classExpressions block to wrap in an iife must - * - end with an ExpressionStatement - * - it's expression must be a BinaryExpression - * - have the same name - * - * ``` - let Foo = class Foo {}; - Foo = __decorate([]); - ``` - */ -function findStatements( - name: string, - statements: ts.NodeArray, - statementIndex: number, - offset = 0, -): ts.Statement[] | undefined { - let count = 1; - - for (let index = statementIndex + 1; index < statements.length; ++index) { - const statement = statements[index]; - if (!ts.isExpressionStatement(statement)) { - break; - } - - const expression = statement.expression; - - if (ts.isCallExpression(expression)) { - // Ex: - // setClassMetadata(FooClass, [{}], void 0); - // __decorate([propDecorator()], FooClass.prototype, "propertyName", void 0); - // __decorate([propDecorator()], FooClass, "propertyName", void 0); - // __decorate$1([propDecorator()], FooClass, "propertyName", void 0); - const args = expression.arguments; - - if (args.length > 2) { - const isReferenced = args.some(arg => { - const potentialIdentifier = ts.isPropertyAccessExpression(arg) ? arg.expression : arg; - - return ts.isIdentifier(potentialIdentifier) && potentialIdentifier.text === name; - }); - - if (isReferenced) { - count++; - continue; - } - } - } else if (ts.isBinaryExpression(expression)) { - const node = ts.isBinaryExpression(expression.left) - ? expression.left.left - : expression.left; - - const leftExpression = ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node) - // Static Properties // Ex: Foo.bar = 'value'; - // ENUM Property // Ex: ChangeDetectionStrategy[ChangeDetectionStrategy.Default] = "Default"; - ? node.expression - // Ex: FooClass = __decorate([Component()], FooClass); - : node; - - if (ts.isIdentifier(leftExpression) && leftExpression.text === name) { - count++; - continue; - } - } - - break; - } - - if (count > 1) { - return statements.slice(statementIndex + offset, statementIndex + count); - } - - return undefined; -} - -function updateEnumIife( - nodeFactory: ts.NodeFactory, - hostNode: ts.VariableStatement, - iife: ts.CallExpression, - exportAssignment?: ts.Expression, -): ts.Statement[] { - if (!ts.isParenthesizedExpression(iife.expression) - || !ts.isFunctionExpression(iife.expression.expression)) { - throw new Error('Invalid IIFE Structure'); - } - - // Ignore export assignment if variable is directly exported - if (hostNode.modifiers - && hostNode.modifiers.findIndex(m => m.kind == ts.SyntaxKind.ExportKeyword) != -1) { - exportAssignment = undefined; - } - - const expression = iife.expression.expression; - const updatedFunction = nodeFactory.updateFunctionExpression( - expression, - expression.modifiers, - expression.asteriskToken, - expression.name, - expression.typeParameters, - expression.parameters, - expression.type, - nodeFactory.updateBlock( - expression.body, - [ - ...expression.body.statements, - nodeFactory.createReturnStatement(expression.parameters[0].name as ts.Identifier), - ], - ), - ); - - let arg: ts.Expression = nodeFactory.createObjectLiteralExpression(); - if (exportAssignment) { - arg = nodeFactory.createBinaryExpression(exportAssignment, ts.SyntaxKind.BarBarToken, arg); - } - const updatedIife = nodeFactory.updateCallExpression( - iife, - nodeFactory.updateParenthesizedExpression( - iife.expression, - updatedFunction, - ), - iife.typeArguments, - [arg], - ); - - let value: ts.Expression = addPureComment(updatedIife); - if (exportAssignment) { - value = nodeFactory.createBinaryExpression( - exportAssignment, - ts.SyntaxKind.FirstAssignment, - updatedIife); - } - - return [updateHostNode(nodeFactory, hostNode, value)]; -} - -function createWrappedClass( - nodeFactory: ts.NodeFactory, - hostNode: ts.ClassDeclaration | ts.VariableDeclaration, - statements: ts.Statement[], -): ts.Statement[] { - const name = (hostNode.name as ts.Identifier).text; - - const updatedStatements = [...statements]; - - if (ts.isClassDeclaration(hostNode)) { - updatedStatements[0] = nodeFactory.createClassDeclaration( - hostNode.decorators, - undefined, - hostNode.name, - hostNode.typeParameters, - hostNode.heritageClauses, - hostNode.members, - ); - } - - const pureIife = addPureComment( - nodeFactory.createImmediatelyInvokedArrowFunction([ - ...updatedStatements, - nodeFactory.createReturnStatement(nodeFactory.createIdentifier(name)), - ]), - ); - - const modifiers = hostNode.modifiers; - const isDefault = !!modifiers - && modifiers.some(x => x.kind === ts.SyntaxKind.DefaultKeyword); - - const newStatement: ts.Statement[] = []; - newStatement.push( - nodeFactory.createVariableStatement( - isDefault ? undefined : modifiers, - nodeFactory.createVariableDeclarationList([ - nodeFactory.createVariableDeclaration(name, undefined, undefined, pureIife), - ], - ts.NodeFlags.Let, - ), - )); - - if (isDefault) { - newStatement.push( - nodeFactory.createExportAssignment( - undefined, - undefined, - false, - nodeFactory.createIdentifier(name), - )); - } - - return newStatement; -} diff --git a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts deleted file mode 100644 index 0789b1c5643b..000000000000 --- a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts +++ /dev/null @@ -1,656 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable-next-line:no-implicit-dependencies -import { tags } from '@angular-devkit/core'; -import { transformJavascript } from '../helpers/transform-javascript'; -import { getWrapEnumsTransformer } from './wrap-enums'; - - -const transform = (content: string) => transformJavascript( - { content, getTransforms: [getWrapEnumsTransformer] }).content; - -// tslint:disable:no-big-function -describe('wrap enums and classes transformer', () => { - describe('wraps class declarations', () => { - it('should wrap default exported classes', () => { - const defaultClass = tags.stripIndent` - export default class CustomComponentEffects { - constructor(_actions) { - this._actions = _actions; - this.doThis = this._actions; - } - } - CustomComponentEffects.decorators = [{ type: Injectable }]; - `; - - const namedClass = tags.stripIndent` - class CustomComponent { - constructor(_actions) { - this._actions = _actions; - this.doThis = this._actions; - } - } - CustomComponent.decorators = [{ type: Injectable }]; - `; - - const output = tags.stripIndent` - let CustomComponentEffects = /*@__PURE__*/ (() => { - ${defaultClass.replace('export default ', '')} - - return CustomComponentEffects; - })(); - export default CustomComponentEffects; - - let CustomComponent = /*@__PURE__*/ (() => { - ${namedClass} - - return CustomComponent; - })(); - `; - - const input = defaultClass + namedClass; - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('should wrap tsickle emitted classes which followed by metadata', () => { - const input = tags.stripIndent` - class CustomComponentEffects { - constructor(_actions) { - this._actions = _actions; - this.doThis = this._actions; - } - } - CustomComponentEffects.decorators = [{ type: Injectable }]; - CustomComponentEffects.ctorParameters = () => [{ type: Actions }]; - tslib_1.__decorate([ - Effect(), - tslib_1.__metadata("design:type", Object) - ], CustomComponentEffects.prototype, "doThis", void 0); - tslib_1.__decorate([ - Effect({ dispatch: false }), - tslib_1.__metadata("design:type", Object) - ], CustomComponentEffects.prototype, "doThat", void 0); - `; - - const output = tags.stripIndent` - let CustomComponentEffects = /*@__PURE__*/ (() => { - ${input} - - return CustomComponentEffects; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('should not wrap enum like which are inside of methods', () => { - const input = tags.stripIndent` - class LayoutDirective { - constructor(elRef) { } - - applyStyleToElement(element, style, value) { - let styles = {}; - if (typeof style === 'string') { - styles[style] = value; - style = styles; - } - styles = this.layoutConfig.disableVendorPrefixes ? style : applyCssPrefixes(style); - this._applyMultiValueStyleToElement(styles, element); - } - } - LayoutDirective.ctorParameters = () => [ - { type: ElementRef } - ]; - `; - - const output = tags.stripIndent` - let LayoutDirective = /*@__PURE__*/ (() => { - ${input} - - return LayoutDirective; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('should ClassDeclarations that are referenced with in CallExpressions', () => { - const input = tags.stripIndent` - class ApplicationModule { - constructor(appRef) { } - } - ApplicationModule.ɵmod = ɵɵdefineNgModule({ type: ApplicationModule }); - /*@__PURE__*/ setClassMetadata(ApplicationModule, [{ - type: NgModule, - args: [{ providers: APPLICATION_MODULE_PROVIDERS }] - }], function () { return [{ type: ApplicationRef }]; }, { constructor: [] }); - ApplicationModule.ctorParameters = () => [ - { type: ApplicationRef } - ]; - `; - - const output = tags.stripIndent` - let ApplicationModule = /*@__PURE__*/ (() => { - ${input} - - return ApplicationModule; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('with nested static properties in IIFE', () => { - const input = tags.stripIndent` - class CommonModule { } - CommonModule.ɵmod = defineNgModule({ - type: CommonModule - }), CommonModule.ngInjectorDef = defineInjector({ - factory: function (t) { - return new (t || CommonModule)(); - }, - providers: [{ - provide: NgLocalization, - useClass: NgLocaleLocalization - }] - }); - `; - - const output = tags.stripIndent` - let CommonModule = /*@__PURE__*/ (() => { - ${input} - - return CommonModule; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('with property decorators in IIFE', () => { - const input = tags.stripIndent` - export class Foo { - method() { - } - } - Foo.bar = 'barValue'; - __decorate([ - methodDecorator - ], Foo.prototype, "method", null); - `; - - const output = tags.stripIndent` - export let Foo = /*@__PURE__*/ (() => { - ${input.replace('export ', '')} - - return Foo; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('folds static properties in IIFE', () => { - const input = tags.stripIndent` - export class TemplateRef { } - TemplateRef.__NG_ELEMENT_ID__ = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef); - `; - const output = tags.stripIndent` - export let TemplateRef = /*@__PURE__*/ (() => { - class TemplateRef { } - TemplateRef.__NG_ELEMENT_ID__ = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef); - return TemplateRef; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('folds multiple static properties into class', () => { - const input = tags.stripIndent` - export class TemplateRef { } - TemplateRef.__NG_ELEMENT_ID__ = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef); - TemplateRef.somethingElse = true; - `; - const output = tags.stripIndent` - export let TemplateRef = /*@__PURE__*/ (() => { - class TemplateRef { - } - TemplateRef.__NG_ELEMENT_ID__ = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef); - TemplateRef.somethingElse = true; - return TemplateRef; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it(`doesn't wrap classes without static properties in IIFE`, () => { - const input = tags.stripIndent` - export class TemplateRef { } - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); - }); - }); - - describe('wrap class expressions', () => { - it('should wrap default exported classes', () => { - const defaultClass = tags.stripIndent` - let Foo = class Foo { - }; - Foo.bar = 'bar'; - Foo = __decorate([ - component() - ], Foo); - export default Foo; - `; - - const namedClass = tags.stripIndent` - let AggregateColumnDirective = class AggregateColumnDirective { - constructor(viewContainerRef) { } - }; - AggregateColumnDirective = __decorate([ - Directive({}), - __metadata("design:paramtypes", [ViewContainerRef]) - ], AggregateColumnDirective); - `; - - const output = tags.stripIndent` - let Foo = /*@__PURE__*/ (() => { - ${defaultClass.replace('export default Foo;', '')} - - return Foo; - })(); - export default Foo; - - let AggregateColumnDirective = /*@__PURE__*/ (() => { - ${namedClass} - - return AggregateColumnDirective; - })(); - `; - - const input = defaultClass + namedClass; - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('without property decorators in IIFE', () => { - const input = tags.stripIndent` - let AggregateColumnDirective = class AggregateColumnDirective { - constructor(viewContainerRef) { } - }; - AggregateColumnDirective = __decorate([ - Directive({}), - __metadata("design:paramtypes", [ViewContainerRef]) - ], AggregateColumnDirective); - `; - - const output = tags.stripIndent` - let AggregateColumnDirective = /*@__PURE__*/ (() => { - ${input} - - return AggregateColumnDirective; - })(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('with forwardRef in IIFE', () => { - const classContent = tags.stripIndent` - let FooDirective = FooDirective_1 = class FooDirective { - constructor(parent) { } - }; - FooDirective = FooDirective_1 = __decorate([ - Directive({ - selector: '[libUnshakeable2]', - }), - __param(0, SkipSelf()), __param(0, Inject(forwardRef(() => FooDirective_1))), - __metadata("design:paramtypes", [FooDirective]) - ], FooDirective); - `; - - const input = tags.stripIndent` - var FooDirective_1; - ${classContent} - export { FooDirective }; - `; - - const output = tags.stripIndent` - var FooDirective_1; - let FooDirective = /*@__PURE__*/ (() => { - ${classContent} - - return FooDirective; - })(); - export { FooDirective }; - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('with property decorators in IIFE', () => { - const input = tags.stripIndent` - let ChipList = class ChipList extends Component { - constructor(options, element) { - super(options, element); - } - }; - __decorate$4([Property([])], ChipList.prototype, "chips", void 0); - ChipList = __decorate$4([NotifyPropertyChanges], ChipList); - `; - - const output = tags.stripIndent` - let ChipList = /*@__PURE__*/ (() => { - ${input} - return ChipList; - })();`; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('should not wrap without decorators', () => { - const input = tags.stripIndent` - let ChipList = class ChipList extends Component { - constructor(options, element) { - super(options, element); - } - }; - fooBar(); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); - }); - - it('should wrap ClassExpression with property decorators and static property in IIFE', () => { - const input = tags.stripIndent` - let ChipList = class ChipList extends Component { - constructor(options, element) { - super(options, element); - } - }; - ChipList.prop = 1; - __decorate$4([Property([])], ChipList.prototype, "chips", void 0); - ChipList = __decorate$4([NotifyPropertyChanges], ChipList);`; - - const output = tags.stripIndent` - let ChipList = /*@__PURE__*/ (() => { - ${input} - return ChipList; - })();`; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('should wrap multiple ClassExpression in IIFE', () => { - const firstClass = ` - let AggregateColumnDirective = class AggregateColumnDirective { - constructor(viewContainerRef) { } - }; - AggregateColumnDirective = __decorate([ - Directive({}), - __metadata("design:paramtypes", [ViewContainerRef]) - ], AggregateColumnDirective); - `; - - const secondClass = ` - let ChipList = class ChipList extends Component { - constructor(options, element) { - super(options, element); - } - }; - __decorate$4([Property([])], ChipList.prototype, "chips", void 0); - ChipList = __decorate$4([NotifyPropertyChanges], ChipList); - `; - - const input = tags.stripIndent` - const minutesMilliSeconds = 60000; - - ${firstClass} - - const CSS = 'e-css'; - const PRIMARY = 'e-primary'; - - ${secondClass} - - const chipList = new ChipList({}, {}); - `; - - const output = tags.stripIndent` - const minutesMilliSeconds = 60000; - - let AggregateColumnDirective = /*@__PURE__*/ (() => { - ${firstClass} - - return AggregateColumnDirective; - })(); - - const CSS = 'e-css'; - const PRIMARY = 'e-primary'; - - let ChipList = /*@__PURE__*/ (() => { - ${secondClass} - - return ChipList; - })(); - - const chipList = new ChipList({}, {}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); - - describe('wrap enums', () => { - it('should not wrap enum like object literal declarations', () => { - const input = tags.stripIndent` - const RendererStyleFlags3 = { - Important: 1, - DashCase: 2, - }; - if (typeof RendererStyleFlags3 === 'object') { - RendererStyleFlags3[RendererStyleFlags3.Important] = 'DashCase'; - } - RendererStyleFlags3[RendererStyleFlags3.Important] = 'Important'; - `; - const output = input; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - - it('wraps ts >2.3 enums in IIFE', () => { - const input = tags.stripIndent` - export var ChangeDetectionStrategy; - (function (ChangeDetectionStrategy) { - ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; - ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; - })(ChangeDetectionStrategy || (ChangeDetectionStrategy = {})); - `; - const output = tags.stripIndent` - export var ChangeDetectionStrategy = /*@__PURE__*/ (function (ChangeDetectionStrategy) { - ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; - ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; - return ChangeDetectionStrategy; - })({}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('wraps ts >2.3 enums in IIFE, even if they have funny numbers', () => { - const input = tags.stripIndent` - export var AnimatorControlState; - (function (AnimatorControlState) { - AnimatorControlState[AnimatorControlState["INITIALIZED"] = 1] = "INITIALIZED"; - AnimatorControlState[AnimatorControlState["STARTED"] = 2] = "STARTED"; - AnimatorControlState[AnimatorControlState["FINISHED"] = 3] = "FINISHED"; - AnimatorControlState[AnimatorControlState["DESTROYED"] = 4] = "DESTROYED"; - })(AnimatorControlState || (AnimatorControlState = {})); - `; - const output = tags.stripIndent` - export var AnimatorControlState = /*@__PURE__*/ (function (AnimatorControlState) { - AnimatorControlState[AnimatorControlState["INITIALIZED"] = 1] = "INITIALIZED"; - AnimatorControlState[AnimatorControlState["STARTED"] = 2] = "STARTED"; - AnimatorControlState[AnimatorControlState["FINISHED"] = 3] = "FINISHED"; - AnimatorControlState[AnimatorControlState["DESTROYED"] = 4] = "DESTROYED"; - return AnimatorControlState; - })({}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('wraps ts >2.3 enums in IIFE, even if they were renamed due to scope hoisting', () => { - const input = tags.stripIndent` - var TokenType$1; - (function (TokenType) { - TokenType[TokenType["TAG_OPEN_START"] = 0] = "TAG_OPEN_START"; - TokenType[TokenType["TAG_OPEN_END"] = 1] = "TAG_OPEN_END"; - TokenType[TokenType["TAG_OPEN_END_VOID"] = 2] = "TAG_OPEN_END_VOID"; - TokenType[TokenType["TAG_CLOSE"] = 3] = "TAG_CLOSE"; - TokenType[TokenType["TEXT"] = 4] = "TEXT"; - TokenType[TokenType["ESCAPABLE_RAW_TEXT"] = 5] = "ESCAPABLE_RAW_TEXT"; - TokenType[TokenType["RAW_TEXT"] = 6] = "RAW_TEXT"; - TokenType[TokenType["COMMENT_START"] = 7] = "COMMENT_START"; - TokenType[TokenType["COMMENT_END"] = 8] = "COMMENT_END"; - TokenType[TokenType["CDATA_START"] = 9] = "CDATA_START"; - TokenType[TokenType["CDATA_END"] = 10] = "CDATA_END"; - TokenType[TokenType["ATTR_NAME"] = 11] = "ATTR_NAME"; - TokenType[TokenType["ATTR_VALUE"] = 12] = "ATTR_VALUE"; - TokenType[TokenType["DOC_TYPE"] = 13] = "DOC_TYPE"; - TokenType[TokenType["EXPANSION_FORM_START"] = 14] = "EXPANSION_FORM_START"; - TokenType[TokenType["EXPANSION_CASE_VALUE"] = 15] = "EXPANSION_CASE_VALUE"; - TokenType[TokenType["EXPANSION_CASE_EXP_START"] = 16] = "EXPANSION_CASE_EXP_START"; - TokenType[TokenType["EXPANSION_CASE_EXP_END"] = 17] = "EXPANSION_CASE_EXP_END"; - TokenType[TokenType["EXPANSION_FORM_END"] = 18] = "EXPANSION_FORM_END"; - TokenType[TokenType["EOF"] = 19] = "EOF"; - })(TokenType$1 || (TokenType$1 = {})); - `; - const output = tags.stripIndent` - var TokenType$1 = /*@__PURE__*/ (function (TokenType) { - TokenType[TokenType["TAG_OPEN_START"] = 0] = "TAG_OPEN_START"; - TokenType[TokenType["TAG_OPEN_END"] = 1] = "TAG_OPEN_END"; - TokenType[TokenType["TAG_OPEN_END_VOID"] = 2] = "TAG_OPEN_END_VOID"; - TokenType[TokenType["TAG_CLOSE"] = 3] = "TAG_CLOSE"; - TokenType[TokenType["TEXT"] = 4] = "TEXT"; - TokenType[TokenType["ESCAPABLE_RAW_TEXT"] = 5] = "ESCAPABLE_RAW_TEXT"; - TokenType[TokenType["RAW_TEXT"] = 6] = "RAW_TEXT"; - TokenType[TokenType["COMMENT_START"] = 7] = "COMMENT_START"; - TokenType[TokenType["COMMENT_END"] = 8] = "COMMENT_END"; - TokenType[TokenType["CDATA_START"] = 9] = "CDATA_START"; - TokenType[TokenType["CDATA_END"] = 10] = "CDATA_END"; - TokenType[TokenType["ATTR_NAME"] = 11] = "ATTR_NAME"; - TokenType[TokenType["ATTR_VALUE"] = 12] = "ATTR_VALUE"; - TokenType[TokenType["DOC_TYPE"] = 13] = "DOC_TYPE"; - TokenType[TokenType["EXPANSION_FORM_START"] = 14] = "EXPANSION_FORM_START"; - TokenType[TokenType["EXPANSION_CASE_VALUE"] = 15] = "EXPANSION_CASE_VALUE"; - TokenType[TokenType["EXPANSION_CASE_EXP_START"] = 16] = "EXPANSION_CASE_EXP_START"; - TokenType[TokenType["EXPANSION_CASE_EXP_END"] = 17] = "EXPANSION_CASE_EXP_END"; - TokenType[TokenType["EXPANSION_FORM_END"] = 18] = "EXPANSION_FORM_END"; - TokenType[TokenType["EOF"] = 19] = "EOF"; - return TokenType; - })({}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('wraps TS string enums in IIFE', () => { - const input = tags.stripIndent` - export var NotificationKind; - (function (NotificationKind) { - NotificationKind["NEXT"] = "N"; - NotificationKind["ERROR"] = "E"; - NotificationKind["COMPLETE"] = "C"; - })(NotificationKind || (NotificationKind = {})); - `; - const output = tags.stripIndent` - export var NotificationKind = /*@__PURE__*/ (function (NotificationKind) { - NotificationKind["NEXT"] = "N"; - NotificationKind["ERROR"] = "E"; - NotificationKind["COMPLETE"] = "C"; - return NotificationKind; - })({}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('wraps enums with multi-line comments in IIFE', () => { - const input = tags.stripIndent` - /** - * Supported http methods. - * @deprecated use @angular/common/http instead - */ - var RequestMethod; - /** - * Supported http methods. - * @deprecated use @angular/common/http instead - */ - (function (RequestMethod) { - RequestMethod[RequestMethod["Get"] = 0] = "Get"; - RequestMethod[RequestMethod["Post"] = 1] = "Post"; - RequestMethod[RequestMethod["Put"] = 2] = "Put"; - RequestMethod[RequestMethod["Delete"] = 3] = "Delete"; - RequestMethod[RequestMethod["Options"] = 4] = "Options"; - RequestMethod[RequestMethod["Head"] = 5] = "Head"; - RequestMethod[RequestMethod["Patch"] = 6] = "Patch"; - })(RequestMethod || (RequestMethod = {})); - `; - // We need to interpolate this space because our editorconfig automatically strips - // trailing whitespace. - const space = ' '; - const output = tags.stripIndent` - /** - * Supported http methods. - * @deprecated use @angular/common/http instead - */ - var RequestMethod =${space} - /** - * Supported http methods. - * @deprecated use @angular/common/http instead - */ - /*@__PURE__*/ (function (RequestMethod) { - RequestMethod[RequestMethod["Get"] = 0] = "Get"; - RequestMethod[RequestMethod["Post"] = 1] = "Post"; - RequestMethod[RequestMethod["Put"] = 2] = "Put"; - RequestMethod[RequestMethod["Delete"] = 3] = "Delete"; - RequestMethod[RequestMethod["Options"] = 4] = "Options"; - RequestMethod[RequestMethod["Head"] = 5] = "Head"; - RequestMethod[RequestMethod["Patch"] = 6] = "Patch"; - return RequestMethod; - })({}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - - it('wraps exported enums in IIFE', () => { - const input = tags.stripIndent` - var ExportEnum; - (function (ExportEnum) { - ExportEnum[ExportEnum["A"] = 0] = "A"; - ExportEnum[ExportEnum["B"] = 1] = "B"; - ExportEnum[ExportEnum["C"] = 2] = "C"; - })(ExportEnum = exports.ExportEnum || (exports.ExportEnum = {})); - `; - const output = tags.stripIndent` - var ExportEnum = exports.ExportEnum = /*@__PURE__*/ (function (ExportEnum) { - ExportEnum[ExportEnum["A"] = 0] = "A"; - ExportEnum[ExportEnum["B"] = 1] = "B"; - ExportEnum[ExportEnum["C"] = 2] = "C"; - return ExportEnum; - })(exports.ExportEnum || {}); - `; - - expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); - }); - }); -}); diff --git a/packages/angular_devkit/build_optimizer/webpack-loader/package.json b/packages/angular_devkit/build_optimizer/webpack-loader/package.json deleted file mode 100644 index a18ef898c128..000000000000 --- a/packages/angular_devkit/build_optimizer/webpack-loader/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "main": "../src/build-optimizer/webpack-loader.js" -} diff --git a/packages/angular_devkit/build_webpack/BUILD.bazel b/packages/angular_devkit/build_webpack/BUILD.bazel index df31bb3302c9..9d1cf4bb25e1 100644 --- a/packages/angular_devkit/build_webpack/BUILD.bazel +++ b/packages/angular_devkit/build_webpack/BUILD.bazel @@ -4,15 +4,12 @@ # found in the LICENSE file at https://angular.io/license load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test") -load("//tools:defaults.bzl", "ts_library") +load("//tools:defaults.bzl", "pkg_npm", "ts_library") load("//tools:ts_json_schema.bzl", "ts_json_schema") +load("//tools:toolchain_info.bzl", "TOOLCHAINS_NAMES", "TOOLCHAINS_VERSIONS") +load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") -# @external_begin -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -# @external_end - -licenses(["notice"]) # MIT +licenses(["notice"]) package(default_visibility = ["//visibility:public"]) @@ -28,6 +25,7 @@ ts_json_schema( ts_library( name = "build_webpack", + package_name = "@angular-devkit/build-webpack", srcs = glob( include = ["src/**/*.ts"], exclude = [ @@ -49,7 +47,6 @@ ts_library( deps = [ "//packages/angular_devkit/architect", "@npm//@types/node", - "@npm//@types/webpack-dev-server", "@npm//rxjs", "@npm//webpack", "@npm//webpack-dev-server", @@ -86,37 +83,57 @@ ts_library( "@npm//@angular/platform-browser-dynamic", "@npm//@types/node-fetch", "@npm//node-fetch", - "@npm//raw-loader", "@npm//tslib", "@npm//zone.js", ], ) -jasmine_node_test( - name = "build_webpack_test", - srcs = [":build_webpack_test_lib"], - # Turns off nodejs require patches and turns on the linker, which sets up up node_modules - # so that standard node module resolution work. - templated_args = ["--nobazel_patch_module_resolver"], - deps = [ - "@npm//jasmine", - "@npm//source-map", - ], +[ + jasmine_node_test( + name = "build_webpack_test_" + toolchain_name, + srcs = [":build_webpack_test_lib"], + tags = [toolchain_name], + # Turns off nodejs require patches and turns on the linker, which sets up up node_modules + # so that standard node module resolution work. + templated_args = ["--nobazel_patch_module_resolver"], + toolchain = toolchain, + deps = [ + "@npm//jasmine", + "@npm//source-map", + ], + ) + for toolchain_name, toolchain in zip( + TOOLCHAINS_NAMES, + TOOLCHAINS_VERSIONS, + ) +] + +genrule( + name = "license", + srcs = ["//:LICENSE"], + outs = ["LICENSE"], + cmd = "cp $(execpath //:LICENSE) $@", ) -# @external_begin pkg_npm( name = "npm_package", + pkg_deps = [ + "//packages/angular_devkit/architect:package.json", + ], deps = [ + ":README.md", ":build_webpack", + ":builders.json", + ":license", ], ) -pkg_tar( - name = "npm_package_archive", - srcs = [":npm_package"], - extension = "tar.gz", - strip_prefix = "./npm_package", - tags = ["manual"], +api_golden_test_npm_package( + name = "build_webpack_api", + data = [ + ":npm_package", + "//goldens:public-api", + ], + golden_dir = "angular_cli/goldens/public-api/angular_devkit/build_webpack", + npm_package = "angular_cli/packages/angular_devkit/build_webpack/npm_package", ) -# @external_end diff --git a/packages/angular_devkit/build_webpack/README.md b/packages/angular_devkit/build_webpack/README.md index 49e19302d939..2663efe82dae 100644 --- a/packages/angular_devkit/build_webpack/README.md +++ b/packages/angular_devkit/build_webpack/README.md @@ -7,6 +7,7 @@ To use it on your Angular CLI app, follow these steps: - run `npm install @angular-devkit/build-webpack`. - create a webpack configuration. - add the following targets inside `angular.json`. + ``` "projects": { "app": { @@ -27,6 +28,7 @@ To use it on your Angular CLI app, follow these steps: } } ``` + - run `ng run app:build-webpack` to build, and `ng run app:serve-webpack` to serve. -All options, including `watch` and `stats`, are looked up inside the webpack configuration. \ No newline at end of file +All options, including `watch` and `stats`, are looked up inside the webpack configuration. diff --git a/packages/angular_devkit/build_webpack/package.json b/packages/angular_devkit/build_webpack/package.json index 2db099a6a9e3..c566017e1f9f 100644 --- a/packages/angular_devkit/build_webpack/package.json +++ b/packages/angular_devkit/build_webpack/package.json @@ -1,22 +1,22 @@ { "name": "@angular-devkit/build-webpack", - "version": "0.0.0", + "version": "0.0.0-EXPERIMENTAL-PLACEHOLDER", "description": "Webpack Builder for Architect", "experimental": true, "main": "src/index.js", "typings": "src/index.d.ts", "builders": "builders.json", "dependencies": { - "@angular-devkit/architect": "0.0.0", + "@angular-devkit/architect": "0.0.0-EXPERIMENTAL-PLACEHOLDER", "rxjs": "6.6.7" }, "devDependencies": { - "@angular-devkit/core": "0.0.0", - "node-fetch": "2.6.1", - "webpack": "5.32.0" + "@angular-devkit/core": "0.0.0-PLACEHOLDER", + "node-fetch": "2.6.7", + "webpack": "5.75.0" }, "peerDependencies": { "webpack": "^5.30.0", - "webpack-dev-server": "^3.1.4" + "webpack-dev-server": "^4.0.0" } } diff --git a/packages/angular_devkit/build_webpack/src/index.ts b/packages/angular_devkit/build_webpack/src/index.ts index fbd92d0eb827..107e3c99d535 100644 --- a/packages/angular_devkit/build_webpack/src/index.ts +++ b/packages/angular_devkit/build_webpack/src/index.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export * from './webpack'; export * from './webpack-dev-server'; export { EmittedFiles } from './utils'; diff --git a/packages/angular_devkit/build_webpack/src/utils.ts b/packages/angular_devkit/build_webpack/src/utils.ts index 64609278b0ac..2367996b842f 100644 --- a/packages/angular_devkit/build_webpack/src/utils.ts +++ b/packages/angular_devkit/build_webpack/src/utils.ts @@ -1,13 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import { existsSync } from 'fs'; import * as path from 'path'; -import * as webpack from 'webpack'; +import { URL, pathToFileURL } from 'url'; +import { Compilation, Configuration } from 'webpack'; export interface EmittedFiles { id?: string; @@ -18,16 +20,20 @@ export interface EmittedFiles { extension: string; } -export function getEmittedFiles(compilation: webpack.Compilation): EmittedFiles[] { +export function getEmittedFiles(compilation: Compilation): EmittedFiles[] { const files: EmittedFiles[] = []; + const chunkFileNames = new Set(); // adds all chunks to the list of emitted files such as lazy loaded modules - for (const chunk of compilation.chunks as Iterable) { + for (const chunk of compilation.chunks) { for (const file of chunk.files) { + if (chunkFileNames.has(file)) { + continue; + } + + chunkFileNames.add(file); files.push({ - // The id is guaranteed to exist at this point in the compilation process - // tslint:disable-next-line: no-non-null-assertion - id: chunk.id!.toString(), + id: chunk.id?.toString(), name: chunk.name, file, extension: path.extname(file), @@ -36,12 +42,63 @@ export function getEmittedFiles(compilation: webpack.Compilation): EmittedFiles[ } } - // other all files + // add all other files for (const file of Object.keys(compilation.assets)) { + // Chunk files have already been added to the files list above + if (chunkFileNames.has(file)) { + continue; + } + files.push({ file, extension: path.extname(file), initial: false, asset: true }); } - // dedupe - return files.filter(({ file, name }, index) => - files.findIndex(f => f.file === file && (!name || name === f.name)) === index); + return files; +} + +/** + * This uses a dynamic import to load a module which may be ESM. + * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript + * will currently, unconditionally downlevel dynamic import into a require call. + * require calls cannot load ESM code and will result in a runtime error. To workaround + * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. + * Once TypeScript provides support for keeping the dynamic import this workaround can + * be dropped. + * + * @param modulePath The path of the module to load. + * @returns A Promise that resolves to the dynamically imported module. + */ +function loadEsmModule(modulePath: string | URL): Promise { + return new Function('modulePath', `return import(modulePath);`)(modulePath) as Promise; +} + +export async function getWebpackConfig(configPath: string): Promise { + if (!existsSync(configPath)) { + throw new Error(`Webpack configuration file ${configPath} does not exist.`); + } + + switch (path.extname(configPath)) { + case '.mjs': + // Load the ESM configuration file using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + return (await loadEsmModule<{ default: Configuration }>(pathToFileURL(configPath))).default; + case '.cjs': + return require(configPath); + default: + // The file could be either CommonJS or ESM. + // CommonJS is tried first then ESM if loading fails. + try { + return require(configPath); + } catch (e) { + if ((e as NodeJS.ErrnoException).code === 'ERR_REQUIRE_ESM') { + // Load the ESM configuration file using the TypeScript dynamic import workaround. + // Once TypeScript provides support for keeping the dynamic import this workaround can be + // changed to a direct dynamic import. + return (await loadEsmModule<{ default: Configuration }>(pathToFileURL(configPath))) + .default; + } + + throw e; + } + } } diff --git a/packages/angular_devkit/build_webpack/src/webpack-dev-server/index.ts b/packages/angular_devkit/build_webpack/src/webpack-dev-server/index.ts index 5a02f9a71149..8cd9febfd58e 100644 --- a/packages/angular_devkit/build_webpack/src/webpack-dev-server/index.ts +++ b/packages/angular_devkit/build_webpack/src/webpack-dev-server/index.ts @@ -1,18 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderContext, createBuilder } from '@angular-devkit/architect'; -import * as net from 'net'; import { resolve as pathResolve } from 'path'; import { Observable, from, isObservable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; -import * as webpack from 'webpack'; -import * as WebpackDevServer from 'webpack-dev-server'; -import { getEmittedFiles } from '../utils'; +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; +import { getEmittedFiles, getWebpackConfig } from '../utils'; import { BuildResult, WebpackFactory, WebpackLoggingCallback } from '../webpack'; import { Schema as WebpackDevServerBuilderSchema } from './schema'; @@ -28,10 +28,11 @@ export function runWebpackDevServer( config: webpack.Configuration, context: BuilderContext, options: { - devServerConfig?: WebpackDevServer.Configuration, - logging?: WebpackLoggingCallback, - webpackFactory?: WebpackFactory, - webpackDevServerFactory?: WebpackDevServerFactory, + shouldProvideStats?: boolean; + devServerConfig?: WebpackDevServer.Configuration; + logging?: WebpackLoggingCallback; + webpackFactory?: WebpackFactory; + webpackDevServerFactory?: WebpackDevServerFactory; } = {}, ): Observable { const createWebpack = (c: webpack.Configuration) => { @@ -52,52 +53,49 @@ export function runWebpackDevServer( config: WebpackDevServer.Configuration, ) => { if (options.webpackDevServerFactory) { - // webpack-dev-server types currently do not support Webpack 5 - // tslint:disable-next-line: no-any - return new options.webpackDevServerFactory(webpack as any, config); + return new options.webpackDevServerFactory(config, webpack); } - // webpack-dev-server types currently do not support Webpack 5 - // tslint:disable-next-line: no-any - return new WebpackDevServer(webpack as any, config); + return new WebpackDevServer(config, webpack); }; - const log: WebpackLoggingCallback = options.logging - || ((stats, config) => context.logger.info(stats.toString(config.stats))); + const log: WebpackLoggingCallback = + options.logging || ((stats, config) => context.logger.info(stats.toString(config.stats))); - // tslint:disable-next-line: no-any - const devServerConfig = options.devServerConfig || (config as any).devServer || {}; - if (devServerConfig.stats) { - config.stats = devServerConfig.stats; - } - // Disable stats reporting by the devserver, we have our own logger. - devServerConfig.stats = false; + const shouldProvideStats = options.shouldProvideStats ?? true; return createWebpack({ ...config, watch: false }).pipe( - switchMap(webpackCompiler => new Observable(obs => { - const server = createWebpackDevServer(webpackCompiler, devServerConfig); - let result: Partial; - - webpackCompiler.hooks.done.tap('build-webpack', (stats) => { - // Log stats. - log(stats, config); - - obs.next({ - ...result, - emittedFiles: getEmittedFiles(stats.compilation), - success: !stats.hasErrors(), - outputPath: stats.compilation.outputOptions.path, - } as unknown as DevServerBuildOutput); - }); - - server.listen( - devServerConfig.port === undefined ? 8080 : devServerConfig.port, - devServerConfig.host === undefined ? 'localhost' : devServerConfig.host, - function (this: net.Server, err) { - if (err) { - obs.error(err); - } else { - const address = this.address(); + switchMap( + (webpackCompiler) => + new Observable((obs) => { + const devServerConfig = options.devServerConfig || config.devServer || {}; + devServerConfig.host ??= 'localhost'; + + let result: Partial; + + const statsOptions = typeof config.stats === 'boolean' ? undefined : config.stats; + + webpackCompiler.hooks.done.tap('build-webpack', (stats) => { + // Log stats. + log(stats, config); + obs.next({ + ...result, + webpackStats: shouldProvideStats ? stats.toJson(statsOptions) : undefined, + emittedFiles: getEmittedFiles(stats.compilation), + success: !stats.hasErrors(), + outputPath: stats.compilation.outputOptions.path, + } as unknown as DevServerBuildOutput); + }); + + const devServer = createWebpackDevServer(webpackCompiler, devServerConfig); + devServer.startCallback((err) => { + if (err) { + obs.error(err); + + return; + } + + const address = devServer.server?.address(); if (!address) { obs.error(new Error(`Dev-server address info is not defined.`)); @@ -110,26 +108,24 @@ export function runWebpackDevServer( family: typeof address === 'string' ? '' : address.family, address: typeof address === 'string' ? address : address.address, }; - } - }, - ); - - // Teardown logic. Close the server when unsubscribed from. - return (() => { - server.close(); - webpackCompiler.close?.(() => {}); - }); - })), + }); + + // Teardown logic. Close the server when unsubscribed from. + return () => { + devServer.stopCallback(() => {}); + webpackCompiler.close(() => {}); + }; + }), + ), ); } - export default createBuilder( (options, context) => { const configPath = pathResolve(context.workspaceRoot, options.webpackConfig); - return from(import(configPath)).pipe( - switchMap((config: webpack.Configuration) => runWebpackDevServer(config, context)), + return from(getWebpackConfig(configPath)).pipe( + switchMap((config) => runWebpackDevServer(config, context)), ); }, ); diff --git a/packages/angular_devkit/build_webpack/src/webpack-dev-server/index_spec.ts b/packages/angular_devkit/build_webpack/src/webpack-dev-server/index_spec.ts index c8294fd0f1f5..8d8812c0ea65 100644 --- a/packages/angular_devkit/build_webpack/src/webpack-dev-server/index_spec.ts +++ b/packages/angular_devkit/build_webpack/src/webpack-dev-server/index_spec.ts @@ -1,20 +1,20 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect } from '@angular-devkit/architect'; import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; import { TestingArchitectHost } from '@angular-devkit/architect/testing'; -import { schema, workspaces } from '@angular-devkit/core'; // tslint:disable-line:no-implicit-dependencies -import { NodeJsSyncHost } from '@angular-devkit/core/node'; // tslint:disable-line:no-implicit-dependencies -import fetch from 'node-fetch'; // tslint:disable-line:no-implicit-dependencies +import { schema, workspaces } from '@angular-devkit/core'; +import { NodeJsSyncHost } from '@angular-devkit/core/node'; +import fetch from 'node-fetch'; // eslint-disable-line import/no-extraneous-dependencies import * as path from 'path'; import { DevServerBuildOutput } from './index'; - describe('Dev Server Builder', () => { let testArchitectHost: TestingArchitectHost; let architect: Architect; @@ -33,8 +33,11 @@ describe('Dev Server Builder', () => { workspaces.createWorkspaceHost(vfHost), ); - testArchitectHost = new TestingArchitectHost(workspaceRoot, workspaceRoot, - new WorkspaceNodeModulesArchitectHost(workspace, workspaceRoot)); + testArchitectHost = new TestingArchitectHost( + workspaceRoot, + workspaceRoot, + new WorkspaceNodeModulesArchitectHost(workspace, workspaceRoot), + ); architect = new Architect(testArchitectHost, registry); } @@ -44,9 +47,24 @@ describe('Dev Server Builder', () => { await createArchitect(workspaceRoot); }); - it('works', async () => { - const run = await architect.scheduleTarget(webpackTargetSpec); - const output = await run.result as DevServerBuildOutput; + it('works with CJS config', async () => { + const run = await architect.scheduleTarget(webpackTargetSpec, { + webpackConfig: 'webpack.config.cjs', + }); + const output = (await run.result) as DevServerBuildOutput; + expect(output.success).toBe(true); + + const response = await fetch(`http://${output.address}:${output.port}/bundle.js`); + expect(await response.text()).toContain(`console.log('hello world')`); + + await run.stop(); + }, 30000); + + it('works with ESM config', async () => { + const run = await architect.scheduleTarget(webpackTargetSpec, { + webpackConfig: 'webpack.config.mjs', + }); + const output = (await run.result) as DevServerBuildOutput; expect(output.success).toBe(true); const response = await fetch(`http://${output.address}:${output.port}/bundle.js`); @@ -57,7 +75,7 @@ describe('Dev Server Builder', () => { it('works and returns emitted files', async () => { const run = await architect.scheduleTarget(webpackTargetSpec); - const output = await run.result as DevServerBuildOutput; + const output = (await run.result) as DevServerBuildOutput; expect(output.success).toBe(true); expect(output.emittedFiles).toContain({ diff --git a/packages/angular_devkit/build_webpack/src/webpack-dev-server/schema.json b/packages/angular_devkit/build_webpack/src/webpack-dev-server/schema.json index 9a694cc36edc..6a1e18fd92e5 100644 --- a/packages/angular_devkit/build_webpack/src/webpack-dev-server/schema.json +++ b/packages/angular_devkit/build_webpack/src/webpack-dev-server/schema.json @@ -10,7 +10,5 @@ } }, "additionalProperties": false, - "required": [ - "webpackConfig" - ] + "required": ["webpackConfig"] } diff --git a/packages/angular_devkit/build_webpack/src/webpack/index.ts b/packages/angular_devkit/build_webpack/src/webpack/index.ts index 0f99f565be37..cdb9f9b2ce28 100644 --- a/packages/angular_devkit/build_webpack/src/webpack/index.ts +++ b/packages/angular_devkit/build_webpack/src/webpack/index.ts @@ -1,16 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; import { resolve as pathResolve } from 'path'; import { Observable, from, isObservable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; -import * as webpack from 'webpack'; -import { EmittedFiles, getEmittedFiles } from '../utils'; +import webpack from 'webpack'; +import { EmittedFiles, getEmittedFiles, getWebpackConfig } from '../utils'; import { Schema as RealWebpackBuilderSchema } from './schema'; export type WebpackBuilderSchema = RealWebpackBuilderSchema; @@ -32,9 +33,9 @@ export function runWebpack( config: webpack.Configuration, context: BuilderContext, options: { - logging?: WebpackLoggingCallback, - webpackFactory?: WebpackFactory, - shouldProvideStats?: boolean, + logging?: WebpackLoggingCallback; + webpackFactory?: WebpackFactory; + shouldProvideStats?: boolean; } = {}, ): Observable { const { @@ -55,68 +56,69 @@ export function runWebpack( }; return createWebpack({ ...config, watch: false }).pipe( - switchMap(webpackCompiler => new Observable(obs => { - // Webpack 5 has a compiler level close function - const compilerClose = (webpackCompiler as { close?(callback: () => void): void }).close?.bind( - webpackCompiler, - ); - - const callback = (err?: Error, stats?: webpack.Stats) => { - if (err) { - return obs.error(err); - } + switchMap( + (webpackCompiler) => + new Observable((obs) => { + const callback = (err?: Error | null, stats?: webpack.Stats) => { + if (err) { + return obs.error(err); + } - if (!stats) { - return; - } + if (!stats) { + return; + } - // Log stats. - log(stats, config); + // Log stats. + log(stats, config); - obs.next({ - success: !stats.hasErrors(), - webpackStats: shouldProvideStats ? stats.toJson() : undefined, - emittedFiles: getEmittedFiles(stats.compilation), - outputPath: stats.compilation.outputOptions.path, - } as unknown as BuildResult); + const statsOptions = typeof config.stats === 'boolean' ? undefined : config.stats; + const result = { + success: !stats.hasErrors(), + webpackStats: shouldProvideStats ? stats.toJson(statsOptions) : undefined, + emittedFiles: getEmittedFiles(stats.compilation), + outputPath: stats.compilation.outputOptions.path, + } as unknown as BuildResult; - if (!config.watch) { - if (compilerClose) { - compilerClose(() => obs.complete()); - } else { - obs.complete(); - } - } - }; + if (config.watch) { + obs.next(result); + } else { + webpackCompiler.close(() => { + obs.next(result); + obs.complete(); + }); + } + }; - try { - if (config.watch) { - const watchOptions = config.watchOptions || {}; - const watching = webpackCompiler.watch(watchOptions, callback); + try { + if (config.watch) { + const watchOptions = config.watchOptions || {}; + const watching = webpackCompiler.watch(watchOptions, callback); - // Teardown logic. Close the watcher when unsubscribed from. - return () => { - watching.close(() => { }); - compilerClose?.(() => { }); - }; - } else { - webpackCompiler.run(callback); - } - } catch (err) { - if (err) { - context.logger.error(`\nAn error occurred during the build:\n${err && err.stack || err}`); - } - throw err; - } - }), - )); + // Teardown logic. Close the watcher when unsubscribed from. + return () => { + watching.close(() => {}); + webpackCompiler.close(() => {}); + }; + } else { + webpackCompiler.run(callback); + } + } catch (err) { + if (err) { + context.logger.error( + `\nAn error occurred during the build:\n${err instanceof Error ? err.stack : err}`, + ); + } + throw err; + } + }), + ), + ); } - export default createBuilder((options, context) => { const configPath = pathResolve(context.workspaceRoot, options.webpackConfig); - return from(import(configPath)).pipe( - switchMap((config: webpack.Configuration) => runWebpack(config, context)), + return from(getWebpackConfig(configPath)).pipe( + switchMap((config) => runWebpack(config, context)), ); }); diff --git a/packages/angular_devkit/build_webpack/src/webpack/index_spec.ts b/packages/angular_devkit/build_webpack/src/webpack/index_spec.ts index 537e36729ee4..0b798d427ec6 100644 --- a/packages/angular_devkit/build_webpack/src/webpack/index_spec.ts +++ b/packages/angular_devkit/build_webpack/src/webpack/index_spec.ts @@ -1,19 +1,19 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Architect } from '@angular-devkit/architect'; import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; import { TestingArchitectHost } from '@angular-devkit/architect/testing'; -import { join, normalize, schema, workspaces } from '@angular-devkit/core'; // tslint:disable-line:no-implicit-dependencies -import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node'; // tslint:disable-line:no-implicit-dependencies +import { join, normalize, schema, workspaces } from '@angular-devkit/core'; +import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node'; import * as path from 'path'; import { BuildResult } from './index'; - describe('Webpack Builder basic test', () => { let testArchitectHost: TestingArchitectHost; let architect: Architect; @@ -30,8 +30,11 @@ describe('Webpack Builder basic test', () => { workspaces.createWorkspaceHost(vfHost), ); - testArchitectHost = new TestingArchitectHost(workspaceRoot, workspaceRoot, - new WorkspaceNodeModulesArchitectHost(workspace, workspaceRoot)); + testArchitectHost = new TestingArchitectHost( + workspaceRoot, + workspaceRoot, + new WorkspaceNodeModulesArchitectHost(workspace, workspaceRoot), + ); architect = new Architect(testArchitectHost, registry); } @@ -44,8 +47,23 @@ describe('Webpack Builder basic test', () => { await createArchitect(workspaceRoot); }); - it('works', async () => { - const run = await architect.scheduleTarget({ project: 'app', target: 'build' }); + it('works with CJS config', async () => { + const run = await architect.scheduleTarget( + { project: 'app', target: 'build' }, + { webpackConfig: 'webpack.config.cjs' }, + ); + const output = await run.result; + + expect(output.success).toBe(true); + expect(await vfHost.exists(join(outputPath, 'bundle.js')).toPromise()).toBe(true); + await run.stop(); + }); + + it('works with ESM config', async () => { + const run = await architect.scheduleTarget( + { project: 'app', target: 'build' }, + { webpackConfig: 'webpack.config.mjs' }, + ); const output = await run.result; expect(output.success).toBe(true); @@ -55,7 +73,7 @@ describe('Webpack Builder basic test', () => { it('works and returns emitted files', async () => { const run = await architect.scheduleTarget({ project: 'app', target: 'build' }); - const output = await run.result as BuildResult; + const output = (await run.result) as BuildResult; expect(output.success).toBe(true); expect(output.emittedFiles).toContain({ @@ -81,7 +99,11 @@ describe('Webpack Builder basic test', () => { }); it('works', async () => { - const run = await architect.scheduleTarget({ project: 'app', target: 'build-webpack' }, {}, {logger: createConsoleLogger()}); + const run = await architect.scheduleTarget( + { project: 'app', target: 'build-webpack' }, + {}, + { logger: createConsoleLogger() }, + ); const output = await run.result; expect(output.success).toBe(true); @@ -92,12 +114,18 @@ describe('Webpack Builder basic test', () => { it('works and returns emitted files', async () => { const run = await architect.scheduleTarget({ project: 'app', target: 'build-webpack' }); - const output = await run.result as BuildResult; + const output = (await run.result) as BuildResult; expect(output.success).toBe(true); expect(output.emittedFiles).toContain( { id: 'main', name: 'main', initial: true, file: 'main.js', extension: '.js' }, - { id: 'polyfills', name: 'polyfills', initial: true, file: 'polyfills.js', extension: '.js' }, + { + id: 'polyfills', + name: 'polyfills', + initial: true, + file: 'polyfills.js', + extension: '.js', + }, ); await run.stop(); diff --git a/packages/angular_devkit/build_webpack/src/webpack/schema.json b/packages/angular_devkit/build_webpack/src/webpack/schema.json index 8f1255f20f3d..fc27c6082b9c 100644 --- a/packages/angular_devkit/build_webpack/src/webpack/schema.json +++ b/packages/angular_devkit/build_webpack/src/webpack/schema.json @@ -10,7 +10,5 @@ } }, "additionalProperties": false, - "required": [ - "webpackConfig" - ] + "required": ["webpackConfig"] } diff --git a/packages/angular_devkit/build_webpack/test/angular-app/angular.json b/packages/angular_devkit/build_webpack/test/angular-app/angular.json index 61b5bf3affc8..3bb325a1d073 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/angular.json +++ b/packages/angular_devkit/build_webpack/test/angular-app/angular.json @@ -2,9 +2,12 @@ "$schema": "../../../../packages/angular_devkit/core/src/workspace/workspace-schema.json", "version": 1, "newProjectRoot": "./projects", - "cli": {}, + "cli": { + "cache": { + "enabled": false + } + }, "schematics": {}, - "targets": {}, "projects": { "app": { "root": "src", @@ -27,4 +30,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.html b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.html index 248632148832..43182ead282e 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.html +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.html @@ -1,17 +1,23 @@ -
-

- Welcome to {{ title }}! -

- Angular Logo +
+

Welcome to {{ title }}!

+ Angular Logo
-

Here are some links to help you start:

+

Here are some links to help you start:

i18n test

- diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.spec.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.spec.ts index 869493ff9aeb..56f6ecbf22fa 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.spec.ts +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.spec.ts @@ -1,18 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], + declarations: [AppComponent], }).compileComponents(); }); it('should create the app', () => { diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.ts index 8366380cf61a..aef2940f181f 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.ts +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.component.ts @@ -1,16 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'app'; diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.module.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.module.ts index c61881ebb3eb..34cc32f32747 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.module.ts +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/app/app.module.ts @@ -1,25 +1,20 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; - import { AppComponent } from './app.component'; - @NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule - ], + declarations: [AppComponent], + imports: [BrowserModule], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/environments/environment.prod.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/environments/environment.prod.ts deleted file mode 100644 index fac764c6d237..000000000000 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/environments/environment.prod.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export const environment = { - production: true -}; diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/environments/environment.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/environments/environment.ts deleted file mode 100644 index a3fe55067a9c..000000000000 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/environments/environment.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// The file contents for the current environment will overwrite these during build. -// The build system defaults to the dev environment which uses `environment.ts`, but if you do -// `ng build --env=prod` then `environment.prod.ts` will be used instead. -// The list of which env maps to which file can be found in `.angular-cli.json`. - -export const environment = { - production: false -}; diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/main.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/main.ts index 3d8f375e6f4f..8fd2558a6c7b 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/main.ts +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/main.ts @@ -1,19 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.log(err)); +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.log(err)); diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/polyfills.ts b/packages/angular_devkit/build_webpack/test/angular-app/src/polyfills.ts index 1b2d66b61857..3f5924842e31 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/polyfills.ts +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/polyfills.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. @@ -25,42 +26,16 @@ * BROWSER POLYFILLS */ -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/weak-map'; -// import 'core-js/es6/set'; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** IE10 and IE11 requires the following for the Reflect API. */ -// import 'core-js/es6/reflect'; - /** * Required to support Web Animations `@angular/platform-browser/animations`. * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation **/ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. - - /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - +import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS diff --git a/packages/angular_devkit/build_webpack/test/angular-app/src/tsconfig.app.json b/packages/angular_devkit/build_webpack/test/angular-app/src/tsconfig.app.json index 40a1cd9e3ec6..4191001bb4d4 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/src/tsconfig.app.json +++ b/packages/angular_devkit/build_webpack/test/angular-app/src/tsconfig.app.json @@ -2,11 +2,8 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "module": "es2020", + "module": "es2022", "types": [] }, - "exclude": [ - "test.ts", - "**/*.spec.ts" - ] + "exclude": ["test.ts", "**/*.spec.ts"] } diff --git a/packages/angular_devkit/build_webpack/test/angular-app/tsconfig.json b/packages/angular_devkit/build_webpack/test/angular-app/tsconfig.json index baba7668686d..5e7ab16f0c42 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/tsconfig.json +++ b/packages/angular_devkit/build_webpack/test/angular-app/tsconfig.json @@ -8,14 +8,9 @@ "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "target": "es2017", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] + "target": "es2022", + "typeRoots": ["node_modules/@types"], + "lib": ["es2022", "dom"] }, "angularCompilerOptions": { "enableIvy": true, diff --git a/packages/angular_devkit/build_webpack/test/angular-app/webpack.config.js b/packages/angular_devkit/build_webpack/test/angular-app/webpack.config.js index 1a8572ea232f..ed885f54ad43 100644 --- a/packages/angular_devkit/build_webpack/test/angular-app/webpack.config.js +++ b/packages/angular_devkit/build_webpack/test/angular-app/webpack.config.js @@ -1,7 +1,6 @@ const ngToolsWebpack = require('@ngtools/webpack'); const path = require('path'); - const workspaceRoot = path.resolve(__dirname, './'); const projectRoot = path.resolve(__dirname, './'); @@ -12,7 +11,7 @@ module.exports = { }, entry: { main: path.resolve(projectRoot, './src/main.ts'), - polyfills: path.resolve(projectRoot, './src/polyfills.ts') + polyfills: path.resolve(projectRoot, './src/polyfills.ts'), }, output: { path: path.resolve(workspaceRoot, './dist'), @@ -20,17 +19,21 @@ module.exports = { }, plugins: [ new ngToolsWebpack.AngularWebpackPlugin({ - tsconfig: path.resolve(projectRoot, './src/tsconfig.app.json') - }) + tsconfig: path.resolve(projectRoot, './src/tsconfig.app.json'), + }), ], module: { rules: [ - { test: /\.css$/, loader: 'raw-loader' }, - { test: /\.html$/, loader: 'raw-loader' }, + // rxjs 6 requires directory imports which are not support in ES modules. + // Disabling `fullySpecified` allows Webpack to ignore this but this is + // not ideal because it currently disables ESM behavior import for all JS files. + { test: /\.[m]?js$/, resolve: { fullySpecified: false } }, + { test: /\.css$/, type: 'asset/source' }, + { test: /\.html$/, type: 'asset/source' }, { test: /\.ts$/, loader: '@ngtools/webpack' }, - ] + ], }, devServer: { - historyApiFallback: true - } + historyApiFallback: true, + }, }; diff --git a/packages/angular_devkit/build_webpack/test/basic-app/.gitignore b/packages/angular_devkit/build_webpack/test/basic-app/.gitignore index 54bfd2001e64..e0406551d1ea 100644 --- a/packages/angular_devkit/build_webpack/test/basic-app/.gitignore +++ b/packages/angular_devkit/build_webpack/test/basic-app/.gitignore @@ -25,6 +25,7 @@ !.vscode/extensions.json # misc +/.angular/cache /.sass-cache /connect.lock /coverage diff --git a/packages/angular_devkit/build_webpack/test/basic-app/angular.json b/packages/angular_devkit/build_webpack/test/basic-app/angular.json index daae4b2252da..5620c21f7ff8 100644 --- a/packages/angular_devkit/build_webpack/test/basic-app/angular.json +++ b/packages/angular_devkit/build_webpack/test/basic-app/angular.json @@ -4,7 +4,6 @@ "newProjectRoot": "./projects", "cli": {}, "schematics": {}, - "targets": {}, "projects": { "app": { "root": "src", @@ -15,16 +14,16 @@ "build": { "builder": "../../:webpack", "options": { - "webpackConfig": "webpack.config.js" + "webpackConfig": "webpack.config.cjs" } }, "serve": { "builder": "../../:webpack-dev-server", "options": { - "webpackConfig": "webpack.config.js" + "webpackConfig": "webpack.config.cjs" } } } } } -} \ No newline at end of file +} diff --git a/packages/angular_devkit/build_webpack/test/basic-app/src/main.js b/packages/angular_devkit/build_webpack/test/basic-app/src/main.js index 5893f9d0d514..6be02374db11 100644 --- a/packages/angular_devkit/build_webpack/test/basic-app/src/main.js +++ b/packages/angular_devkit/build_webpack/test/basic-app/src/main.js @@ -1 +1 @@ -console.log('hello world'); \ No newline at end of file +console.log('hello world'); diff --git a/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json b/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json index badbab349c48..985646bca926 100644 --- a/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json +++ b/packages/angular_devkit/build_webpack/test/basic-app/tsconfig.json @@ -7,15 +7,10 @@ "declaration": false, "moduleResolution": "node", "experimentalDecorators": true, - "target": "es2017", + "target": "es2022", "module": "esnext", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] + "typeRoots": ["node_modules/@types"], + "lib": ["es2022", "dom"] }, "angularCompilerOptions": { "disableTypeScriptVersionCheck": true diff --git a/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.cjs b/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.cjs new file mode 100644 index 000000000000..b1f988ab2071 --- /dev/null +++ b/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.cjs @@ -0,0 +1,18 @@ +const path = require('path'); + +module.exports = { + mode: 'development', + entry: path.resolve(__dirname, './src/main.js'), + module: { + rules: [ + // rxjs 6 requires directory imports which are not support in ES modules. + // Disabling `fullySpecified` allows Webpack to ignore this but this is + // not ideal because it currently disables ESM behavior import for all JS files. + { test: /\.[m]?js$/, resolve: { fullySpecified: false } }, + ], + }, + output: { + path: path.resolve(__dirname, './dist'), + filename: 'bundle.js', + }, +}; diff --git a/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.js b/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.js deleted file mode 100644 index f107cc39f3c7..000000000000 --- a/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const path = require('path'); - -module.exports = { - mode: 'development', - entry: path.resolve(__dirname, './src/main.js'), - output: { - path: path.resolve(__dirname, './dist'), - filename: 'bundle.js' - } -}; diff --git a/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.mjs b/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.mjs new file mode 100644 index 000000000000..289fff7bdb69 --- /dev/null +++ b/packages/angular_devkit/build_webpack/test/basic-app/webpack.config.mjs @@ -0,0 +1,19 @@ +import { resolve } from 'path'; +import { fileURLToPath } from 'url'; + +export default { + mode: 'development', + entry: resolve(fileURLToPath(import.meta.url), '../src/main.js'), + module: { + rules: [ + // rxjs 6 requires directory imports which are not support in ES modules. + // Disabling `fullySpecified` allows Webpack to ignore this but this is + // not ideal because it currently disables ESM behavior import for all JS files. + { test: /\.[m]?js$/, resolve: { fullySpecified: false } }, + ], + }, + output: { + path: resolve(fileURLToPath(import.meta.url), '../dist'), + filename: 'bundle.js', + }, +}; diff --git a/packages/angular_devkit/core/BUILD.bazel b/packages/angular_devkit/core/BUILD.bazel index d9f09172b1ca..8592428c20c4 100644 --- a/packages/angular_devkit/core/BUILD.bazel +++ b/packages/angular_devkit/core/BUILD.bazel @@ -1,10 +1,7 @@ +load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test") -load("//tools:defaults.bzl", "ts_library") - -# @external_begin -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -# @external_end +load("//tools:defaults.bzl", "pkg_npm", "ts_library") +load("//tools:toolchain_info.bzl", "TOOLCHAINS_NAMES", "TOOLCHAINS_VERSIONS") # Copyright Google Inc. All Rights Reserved. # @@ -12,12 +9,13 @@ load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") # found in the LICENSE file at https://angular.io/license package(default_visibility = ["//visibility:public"]) -licenses(["notice"]) # MIT License +licenses(["notice"]) # @angular-devkit/core ts_library( name = "core", + package_name = "@angular-devkit/core", srcs = glob( include = ["src/**/*.ts"], exclude = [ @@ -28,20 +26,20 @@ ts_library( data = glob( include = ["**/*.json"], # NB: we need to exclude the nested node_modules that is laid out by yarn workspaces - exclude = ["node_modules/**"], + exclude = [ + "node_modules/**", + "src/workspace/json/test/**", + ], ), module_name = "@angular-devkit/core", module_root = "src/index.d.ts", - # The attribute below is needed in g3 to turn off strict typechecking - # strict_checks = False, deps = [ "@npm//@types/node", "@npm//ajv", "@npm//ajv-formats", - "@npm//fast-json-stable-stringify", - "@npm//magic-string", + "@npm//jsonc-parser", "@npm//rxjs", - "@npm//source-map", # @external + "@npm//source-map", # @node_module: typescript:es2015.proxy # @node_module: typescript:es2015.reflect # @node_module: typescript:es2015.symbol.wellknown @@ -49,13 +47,13 @@ ts_library( ], ) +# @external_begin + ts_library( name = "core_test_lib", testonly = True, srcs = glob(["src/**/*_spec.ts"]), data = glob(["src/workspace/json/test/**/*.json"]), - # The attribute below is needed in g3 to turn off strict typechecking - # strict_checks = False, deps = [ ":core", "//packages/angular_devkit/core/node", @@ -63,34 +61,46 @@ ts_library( ], ) -jasmine_node_test( - name = "core_test", - srcs = [":core_test_lib"], - # TODO: Audit tests to determine if tests can be run in RBE environments - local = True, - deps = [ - # @node_module: ajv - # @node_module: fast_json_stable_stringify - # @node_module: magic_string - # @node_module: source_map - ], +[ + jasmine_node_test( + name = "core_test_" + toolchain_name, + srcs = [":core_test_lib"], + tags = [toolchain_name], + toolchain = toolchain, + ) + for toolchain_name, toolchain in zip( + TOOLCHAINS_NAMES, + TOOLCHAINS_VERSIONS, + ) +] + +genrule( + name = "license", + srcs = ["//:LICENSE"], + outs = ["LICENSE"], + cmd = "cp $(execpath //:LICENSE) $@", ) -# @external_begin pkg_npm( name = "npm_package", deps = [ + ":README.md", ":core", + ":license", "//packages/angular_devkit/core/node", + "//packages/angular_devkit/core/node:package.json", "//packages/angular_devkit/core/node/testing", ], ) -pkg_tar( - name = "npm_package_archive", - srcs = [":npm_package"], - extension = "tar.gz", - strip_prefix = "./npm_package", - tags = ["manual"], +api_golden_test_npm_package( + name = "core_api", + data = [ + ":npm_package", + "//goldens:public-api", + ], + golden_dir = "angular_cli/goldens/public-api/angular_devkit/core", + npm_package = "angular_cli/packages/angular_devkit/core/npm_package", + types = ["@npm//@types/node"], ) # @external_end diff --git a/packages/angular_devkit/core/README.md b/packages/angular_devkit/core/README.md index c2821d0e4584..18ebc31ca477 100644 --- a/packages/angular_devkit/core/README.md +++ b/packages/angular_devkit/core/README.md @@ -1,4 +1,5 @@ # Core + > Shared utilities for Angular DevKit. # Exception @@ -8,6 +9,7 @@ ## Schema ### SchemaValidatorResult + ``` export interface SchemaValidatorResult { success: boolean; @@ -62,33 +64,33 @@ export class CoreSchemaRegistry implements SchemaRegistry { The `workspaces` namespace provides an API for interacting with the workspace file formats. It provides an abstraction of the underlying storage format of the workspace and provides -support for both reading and writing. Currently, the only supported format is the JSON-based -format used by the Angular CLI. For this format, the API provides internal change tracking of values which -enables fine-grained updates to the underlying storage of the workspace. This allows for the +support for both reading and writing. Currently, the only supported format is the JSON-based +format used by the Angular CLI. For this format, the API provides internal change tracking of values which +enables fine-grained updates to the underlying storage of the workspace. This allows for the retention of existing formatting and comments. -A workspace is defined via the following object model. Definition collection objects are specialized +A workspace is defined via the following object model. Definition collection objects are specialized Javascript `Map` objects with an additional `add` method to simplify addition and provide more localized error checking of the newly added values. ```ts export interface WorkspaceDefinition { - readonly extensions: Record; - readonly projects: ProjectDefinitionCollection; + readonly extensions: Record; + readonly projects: ProjectDefinitionCollection; } export interface ProjectDefinition { - readonly extensions: Record; - readonly targets: TargetDefinitionCollection; - root: string; - prefix?: string; - sourceRoot?: string; + readonly extensions: Record; + readonly targets: TargetDefinitionCollection; + root: string; + prefix?: string; + sourceRoot?: string; } export interface TargetDefinition { - options?: Record; - configurations?: Record | undefined>; - builder: string; + options?: Record; + configurations?: Record | undefined>; + builder: string; } ``` @@ -106,7 +108,7 @@ export function readWorkspace( path: string, host: WorkspaceHost, format?: WorkspaceFormat, -): Promise<{ workspace: WorkspaceDefinition; }>; +): Promise<{ workspace: WorkspaceDefinition }>; ``` ```ts @@ -118,16 +120,16 @@ export function writeWorkspace( ): Promise; ``` -A `WorkspaceHost` abstracts the underlying data access methods from the functions. It provides -methods to read, write, and analyze paths. A utility function is provided to create +A `WorkspaceHost` abstracts the underlying data access methods from the functions. It provides +methods to read, write, and analyze paths. A utility function is provided to create an instance of a `WorkspaceHost` from the Angular DevKit's virtual filesystem host abstraction. ```ts export interface WorkspaceHost { - readFile(path: string): Promise; - writeFile(path: string, data: string): Promise; - isDirectory(path: string): Promise; - isFile(path: string): Promise; + readFile(path: string): Promise; + writeFile(path: string, data: string): Promise; + isDirectory(path: string): Promise; + isFile(path: string): Promise; } export function createWorkspaceHost(host: virtualFs.Host): WorkspaceHost; @@ -143,23 +145,23 @@ import { NodeJsSyncHost } from '@angular-devkit/core/node'; import { workspaces } from '@angular-devkit/core'; async function demonstrate() { - const host = workspaces.createWorkspaceHost(new NodeJsSyncHost()); - const { workspace } = await workspaces.readWorkspace('path/to/workspace/directory/', host); + const host = workspaces.createWorkspaceHost(new NodeJsSyncHost()); + const { workspace } = await workspaces.readWorkspace('path/to/workspace/directory/', host); - const project = workspace.projects.get('my-app'); - if (!project) { - throw new Error('my-app does not exist'); - } + const project = workspace.projects.get('my-app'); + if (!project) { + throw new Error('my-app does not exist'); + } - const buildTarget = project.targets.get('build'); - if (!buildTarget) { - throw new Error('build target does not exist'); - } + const buildTarget = project.targets.get('build'); + if (!buildTarget) { + throw new Error('build target does not exist'); + } - buildTarget.options.optimization = true; + buildTarget.options.optimization = true; - await workspaces.writeWorkspace(workspace, host); + await workspaces.writeWorkspace(workspace, host); } demonstrate(); -``` \ No newline at end of file +``` diff --git a/packages/angular_devkit/core/node/BUILD.bazel b/packages/angular_devkit/core/node/BUILD.bazel index 4b36f7b7e60b..68bd7f28d27f 100644 --- a/packages/angular_devkit/core/node/BUILD.bazel +++ b/packages/angular_devkit/core/node/BUILD.bazel @@ -5,8 +5,9 @@ load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test") load("//tools:defaults.bzl", "ts_library") +load("//tools:toolchain_info.bzl", "TOOLCHAINS_NAMES", "TOOLCHAINS_VERSIONS") -licenses(["notice"]) # MIT License +licenses(["notice"]) package(default_visibility = ["//visibility:public"]) @@ -20,17 +21,19 @@ ts_library( "**/*_benchmark.ts", ], ), + data = ["package.json"], module_name = "@angular-devkit/core/node", module_root = "index.d.ts", - # The attribute below is needed in g3 to turn off strict typechecking - # strict_checks = False, deps = [ "//packages/angular_devkit/core", "@npm//@types/node", + "@npm//chokidar", "@npm//rxjs", ], ) +# @external_begin + ts_library( name = "node_test_lib", testonly = True, @@ -45,21 +48,23 @@ ts_library( deps = [ ":node", "//packages/angular_devkit/core", - "//tests/angular_devkit/core/node/jobs:jobs_test_lib", "@npm//rxjs", ], ) -jasmine_node_test( - name = "node_test", - srcs = [":node_test_lib"], - # TODO: Audit tests to determine if tests can be run in RBE environments - local = True, - deps = [ - "@npm//chokidar", - "@npm//temp", - # @node_module: ajv - # @node_module: fast_json_stable_stringify - # @node_module: magic_string - ], -) +[ + jasmine_node_test( + name = "node_test_" + toolchain_name, + srcs = [":node_test_lib"], + tags = [toolchain_name], + toolchain = toolchain, + deps = [ + "@npm//chokidar", + ], + ) + for toolchain_name, toolchain in zip( + TOOLCHAINS_NAMES, + TOOLCHAINS_VERSIONS, + ) +] +# @external_end diff --git a/packages/angular_devkit/core/node/_golden-api.ts b/packages/angular_devkit/core/node/_golden-api.ts deleted file mode 100644 index 1e8277d996a8..000000000000 --- a/packages/angular_devkit/core/node/_golden-api.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -// Start experimental namespace -// Start jobs namespace -export * from './experimental/jobs/job-registry'; -// End jobs namespace -// End experimental namespace - -export * from './fs'; -export * from './cli-logger'; -export * from './host'; diff --git a/packages/angular_devkit/core/node/cli-logger.ts b/packages/angular_devkit/core/node/cli-logger.ts index 07cb5ef39aa9..2ff2e1d145ef 100644 --- a/packages/angular_devkit/core/node/cli-logger.ts +++ b/packages/angular_devkit/core/node/cli-logger.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { filter } from 'rxjs/operators'; import { logging } from '../src'; @@ -23,48 +24,46 @@ export function createConsoleLogger( ): logging.Logger { const logger = new logging.IndentLogger('cling'); - logger - .pipe(filter(entry => entry.level !== 'debug' || verbose)) - .subscribe(entry => { - const color = colors && colors[entry.level]; - let output = stdout; + logger.pipe(filter((entry) => entry.level !== 'debug' || verbose)).subscribe((entry) => { + const color = colors && colors[entry.level]; + let output = stdout; - switch (entry.level) { - case 'warn': - case 'fatal': - case 'error': - output = stderr; - break; - } + switch (entry.level) { + case 'warn': + case 'fatal': + case 'error': + output = stderr; + break; + } - // If we do console.log(message) or process.stdout.write(message + '\n'), the process might - // stop before the whole message is written and the stream is flushed. This happens when - // streams are asynchronous. - // - // NodeJS IO streams are different depending on platform and usage. In POSIX environment, - // for example, they're asynchronous when writing to a pipe, but synchronous when writing - // to a TTY. In windows, it's the other way around. You can verify which is which with - // stream.isTTY and platform, but this is not good enough. - // In the async case, one should wait for the callback before sending more data or - // continuing the process. In our case it would be rather hard to do (but not impossible). - // - // Instead we take the easy way out and simply chunk the message and call the write - // function while the buffer drain itself asynchronously. With a smaller chunk size than - // the buffer, we are mostly certain that it works. In this case, the chunk has been picked - // as half a page size (4096/2 = 2048), minus some bytes for the color formatting. - // On POSIX it seems the buffer is 2 pages (8192), but just to be sure (could be different - // by platform). - // - // For more details, see https://nodejs.org/api/process.html#process_a_note_on_process_i_o - const chunkSize = 2000; // Small chunk. - let message = entry.message; - while (message) { - const chunk = message.slice(0, chunkSize); - message = message.slice(chunkSize); - output.write(color ? color(chunk) : chunk); - } - output.write('\n'); - }); + // If we do console.log(message) or process.stdout.write(message + '\n'), the process might + // stop before the whole message is written and the stream is flushed. This happens when + // streams are asynchronous. + // + // NodeJS IO streams are different depending on platform and usage. In POSIX environment, + // for example, they're asynchronous when writing to a pipe, but synchronous when writing + // to a TTY. In windows, it's the other way around. You can verify which is which with + // stream.isTTY and platform, but this is not good enough. + // In the async case, one should wait for the callback before sending more data or + // continuing the process. In our case it would be rather hard to do (but not impossible). + // + // Instead we take the easy way out and simply chunk the message and call the write + // function while the buffer drain itself asynchronously. With a smaller chunk size than + // the buffer, we are mostly certain that it works. In this case, the chunk has been picked + // as half a page size (4096/2 = 2048), minus some bytes for the color formatting. + // On POSIX it seems the buffer is 2 pages (8192), but just to be sure (could be different + // by platform). + // + // For more details, see https://nodejs.org/api/process.html#process_a_note_on_process_i_o + const chunkSize = 2000; // Small chunk. + let message = entry.message; + while (message) { + const chunk = message.slice(0, chunkSize); + message = message.slice(chunkSize); + output.write(color ? color(chunk) : chunk); + } + output.write('\n'); + }); return logger; } diff --git a/packages/angular_devkit/core/node/experimental/index.ts b/packages/angular_devkit/core/node/experimental/index.ts deleted file mode 100644 index 012ac72a4ae5..000000000000 --- a/packages/angular_devkit/core/node/experimental/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as jobs from './jobs'; - -export { - jobs, -}; diff --git a/packages/angular_devkit/core/node/experimental/jobs/index.ts b/packages/angular_devkit/core/node/experimental/jobs/index.ts deleted file mode 100644 index d931a0289414..000000000000 --- a/packages/angular_devkit/core/node/experimental/jobs/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export * from './job-registry'; diff --git a/packages/angular_devkit/core/node/experimental/jobs/job-registry.ts b/packages/angular_devkit/core/node/experimental/jobs/job-registry.ts deleted file mode 100644 index 6c60ef8c5069..000000000000 --- a/packages/angular_devkit/core/node/experimental/jobs/job-registry.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Observable, of } from 'rxjs'; -import { JsonValue, experimental as core_experimental, schema } from '../../../src'; - -export class NodeModuleJobRegistry implements core_experimental.jobs.Registry { - - protected _resolve(name: string): string | null { - try { - return require.resolve(name); - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - return null; - } - throw e; - } - } - - /** - * Get a job description for a named job. - * - * @param name The name of the job. - * @returns A description, or null if the job is not registered. - */ - get
( - name: core_experimental.jobs.JobName, - ): Observable | null> { - const [moduleName, exportName] = name.split(/#/, 2); - - const resolvedPath = this._resolve(moduleName); - if (!resolvedPath) { - return of(null); - } - - const pkg = require(resolvedPath); - const handler = pkg[exportName || 'default']; - if (!handler) { - return of(null); - } - - function _getValue(...fields: unknown[]) { - return fields.find(x => schema.isJsonSchema(x)) || true; - } - - const argument = _getValue(pkg.argument, handler.argument); - const input = _getValue(pkg.input, handler.input); - const output = _getValue(pkg.output, handler.output); - const channels = _getValue(pkg.channels, handler.channels); - - return of(Object.assign(handler.bind(undefined), { - jobDescription: { - argument, - input, - output, - channels, - }, - })); - } -} diff --git a/packages/angular_devkit/core/node/experimental/jobs/job-registry_spec.ts b/packages/angular_devkit/core/node/experimental/jobs/job-registry_spec.ts deleted file mode 100644 index 2a46aa873995..000000000000 --- a/packages/angular_devkit/core/node/experimental/jobs/job-registry_spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as path from 'path'; -import { experimental as core_experimental } from '../../../src'; -import { NodeModuleJobRegistry } from './job-registry'; - -const root = path.join( - path.dirname(require.resolve(__filename)), - '../../../../../../tests/angular_devkit/core/node/jobs', -); - - -describe('NodeModuleJobScheduler', () => { - it('works', async () => { - const registry = new NodeModuleJobRegistry(); - const scheduler = new core_experimental.jobs.SimpleScheduler(registry); - - const job = scheduler.schedule(path.join(root, 'add'), [1, 2, 3]); - expect(await job.output.toPromise()).toBe(6); - }); -}); diff --git a/packages/angular_devkit/core/node/fs.ts b/packages/angular_devkit/core/node/fs.ts deleted file mode 100644 index a09048e9a87d..000000000000 --- a/packages/angular_devkit/core/node/fs.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { statSync } from 'fs'; - -/** @deprecated Since v11.0, unused by the Angular tooling */ -export function isFile(filePath: string): boolean { - let stat; - try { - stat = statSync(filePath); - } catch (e) { - if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) { - return false; - } - throw e; - } - - return stat.isFile() || stat.isFIFO(); -} - -/** @deprecated Since v11.0, unused by the Angular tooling */ -export function isDirectory(filePath: string): boolean { - let stat; - try { - stat = statSync(filePath); - } catch (e) { - if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) { - return false; - } - throw e; - } - - return stat.isDirectory(); -} diff --git a/packages/angular_devkit/core/node/host.ts b/packages/angular_devkit/core/node/host.ts index 6a525e079827..744eb4f1b211 100644 --- a/packages/angular_devkit/core/node/host.ts +++ b/packages/angular_devkit/core/node/host.ts @@ -1,56 +1,29 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { + +import type { FSWatcher as ChokidarWatcher } from 'chokidar'; +import fs, { PathLike, Stats, constants, existsSync, - mkdirSync, promises as fsPromises, + mkdirSync, readFileSync, readdirSync, renameSync, - rmdirSync, statSync, - unlinkSync, writeFileSync, } from 'fs'; -import { dirname as pathDirname, join as pathJoin } from 'path'; -import { Observable, concat, from as observableFrom, of, throwError } from 'rxjs'; -import { - concatMap, - map, - mergeMap, - publish, - refCount, -} from 'rxjs/operators'; -import { - Path, - PathFragment, - dirname, - fragment, - getSystemPath, - join, - normalize, - virtualFs, -} from '../src'; - - -interface ChokidarWatcher { - new(options: {}): ChokidarWatcher; - - add(path: string): ChokidarWatcher; - on(type: 'change', cb: (path: string) => void): ChokidarWatcher; - on(type: 'add', cb: (path: string) => void): ChokidarWatcher; - on(type: 'unlink', cb: (path: string) => void): ChokidarWatcher; - - close(): void; -} +import { dirname as pathDirname } from 'path'; +import { Observable, from as observableFrom } from 'rxjs'; +import { map, mergeMap, publish, refCount } from 'rxjs/operators'; +import { Path, PathFragment, dirname, fragment, getSystemPath, normalize, virtualFs } from '../src'; async function exists(path: PathLike): Promise { try { @@ -65,16 +38,18 @@ async function exists(path: PathLike): Promise { // This will only be initialized if the watch() method is called. // Otherwise chokidar appears only in type positions, and shouldn't be referenced // in the JavaScript output. -let FSWatcher: ChokidarWatcher; +let FSWatcher: typeof ChokidarWatcher; function loadFSWatcher() { if (!FSWatcher) { try { - // tslint:disable-next-line:no-implicit-dependencies + // eslint-disable-next-line import/no-extraneous-dependencies FSWatcher = require('chokidar').FSWatcher; } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') { - throw new Error('As of angular-devkit version 8.0, the "chokidar" package ' + - 'must be installed in order to use watch() features.'); + if ((e as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') { + throw new Error( + 'As of angular-devkit version 8.0, the "chokidar" package ' + + 'must be installed in order to use watch() features.', + ); } throw e; } @@ -91,42 +66,20 @@ export class NodeJsAsyncHost implements virtualFs.Host { } write(path: Path, content: virtualFs.FileBuffer): Observable { - return observableFrom(fsPromises.mkdir(getSystemPath(dirname(path)), { recursive: true })) - .pipe( - mergeMap(() => fsPromises.writeFile(getSystemPath(path), content)), - ); + return observableFrom(fsPromises.mkdir(getSystemPath(dirname(path)), { recursive: true })).pipe( + mergeMap(() => fsPromises.writeFile(getSystemPath(path), new Uint8Array(content))), + ); } read(path: Path): Observable { - return observableFrom(fsPromises.readFile(getSystemPath(path))) - .pipe( - map(buffer => new Uint8Array(buffer).buffer as virtualFs.FileBuffer), - ); + return observableFrom(fsPromises.readFile(getSystemPath(path))).pipe( + map((buffer) => new Uint8Array(buffer).buffer as virtualFs.FileBuffer), + ); } delete(path: Path): Observable { - return this.isDirectory(path).pipe( - mergeMap(async isDirectory => { - if (isDirectory) { - const recursiveDelete = async (dirPath: string) => { - for (const fragment of (await fsPromises.readdir(dirPath))) { - const sysPath = pathJoin(dirPath, fragment); - const stats = await fsPromises.stat(sysPath); - - if (stats.isDirectory()) { - await recursiveDelete(sysPath); - await fsPromises.rmdir(sysPath); - } else { - await fsPromises.unlink(sysPath); - } - } - }; - - await recursiveDelete(getSystemPath(path)); - } else { - await fsPromises.unlink(getSystemPath(path)); - } - }), + return observableFrom( + fsPromises.rm(getSystemPath(path), { force: true, recursive: true, maxRetries: 3 }), ); } @@ -136,23 +89,20 @@ export class NodeJsAsyncHost implements virtualFs.Host { list(path: Path): Observable { return observableFrom(fsPromises.readdir(getSystemPath(path))).pipe( - map((names) => names.map(name => fragment(name))), + map((names) => names.map((name) => fragment(name))), ); } exists(path: Path): Observable { - return observableFrom(exists(path)); + return observableFrom(exists(getSystemPath(path))); } isDirectory(path: Path): Observable { - return this.stat(path).pipe( - map(stat => stat.isDirectory()), - ); + return this.stat(path).pipe(map((stat) => stat.isDirectory())); } + isFile(path: Path): Observable { - return this.stat(path).pipe( - map(stat => stat.isFile()), - ); + return this.stat(path).pipe(map((stat) => stat.isFile())); } // Some hosts may not support stat. @@ -165,26 +115,27 @@ export class NodeJsAsyncHost implements virtualFs.Host { path: Path, _options?: virtualFs.HostWatchOptions, ): Observable | null { - return new Observable(obs => { + return new Observable((obs) => { loadFSWatcher(); - const watcher = new FSWatcher({ persistent: true }).add(getSystemPath(path)); + const watcher = new FSWatcher({ persistent: true }); + watcher.add(getSystemPath(path)); watcher - .on('change', path => { + .on('change', (path) => { obs.next({ path: normalize(path), time: new Date(), type: virtualFs.HostWatchEventType.Changed, }); }) - .on('add', path => { + .on('add', (path) => { obs.next({ path: normalize(path), time: new Date(), type: virtualFs.HostWatchEventType.Created, }); }) - .on('unlink', path => { + .on('unlink', (path) => { obs.next({ path: normalize(path), time: new Date(), @@ -193,14 +144,10 @@ export class NodeJsAsyncHost implements virtualFs.Host { }); return () => watcher.close(); - }).pipe( - publish(), - refCount(), - ); + }).pipe(publish(), refCount()); } } - /** * An implementation of the Virtual FS using Node as the backend, synchronously. */ @@ -210,7 +157,7 @@ export class NodeJsSyncHost implements virtualFs.Host { } write(path: Path, content: virtualFs.FileBuffer): Observable { - return new Observable(obs => { + return new Observable((obs) => { mkdirSync(getSystemPath(dirname(path)), { recursive: true }); writeFileSync(getSystemPath(path), new Uint8Array(content)); obs.next(); @@ -219,7 +166,7 @@ export class NodeJsSyncHost implements virtualFs.Host { } read(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { const buffer = readFileSync(getSystemPath(path)); obs.next(new Uint8Array(buffer).buffer as virtualFs.FileBuffer); @@ -228,34 +175,15 @@ export class NodeJsSyncHost implements virtualFs.Host { } delete(path: Path): Observable { - return this.isDirectory(path).pipe( - concatMap(isDir => { - if (isDir) { - const dirPaths = readdirSync(getSystemPath(path)); - const rmDirComplete = new Observable((obs) => { - rmdirSync(getSystemPath(path)); - obs.complete(); - }); + return new Observable((obs) => { + fs.rmSync(getSystemPath(path), { force: true, recursive: true, maxRetries: 3 }); - return concat( - ...dirPaths.map(name => this.delete(join(path, name))), - rmDirComplete, - ); - } else { - try { - unlinkSync(getSystemPath(path)); - } catch (err) { - return throwError(err); - } - - return of(undefined); - } - }), - ); + obs.complete(); + }); } rename(from: Path, to: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { const toSystemPath = getSystemPath(to); mkdirSync(pathDirname(toSystemPath), { recursive: true }); renameSync(getSystemPath(from), toSystemPath); @@ -265,32 +193,33 @@ export class NodeJsSyncHost implements virtualFs.Host { } list(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { const names = readdirSync(getSystemPath(path)); - obs.next(names.map(name => fragment(name))); + obs.next(names.map((name) => fragment(name))); obs.complete(); }); } exists(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { obs.next(existsSync(getSystemPath(path))); obs.complete(); }); } isDirectory(path: Path): Observable { - // tslint:disable-next-line:no-non-null-assertion - return this.stat(path)!.pipe(map(stat => stat.isDirectory())); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.stat(path)!.pipe(map((stat) => stat.isDirectory())); } + isFile(path: Path): Observable { - // tslint:disable-next-line:no-non-null-assertion - return this.stat(path)!.pipe(map(stat => stat.isFile())); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.stat(path)!.pipe(map((stat) => stat.isFile())); } // Some hosts may not support stat. stat(path: Path): Observable> { - return new Observable(obs => { + return new Observable((obs) => { obs.next(statSync(getSystemPath(path))); obs.complete(); }); @@ -301,27 +230,27 @@ export class NodeJsSyncHost implements virtualFs.Host { path: Path, _options?: virtualFs.HostWatchOptions, ): Observable | null { - return new Observable(obs => { - const opts = { persistent: false }; + return new Observable((obs) => { loadFSWatcher(); - const watcher = new FSWatcher(opts).add(getSystemPath(path)); + const watcher = new FSWatcher({ persistent: false }); + watcher.add(getSystemPath(path)); watcher - .on('change', path => { + .on('change', (path) => { obs.next({ path: normalize(path), time: new Date(), type: virtualFs.HostWatchEventType.Changed, }); }) - .on('add', path => { + .on('add', (path) => { obs.next({ path: normalize(path), time: new Date(), type: virtualFs.HostWatchEventType.Created, }); }) - .on('unlink', path => { + .on('unlink', (path) => { obs.next({ path: normalize(path), time: new Date(), @@ -330,9 +259,6 @@ export class NodeJsSyncHost implements virtualFs.Host { }); return () => watcher.close(); - }).pipe( - publish(), - refCount(), - ); + }).pipe(publish(), refCount()); } } diff --git a/packages/angular_devkit/core/node/host_spec.ts b/packages/angular_devkit/core/node/host_spec.ts index 7ac9615b5db3..078a881ea5ba 100644 --- a/packages/angular_devkit/core/node/host_spec.ts +++ b/packages/angular_devkit/core/node/host_spec.ts @@ -1,20 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any -// tslint:disable:no-non-null-assertion -// tslint:disable:no-implicit-dependencies + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable import/no-extraneous-dependencies */ import { normalize, virtualFs } from '@angular-devkit/core'; import { NodeJsAsyncHost, NodeJsSyncHost } from '@angular-devkit/core/node'; import * as fs from 'fs'; -import { Observable, Subscription } from 'rxjs'; - -const temp = require('temp'); - +import { tmpdir } from 'os'; +import { join } from 'path'; // TODO: replace this with an "it()" macro that's reusable globally. let linuxOnlyIt: typeof it = it; @@ -22,106 +20,113 @@ if (process.platform.startsWith('win') || process.platform.startsWith('darwin')) linuxOnlyIt = xit; } - describe('NodeJsAsyncHost', () => { let root: string; let host: virtualFs.Host; beforeEach(() => { - root = temp.mkdirSync('core-node-spec-'); + root = fs.mkdtempSync(join(fs.realpathSync(tmpdir()), 'core-node-spec-')); host = new virtualFs.ScopedHost(new NodeJsAsyncHost(), normalize(root)); }); - afterEach(done => host.delete(normalize('/')).toPromise().then(done, done.fail)); - - linuxOnlyIt('can watch', done => { - let obs: Observable; - let subscription: Subscription; - const content = virtualFs.stringToFileBuffer('hello world'); - const content2 = virtualFs.stringToFileBuffer('hello world 2'); - const allEvents: virtualFs.HostWatchEvent[] = []; - - Promise.resolve() - .then(() => fs.mkdirSync(root + '/sub1')) - .then(() => fs.writeFileSync(root + '/sub1/file1', 'hello world')) - .then(() => { - obs = host.watch(normalize('/sub1'), { recursive: true }) !; - expect(obs).not.toBeNull(); - subscription = obs.subscribe(event => { allEvents.push(event); }); - }) - .then(() => new Promise(resolve => setTimeout(resolve, 100))) + + afterEach(async () => host.delete(normalize('/')).toPromise()); + + it('should get correct result for exists', async () => { + const filePath = normalize('not-found'); + expect(await host.exists(filePath).toPromise()).toBeFalse(); + await host.write(filePath, virtualFs.stringToFileBuffer('content')).toPromise(); + expect(await host.exists(filePath).toPromise()).toBeTrue(); + }); + + linuxOnlyIt( + 'can watch', + async () => { + const content = virtualFs.stringToFileBuffer('hello world'); + const content2 = virtualFs.stringToFileBuffer('hello world 2'); + const allEvents: virtualFs.HostWatchEvent[] = []; + + fs.mkdirSync(root + '/sub1'); + fs.writeFileSync(root + '/sub1/file1', 'hello world'); + + const obs = host.watch(normalize('/sub1'), { recursive: true }); + expect(obs).toBeDefined(); + const subscription = obs!.subscribe((event) => { + allEvents.push(event); + }); + await new Promise((resolve) => setTimeout(resolve, 100)); + // Discard the events registered so far. - .then(() => allEvents.splice(0)) - .then(() => host.write(normalize('/sub1/sub2/file3'), content).toPromise()) - .then(() => host.write(normalize('/sub1/file2'), content2).toPromise()) - .then(() => host.delete(normalize('/sub1/file1')).toPromise()) - .then(() => new Promise(resolve => setTimeout(resolve, 2000))) - .then(() => { - expect(allEvents.length).toBe(3); - subscription.unsubscribe(); - }) - .then(done, done.fail); - }, 30000); + allEvents.splice(0); + await host.write(normalize('/sub1/sub2/file3'), content).toPromise(); + await host.write(normalize('/sub1/file2'), content2).toPromise(); + await host.delete(normalize('/sub1/file1')).toPromise(); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + expect(allEvents.length).toBe(3); + subscription.unsubscribe(); + }, + 30000, + ); }); - describe('NodeJsSyncHost', () => { let root: string; let host: virtualFs.SyncDelegateHost; beforeEach(() => { - root = temp.mkdirSync('core-node-spec-'); + root = fs.mkdtempSync(join(fs.realpathSync(tmpdir()), 'core-node-spec-')); host = new virtualFs.SyncDelegateHost( - new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(root))); + new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(root)), + ); }); afterEach(() => { host.delete(normalize('/')); }); - linuxOnlyIt('can watch', done => { - let obs: Observable; - let subscription: Subscription; - const content = virtualFs.stringToFileBuffer('hello world'); - const content2 = virtualFs.stringToFileBuffer('hello world 2'); - const allEvents: virtualFs.HostWatchEvent[] = []; - - Promise.resolve() - .then(() => fs.mkdirSync(root + '/sub1')) - .then(() => fs.writeFileSync(root + '/sub1/file1', 'hello world')) - .then(() => { - obs = host.watch(normalize('/sub1'), { recursive: true })!; - expect(obs).not.toBeNull(); - subscription = obs.subscribe(event => { allEvents.push(event); }); - }) - .then(() => new Promise(resolve => setTimeout(resolve, 100))) - // Discard the events registered so far. - .then(() => allEvents.splice(0)) - .then(() => { - host.write(normalize('/sub1/sub2/file3'), content); - host.write(normalize('/sub1/file2'), content2); - host.delete(normalize('/sub1/file1')); - }) - .then(() => new Promise(resolve => setTimeout(resolve, 2000))) - .then(() => { - expect(allEvents.length).toBe(3); - subscription.unsubscribe(); - }) - .then(done, done.fail); - }, 30000); - - linuxOnlyIt('rename to a non-existing dir', done => { - - Promise.resolve() - .then(() => fs.mkdirSync(root + '/rename')) - .then(() => fs.writeFileSync(root + '/rename/a.txt', 'hello world')) - .then(() => { - host.rename(normalize('/rename/a.txt'), normalize('/rename/b/c/d/a.txt')); - if (fs.existsSync(root + '/rename/b/c/d/a.txt')) { - const resContent = host.read(normalize('/rename/b/c/d/a.txt')); - const content = virtualFs.fileBufferToString(resContent); - expect(content).toEqual('hello world'); - } - }) - .then(done, done.fail); - }, 30000); + linuxOnlyIt( + 'can watch', + async () => { + const content = virtualFs.stringToFileBuffer('hello world'); + const content2 = virtualFs.stringToFileBuffer('hello world 2'); + const allEvents: virtualFs.HostWatchEvent[] = []; + + fs.mkdirSync(root + '/sub1'); + fs.writeFileSync(root + '/sub1/file1', 'hello world'); + const obs = host.watch(normalize('/sub1'), { recursive: true }); + expect(obs).toBeDefined(); + const subscription = obs!.subscribe((event) => { + allEvents.push(event); + }); + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Discard the events registered so far. + allEvents.splice(0); + + host.write(normalize('/sub1/sub2/file3'), content); + host.write(normalize('/sub1/file2'), content2); + host.delete(normalize('/sub1/file1')); + await new Promise((resolve) => setTimeout(resolve, 2000)); + + expect(allEvents.length).toBe(3); + subscription.unsubscribe(); + }, + 30000, + ); + + linuxOnlyIt( + 'rename to a non-existing dir', + () => { + fs.mkdirSync(root + '/rename'); + fs.writeFileSync(root + '/rename/a.txt', 'hello world'); + + host.rename(normalize('/rename/a.txt'), normalize('/rename/b/c/d/a.txt')); + if (fs.existsSync(root + '/rename/b/c/d/a.txt')) { + const resContent = host.read(normalize('/rename/b/c/d/a.txt')); + const content = virtualFs.fileBufferToString(resContent); + expect(content).toEqual('hello world'); + } + }, + 30000, + ); }); diff --git a/packages/angular_devkit/core/node/index.ts b/packages/angular_devkit/core/node/index.ts index 3f3e175f4315..faedea608b52 100644 --- a/packages/angular_devkit/core/node/index.ts +++ b/packages/angular_devkit/core/node/index.ts @@ -1,17 +1,10 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as experimental from './experimental/jobs/job-registry'; -import * as fs from './fs'; export * from './cli-logger'; export * from './host'; - -export { - experimental, - fs, -}; diff --git a/packages/angular_devkit/core/node/package.json b/packages/angular_devkit/core/node/package.json new file mode 100644 index 000000000000..87cb5f4a92c5 --- /dev/null +++ b/packages/angular_devkit/core/node/package.json @@ -0,0 +1,5 @@ +{ + "name": "@angular-devkit/core/node", + "main": "index.js", + "typings": "index.d.ts" +} diff --git a/packages/angular_devkit/core/node/testing/BUILD.bazel b/packages/angular_devkit/core/node/testing/BUILD.bazel index 1b5afd401089..b290e69eb16e 100644 --- a/packages/angular_devkit/core/node/testing/BUILD.bazel +++ b/packages/angular_devkit/core/node/testing/BUILD.bazel @@ -4,7 +4,7 @@ load("//tools:defaults.bzl", "ts_library") # # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.io/license -licenses(["notice"]) # MIT License +licenses(["notice"]) package(default_visibility = ["//visibility:public"]) @@ -19,8 +19,6 @@ ts_library( ), module_name = "@angular-devkit/core/node/testing", module_root = "index.d.ts", - # The attribute below is needed in g3 to turn off strict typechecking - # strict_checks = False, deps = [ "//packages/angular_devkit/core", "//packages/angular_devkit/core/node", diff --git a/packages/angular_devkit/core/node/testing/index.ts b/packages/angular_devkit/core/node/testing/index.ts index d8a7dbea4c6b..687bbf64ca72 100644 --- a/packages/angular_devkit/core/node/testing/index.ts +++ b/packages/angular_devkit/core/node/testing/index.ts @@ -1,14 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { Path, PathFragment, getSystemPath, join, normalize, virtualFs } from '../../src'; +import { Path, getSystemPath, join, normalize, virtualFs } from '../../src'; import { NodeJsSyncHost } from '../host'; /** @@ -16,7 +17,7 @@ import { NodeJsSyncHost } from '../host'; */ export class TempScopedNodeJsSyncHost extends virtualFs.ScopedHost { protected _sync?: virtualFs.SyncDelegateHost; - protected _root: Path; + protected override _root: Path; constructor() { const root = normalize(path.join(os.tmpdir(), `devkit-host-${+Date.now()}-${process.pid}`)); @@ -29,7 +30,8 @@ export class TempScopedNodeJsSyncHost extends virtualFs.ScopedHost { get files(): Path[] { const sync = this.sync; function _visit(p: Path): Path[] { - return sync.list(p) + return sync + .list(p) .map((fragment) => join(p, fragment)) .reduce((files, path) => { if (sync.isDirectory(path)) { @@ -43,7 +45,9 @@ export class TempScopedNodeJsSyncHost extends virtualFs.ScopedHost { return _visit(normalize('/')); } - get root() { return this._root; } + get root() { + return this._root; + } get sync() { if (!this._sync) { this._sync = new virtualFs.SyncDelegateHost(this); diff --git a/packages/angular_devkit/core/package.json b/packages/angular_devkit/core/package.json index c24f1da0bf45..5985806f6826 100644 --- a/packages/angular_devkit/core/package.json +++ b/packages/angular_devkit/core/package.json @@ -1,6 +1,6 @@ { "name": "@angular-devkit/core", - "version": "0.0.0", + "version": "0.0.0-PLACEHOLDER", "description": "Angular DevKit - Core Utility Library", "main": "src/index.js", "typings": "src/index.d.ts", @@ -8,14 +8,18 @@ "core" ], "dependencies": { - "ajv": "8.1.0", - "ajv-formats": "2.0.2", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", + "ajv-formats": "2.1.1", + "ajv": "8.11.0", + "jsonc-parser": "3.2.0", "rxjs": "6.6.7", - "source-map": "0.7.3" + "source-map": "0.7.4" }, - "devDependencies": { - "seedrandom": "^3.0.0" + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } } diff --git a/packages/angular_devkit/core/src/_golden-api.ts b/packages/angular_devkit/core/src/_golden-api.ts deleted file mode 100644 index 0c8c1c52f68d..000000000000 --- a/packages/angular_devkit/core/src/_golden-api.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export * from './exception/exception'; - -// Start json namespace -export * from './json/interface'; -export * from './json/parser'; -export * from './json/schema/interface'; -export * from './json/schema/pointer'; -export * from './json/schema/registry'; -export * from './json/schema/visitor'; -export * from './json/schema/utility'; -export * from './json/schema/transforms'; -// End json namespace - -// Start logging namespace -export * from './logger/indent'; -export * from './logger/level'; -export * from './logger/logger'; -export * from './logger/null-logger'; -export * from './logger/transform-logger'; -// End logging namespace - -// Start utils namespace -export * from './utils/literals'; -export * from './utils/strings'; -export * from './utils/array'; -export * from './utils/object'; -export * from './utils/template'; -export * from './utils/partially-ordered-set'; -export * from './utils/priority-queue'; -export * from './utils/lang'; -// End utils namespace - -// Start virtualFs namespace -export * from './virtual-fs/path'; -export * from './virtual-fs/host/index'; -// End virtualFs namespace - -// Start workspace namespace -export * from './workspace/index'; -// End workspace namespace - -// Start analytics namespace -export * from './analytics/index'; -// End analytics namespace diff --git a/packages/angular_devkit/core/src/analytics/api.ts b/packages/angular_devkit/core/src/analytics/api.ts deleted file mode 100644 index f538d1ba088a..000000000000 --- a/packages/angular_devkit/core/src/analytics/api.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export interface CustomDimensionsAndMetricsOptions { - dimensions?: (boolean | number | string)[]; - metrics?: (boolean | number | string)[]; -} - -export interface EventOptions extends CustomDimensionsAndMetricsOptions { - label?: string; - value?: string; -} - -export interface ScreenviewOptions extends CustomDimensionsAndMetricsOptions { - appVersion?: string; - appId?: string; - appInstallerId?: string; -} - -export interface PageviewOptions extends CustomDimensionsAndMetricsOptions { - hostname?: string; - title?: string; -} - -export interface TimingOptions extends CustomDimensionsAndMetricsOptions { - label?: string; -} - -/** - * Interface for managing analytics. This is highly platform dependent, and mostly matches - * Google Analytics. The reason the interface is here is to remove the dependency to an - * implementation from most other places. - * - * The methods exported from this interface more or less match those needed by us in the - * universal analytics package, see https://unpkg.com/@types/universal-analytics@0.4.2/index.d.ts - * for typings. We mostly named arguments to make it easier to follow, but didn't change or - * add any semantics to those methods. They're mapping GA and u-a one for one. - * - * The Angular CLI (or any other kind of backend) should forward it to some compatible backend. - */ -export interface Analytics { - event(category: string, action: string, options?: EventOptions): void; - screenview(screenName: string, appName: string, options?: ScreenviewOptions): void; - pageview(path: string, options?: PageviewOptions): void; - timing(category: string, variable: string, time: string | number, options?: TimingOptions): void; - - flush(): Promise; -} diff --git a/packages/angular_devkit/core/src/analytics/forwarder.ts b/packages/angular_devkit/core/src/analytics/forwarder.ts deleted file mode 100644 index 8c1ba6de6a92..000000000000 --- a/packages/angular_devkit/core/src/analytics/forwarder.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { JsonObject } from '../json'; -import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api'; - - -export enum AnalyticsReportKind { - Event = 'event', - Screenview = 'screenview', - Pageview = 'pageview', - Timing = 'timing', -} - -export interface AnalyticsReportBase extends JsonObject { - kind: AnalyticsReportKind; -} - -export interface AnalyticsReportEvent extends AnalyticsReportBase { - kind: AnalyticsReportKind.Event; - options: JsonObject & EventOptions; - category: string; - action: string; -} -export interface AnalyticsReportScreenview extends AnalyticsReportBase { - kind: AnalyticsReportKind.Screenview; - options: JsonObject & ScreenviewOptions; - screenName: string; - appName: string; -} -export interface AnalyticsReportPageview extends AnalyticsReportBase { - kind: AnalyticsReportKind.Pageview; - options: JsonObject & PageviewOptions; - path: string; -} -export interface AnalyticsReportTiming extends AnalyticsReportBase { - kind: AnalyticsReportKind.Timing; - options: JsonObject & TimingOptions; - category: string; - variable: string; - time: string | number; -} - -export type AnalyticsReport = - AnalyticsReportEvent - | AnalyticsReportScreenview - | AnalyticsReportPageview - | AnalyticsReportTiming - ; - -/** - * A function that can forward analytics along some stream. AnalyticsReport is already a - * JsonObject descendant, but we force it here so the user knows it's safe to serialize. - */ -export type AnalyticsForwarderFn = (report: JsonObject & AnalyticsReport) => void; - -/** - * A class that follows the Analytics interface and forwards analytic reports (JavaScript objects). - * AnalyticsReporter is the counterpart which takes analytic reports and report them to another - * Analytics interface. - */ -export class ForwardingAnalytics implements Analytics { - constructor(protected _fn: AnalyticsForwarderFn) {} - - event(category: string, action: string, options?: EventOptions) { - this._fn({ - kind: AnalyticsReportKind.Event, - category, - action, - options: { ...options } as JsonObject, - }); - } - screenview(screenName: string, appName: string, options?: ScreenviewOptions) { - this._fn({ - kind: AnalyticsReportKind.Screenview, - screenName, - appName, - options: { ...options } as JsonObject, - }); - } - pageview(path: string, options?: PageviewOptions) { - this._fn({ - kind: AnalyticsReportKind.Pageview, - path, - options: { ...options } as JsonObject, - }); - } - timing(category: string, variable: string, time: string | number, options?: TimingOptions): void { - this._fn({ - kind: AnalyticsReportKind.Timing, - category, - variable, - time, - options: { ...options } as JsonObject, - }); - } - - // We do not support flushing. - flush() { - return Promise.resolve(); - } -} - - -export class AnalyticsReporter { - constructor(protected _analytics: Analytics) {} - - report(report: AnalyticsReport) { - switch (report.kind) { - case AnalyticsReportKind.Event: - this._analytics.event(report.category, report.action, report.options); - break; - case AnalyticsReportKind.Screenview: - this._analytics.screenview(report.screenName, report.appName, report.options); - break; - case AnalyticsReportKind.Pageview: - this._analytics.pageview(report.path, report.options); - break; - case AnalyticsReportKind.Timing: - this._analytics.timing(report.category, report.variable, report.time, report.options); - break; - - default: - throw new Error('Unexpected analytics report: ' + JSON.stringify(report)); - } - } -} diff --git a/packages/angular_devkit/core/src/analytics/index.ts b/packages/angular_devkit/core/src/analytics/index.ts deleted file mode 100644 index a6ca6c5f1c00..000000000000 --- a/packages/angular_devkit/core/src/analytics/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export * from './api'; -export * from './forwarder'; -export * from './logging'; -export * from './multi'; -export * from './noop'; - -/** - * MAKE SURE TO KEEP THIS IN SYNC WITH THE TABLE AND CONTENT IN `/docs/design/analytics.md`. - * WE LIST THOSE DIMENSIONS (AND MORE). - * - * These cannot be in their respective schema.json file because we either change the type - * (e.g. --buildEventLog is string, but we want to know the usage of it, not its value), or - * some validation needs to be done (we cannot record ng add --collection if it's not marked as - * allowed). - */ -export enum NgCliAnalyticsDimensions { - CpuCount = 1, - CpuSpeed = 2, - RamInGigabytes = 3, - NodeVersion = 4, - NgAddCollection = 6, - NgIvyEnabled = 8, - BuildErrors = 20, -} - -export enum NgCliAnalyticsMetrics { - NgComponentCount = 1, - UNUSED_2 = 2, - UNUSED_3 = 3, - UNUSED_4 = 4, - BuildTime = 5, - NgOnInitCount = 6, - InitialChunkSize = 7, - TotalChunkCount = 8, - TotalChunkSize = 9, - LazyChunkCount = 10, - LazyChunkSize = 11, - AssetCount = 12, - AssetSize = 13, - PolyfillSize = 14, - CssSize = 15, -} - -// This table is used when generating the analytics.md file. It should match the enum above -// or the validate-user-analytics script will fail. -export const NgCliAnalyticsDimensionsFlagInfo: { [name: string]: [string, string] } = { - CpuCount: ['CPU Count', 'number'], - CpuSpeed: ['CPU Speed', 'number'], - RamInGigabytes: ['RAM (In GB)', 'number'], - NodeVersion: ['Node Version', 'number'], - NgAddCollection: ['--collection', 'string'], - NgIvyEnabled: ['Ivy Enabled', 'boolean'], - BuildErrors: ['Build Errors (comma separated)', 'string'], -}; - -// This table is used when generating the analytics.md file. It should match the enum above -// or the validate-user-analytics script will fail. -export const NgCliAnalyticsMetricsFlagInfo: { [name: string]: [string, string] } = { - NgComponentCount: ['NgComponentCount', 'number'], - UNUSED_2: ['UNUSED_2', 'none'], - UNUSED_3: ['UNUSED_3', 'none'], - UNUSED_4: ['UNUSED_4', 'none'], - BuildTime: ['Build Time', 'number'], - NgOnInitCount: ['NgOnInit Count', 'number'], - InitialChunkSize: ['Initial Chunk Size', 'number'], - TotalChunkCount: ['Total Chunk Count', 'number'], - TotalChunkSize: ['Total Chunk Size', 'number'], - LazyChunkCount: ['Lazy Chunk Count', 'number'], - LazyChunkSize: ['Lazy Chunk Size', 'number'], - AssetCount: ['Asset Count', 'number'], - AssetSize: ['Asset Size', 'number'], - PolyfillSize: [' Polyfill Size', 'number'], - CssSize: [' Css Size', 'number'], -}; diff --git a/packages/angular_devkit/core/src/analytics/logging.ts b/packages/angular_devkit/core/src/analytics/logging.ts deleted file mode 100644 index e19785727ffe..000000000000 --- a/packages/angular_devkit/core/src/analytics/logging.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Logger } from '../logger'; -import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api'; - -/** - * Analytics implementation that logs analytics events to a logger. This should be used for - * debugging mainly. - */ -export class LoggingAnalytics implements Analytics { - constructor(protected _logger: Logger) {} - - event(category: string, action: string, options?: EventOptions): void { - this._logger.info('event ' + JSON.stringify({ category, action, ...options })); - } - screenview(screenName: string, appName: string, options?: ScreenviewOptions): void { - this._logger.info('screenview ' + JSON.stringify({ screenName, appName, ...options })); - } - pageview(path: string, options?: PageviewOptions): void { - this._logger.info('pageview ' + JSON.stringify({ path, ...options })); - } - timing(category: string, variable: string, time: string | number, options?: TimingOptions): void { - this._logger.info('timing ' + JSON.stringify({ category, variable, time, ...options })); - } - - flush(): Promise { - return Promise.resolve(); - } -} diff --git a/packages/angular_devkit/core/src/analytics/multi.ts b/packages/angular_devkit/core/src/analytics/multi.ts deleted file mode 100644 index 141f0db38e0b..000000000000 --- a/packages/angular_devkit/core/src/analytics/multi.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { Analytics, EventOptions, PageviewOptions, ScreenviewOptions, TimingOptions } from './api'; - -/** - * Analytics implementation that reports to multiple analytics backend. - */ -export class MultiAnalytics implements Analytics { - constructor(protected _backends: Analytics[] = []) {} - - push(...backend: Analytics[]) { - this._backends.push(...backend); - } - - event(category: string, action: string, options?: EventOptions): void { - this._backends.forEach(be => be.event(category, action, options)); - } - screenview(screenName: string, appName: string, options?: ScreenviewOptions): void { - this._backends.forEach(be => be.screenview(screenName, appName, options)); - } - pageview(path: string, options?: PageviewOptions): void { - this._backends.forEach(be => be.pageview(path, options)); - } - timing(category: string, variable: string, time: string | number, options?: TimingOptions): void { - this._backends.forEach(be => be.timing(category, variable, time, options)); - } - - - flush(): Promise { - return Promise.all(this._backends.map(x => x.flush())).then(() => {}); - } -} diff --git a/packages/angular_devkit/core/src/analytics/noop.ts b/packages/angular_devkit/core/src/analytics/noop.ts deleted file mode 100644 index a4592a75c174..000000000000 --- a/packages/angular_devkit/core/src/analytics/noop.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Analytics } from './api'; - -/** - * Analytics implementation that does nothing. - */ -export class NoopAnalytics implements Analytics { - event() {} - screenview() {} - pageview() {} - timing() {} - flush(): Promise { return Promise.resolve(); } -} diff --git a/packages/angular_devkit/core/src/exception.ts b/packages/angular_devkit/core/src/exception.ts new file mode 100644 index 000000000000..607a240d3ed8 --- /dev/null +++ b/packages/angular_devkit/core/src/exception.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export class BaseException extends Error { + constructor(message = '') { + super(message); + } +} + +export class UnknownException extends BaseException { + constructor(message: string) { + super(message); + } +} + +// Exceptions +export class FileDoesNotExistException extends BaseException { + constructor(path: string) { + super(`Path "${path}" does not exist.`); + } +} +export class FileAlreadyExistException extends BaseException { + constructor(path: string) { + super(`Path "${path}" already exist.`); + } +} +export class PathIsDirectoryException extends BaseException { + constructor(path: string) { + super(`Path "${path}" is a directory.`); + } +} +export class PathIsFileException extends BaseException { + constructor(path: string) { + super(`Path "${path}" is a file.`); + } +} + +/** + * @deprecated since version 14. Use the same symbol from `@angular-devkit/schematics`. + */ +export class ContentHasMutatedException extends BaseException { + constructor(path: string) { + super(`Content at path "${path}" has changed between the start and the end of an update.`); + } +} + +/** + * @deprecated since version 14. Use the same symbol from `@angular-devkit/schematics`. + */ + +export class InvalidUpdateRecordException extends BaseException { + constructor() { + super(`Invalid record instance.`); + } +} + +/** + * @deprecated since version 14. Use the same symbol from `@angular-devkit/schematics`. + */ +export class MergeConflictException extends BaseException { + constructor(path: string) { + super(`A merge conflicted on path "${path}".`); + } +} + +/** + * @deprecated since version 14. Create a custom exception implementation instead. + */ +export class UnimplementedException extends BaseException { + constructor() { + super('This function is unimplemented.'); + } +} + +/** + * @deprecated since version 14. Create a custom exception implementation instead. + */ +export class UnsupportedPlatformException extends BaseException { + constructor() { + super('This platform is not supported by this code path.'); + } +} diff --git a/packages/angular_devkit/core/src/exception/exception.ts b/packages/angular_devkit/core/src/exception/exception.ts deleted file mode 100644 index 31dc67badb1a..000000000000 --- a/packages/angular_devkit/core/src/exception/exception.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export class BaseException extends Error { - constructor(message = '') { - super(message); - } -} - - -export class UnknownException extends BaseException { - constructor(message: string) { super(message); } -} - - -// Exceptions -export class FileDoesNotExistException extends BaseException { - constructor(path: string) { super(`Path "${path}" does not exist.`); } -} -export class FileAlreadyExistException extends BaseException { - constructor(path: string) { super(`Path "${path}" already exist.`); } -} -export class PathIsDirectoryException extends BaseException { - constructor(path: string) { super(`Path "${path}" is a directory.`); } -} -export class PathIsFileException extends BaseException { - constructor(path: string) { super(`Path "${path}" is a file.`); } -} -export class ContentHasMutatedException extends BaseException { - constructor(path: string) { - super(`Content at path "${path}" has changed between the start and the end of an update.`); - } -} -export class InvalidUpdateRecordException extends BaseException { - constructor() { super(`Invalid record instance.`); } -} -export class MergeConflictException extends BaseException { - constructor(path: string) { - super(`A merge conflicted on path "${path}".`); - } -} - -export class UnimplementedException extends BaseException { - constructor() { super('This function is unimplemented.'); } -} - -export class UnsupportedPlatformException extends BaseException { - constructor() { super('This platform is not supported by this code path.'); } -} diff --git a/packages/angular_devkit/core/src/exception/index.ts b/packages/angular_devkit/core/src/exception/index.ts deleted file mode 100644 index b335f36dcb1d..000000000000 --- a/packages/angular_devkit/core/src/exception/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export * from './exception'; diff --git a/packages/angular_devkit/core/src/experimental.ts b/packages/angular_devkit/core/src/experimental.ts deleted file mode 100644 index 320138422f53..000000000000 --- a/packages/angular_devkit/core/src/experimental.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import * as jobs from './experimental/jobs/index'; - -export { - jobs, -}; diff --git a/packages/angular_devkit/core/src/experimental/jobs/README.md b/packages/angular_devkit/core/src/experimental/jobs/README.md deleted file mode 100644 index e1640a5382b9..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/README.md +++ /dev/null @@ -1,489 +0,0 @@ -# Description - -Jobs is the Angular DevKit subsystem for scheduling and running generic functions with clearly -typed inputs and outputs. A `Job` instance is a function associated with metadata. You can -schedule a job, synchronize it with other jobs, and use it to schedule other jobs. - -The whole API is serializable, allowing you to use a Node Stream or message channel to -communicate between the job and the job scheduler. - -Jobs are lazy, cold, and guaranteed to execute exactly once when scheduled. Subscribing to a job -returns messages from the point where the job is at. - -## Argument, Input, Output and Channels -A job receives a single argument when scheduled and can also listen to an input channel. It can -emit multiple outputs, and can also provide multiple output channels that emit asynchronous JSON -messages, which can be typed. - -The I/O model is like that of an executable, where the argument corresponds to arguments on the -command line, the input channel to STDIN, the output channel to STDOUT, and the channels -would be additional output streams. - -## LifeCycle -A `Job` goes through multiple LifeCycle messages before its completion; -1. `JobState.Queued`. The job was queued and is waiting. This is the default state from the - scheduler. -1. `JobState.Ready`. The job's dependencies (see - ["Synchronizing and Dependencies"](#Dependencies)) are done running, the argument is - validated, and the job is ready to execute. -1. `JobState.Started`. The argument has been validated, the job has been called and is running. - This is handled by the job itself (or `createJobHandler()`). -1. `JobState.Ended`. The job has ended and is done running. This is handled by the job itself (or - `createJobHandler()`). -1. `JobState.Errored`. A unrecoverable error happened. - -Each state (except `Queued`) corresponds to a `JobOutboundMessage` on the `outboundBus` observable -that triggers the state change. The `Scheduler` emits the `Ready` and `Errored` messages; the job -implementation should not emit them, and if it does they are filtered out. You can listen for -these messages or use the corresponding state member. - -The job implementation should emit the `Start` and `End` messages when it is starting the job logic -itself. Only the first `Start` and `End` messages will be forwarded. Any more will be filtered out. - -The `Queued` state is set as the job is scheduled, so there is no need to listen for the message. - -## `Job` Object -The `Job` object that is returned when you schedule a job provides access to the job's status and -utilities for tracking and modifying the job. - -1. `id`. A unique symbol that can be used as a Map key. -1. `description`. The description of the job from the scheduler. See `JobDescription` object. -1. `argument`. The argument value that was used to start the job. -1. `input`. An `Observer` that can be used to send validated inputs to the job itself. -1. `output`. An `Observable` that filters out messages to get only the returned output - of a job. -1. `promise`. A promise that waits for the last output of a job. Returns the last value outputted - (or no value if there's no last value). -1. `state`. The current state of the job (see `LifeCycle`). -1. `channels`. A map of side channels the user can listen to as `Observable`. -1. `ping()`. A function that can be used to ping the job, receiving a `Promise` for when the ping - is answered. -1. `stop()`. Sends a `stop` input to the job, which suggests to stop the job. The job itself can - choose to ignore this message. -1. `inboundBus`. The raw input `Observer`. This can be used to send messages to - the `context.inboundBus` observable in the job. These are `JobInboundMessage` messages. See - ["Communicating With Jobs"](#Communicating). -1. `outboundBus`. The raw output `Observable`. This can be used to listen to messages - from the job. See ["Communicating With Jobs"](#Communicating). - -## `JobHandlerContext` Object -The `JobHandlerContext<>` is passed to the job handler code in addition to its argument. The -context contains the following members: - -1. `description`. The description of the job. Its name and schemas. -1. `scheduler`. A `Scheduler<>` instance that can be used to create additional jobs. -1. `dependencies`. A generic list of other job instances that were run as dependencies when - scheduling this job. Their `id` is not guaranteed to match the `id` of the `Job<>` instance - itself (those `Job<>`s might just be proxies). The state of those `Job<>` is guaranteed to be - `JobState.Ended`, as `JobState.Errored` would have prevented this handler from running. -1. `inboundBus`. The raw input observable, complement of the `inboundBus` observer from the `Job<>`. - -# Examples - -An example of a job that adds all input together and return the output value. We use a -simple synchronous job registry and a simple job scheduler. - -```typescript -import { jobs } from '@angular-devkit/core'; - -const add = jobs.createJobHandle( - input => input.reduce((total, curr) => total + curr, 0), -); - -// Register the job in a SimpleJobRegistry. Different registries have different API. -const registry = new jobs.SimpleJobRegistry(); -const scheduler = new jobs.SimpleScheduler(registry); -registry.register(add, { - name: 'add', - input: { type: 'array', items: { type: 'number' } }, - output: { type: 'number' }, -}); - -scheduler.schedule('add', [1, 2, 3, 4]).promise - .then(output => console.log('1 + 2 + 3 + 4 is ' + output)); -``` - -# Creating Jobs - -A job is at its core a function with a description object attached to it. The description object -stores the JSON schemas used to validate the types of the argument passed in, the input and -output values. By default, a job accepts and can output any JSON object. - -```typescript -import { Observable } from 'rxjs'; -import { jobs } from '@angular-devkit/core'; - -const argument = { - type: 'array', items: { type: 'number' }, -}; -const output = { - type: 'number', -}; - -export function add(argument: number[]): Observable> { - return new Observable(o => { - o.next({ kind: jobs.JobOutboundMessageKind.Start }); - o.next({ - kind: jobs.JobOutboundMessageKind.Output, - output: argument.reduce((total, curr) => total + curr, 0), - }); - o.next({ kind: jobs.JobOutboundMessageKind.End }); - o.complete(); - }); -} - -// Add a property to `add` to make it officially a JobHandler. The Job system does not recognize -// any function as a JobHandler. -add.jobDescription = { - argument: argument, - output: output, -}; - -// Call the job with an array as argument, and log its output. -declare const scheduler: jobs.Scheduler; -scheduler.schedule('add', [1, 2, 3, 4]) - .output.subscribe(x => console.log(x)); // Will output 10. -``` - -This is a lot of boilerplate, so we made some helpers to improve readability and manage argument, -input and output automatically: - -```typescript -// Add is a JobHandler function, like the above. -export const add = jobs.createJobHandler( - argument => argument.reduce((total, curr) => total + curr, 0), -); - -// Schedule like above. -``` - -You can also return a Promise or an Observable, as jobs are asynchronous. This helper will set -start and end messages appropriately. It will also manage channels automatically (see below). - -A more complex job can be declared like this: - -```typescript -import { Observable } from 'rxjs'; -import { jobs } from '@angular-devkit/core'; - -// Show progress with each count in a separate output channel. Output "more" in a channel. -export const count = jobs.createJobHandler( - // Receive a context that contains additional methods to create channels. - (argument: number, { createChannel }) => new Observable(o => { - const side = createChannel('side', { type: 'string', const: 'more' }); - const progress = createChannel('progress', { type: 'number' }); - let i = 0; - function doCount() { - o.next(i++); - progress.next(i / argument); - side.next('more'); - - if (i < argument) { - setTimeout(doCount, 100); - } else { - o.complete(); - } - } - setTimeout(doCount, 100); - }), - { - argument: { type: 'number' }, - output: { type: 'number' }, - }, -); - -// Get a hold of a scheduler that refers to the job above. -declare const scheduler: jobs.Scheduler; - -const job = scheduler.schedule('count', 0); -job.getChannel('side').subscribe(x => console.log(x)); -// You can type a channel too. Messages will be filtered out. -job.getChannel('progress', { type: 'number' }).subscribe(x => console.log(x)); -``` - -## Communicating With Jobs -Jobs can be started and updated in a separate process or thread, and as such communication with a -job should avoid using global objects (which might not be shared). The jobs API and schedulers -provide 2 communication streams (one for input and the other for output), named `inboundBus` and -`outboundBus`. - -### Raw Input Stream -The `schedule()` function returns a `Job<>` interface that contains a `inboundBus` member of type -`Observer`. All messages sent _to_ the job goes through this stream. The `kind` -member of the `JobInboundMessage` interface dictates what kind of message it is sending: - -1. `JobInboundMessageKind.Ping`. A simple message that should be answered with - `JobOutboundMessageKind.Pong` when the job is responsive. The `id` field of the message should - be used when returning `Pong`. -1. `JobInboundMessageKind.Stop`. The job should be stopped. This is used when - cancelling/unsubscribing from the `output` (or by calling `stop()`). Any inputs or outputs - after this message will be ignored. -1. `JobInboundMessageKind.Input` is used when sending inputs to a job. These correspond to the - `next` methods of an `Observer` and are reported to the job through its `context.input` - Observable. There is no way to communicate an error to the job. - -Using the `createJobHandler()` helper, all those messages are automatically handled by the -boilerplate code. If you need direct access to raw inputs, you should subscribe to the -`context.inboundBus` Observable. - -### Raw Output Stream -The `Job<>` interface also contains a `outboundBus` member (of type -`Observable>` where `O` is the typed output of the job) which is the output -complement of `inboundBus`. All messages sent _from_ the job goes through this stream. The `kind` -member of the `JobOutboundMessage` interface dictates what kind of message it is sending: - -1. `JobOutboundMessageKind.Create`. The `Job<>` was created, its dependencies are done, and the - library is validating Argument and calling the internal job code. -1. `JobOutboundMessageKind.Start`. The job code itself should send that message when started. - `createJobHandler()` will do it automatically. -1. `JobOutboundMessageKind.End`. The job has ended. This is done by the job itself and should always - be sent when completed. The scheduler will listen to this message to set the state and unblock - dependent jobs. `createJobHandler()` automatically send this message. -1. `JobOutboundMessageKind.Pong`. The job should answer a `JobInboundMessageKind.Ping` message with - this. Automatically done by `createJobHandler()`. -1. `JobOutboundMessageKind.Output`. An `Output` has been generated by the job. -1. `JobOutboundMessageKind.ChannelMessage`, `JobOutboundMessageKind.ChannelError` and - `JobOutboundMessageKind.ChannelComplete` are used for output channels. These correspond to the - `next`, `error` and `complete` methods of an `Observer` and are available to the callee through - the `job.channels` map of Observable. - -Those messages can be accessed directly through the `job.outboundBus` member. The job itself should -return an `Observable>`. The `createJobHandler()` helper handles most of use -cases of this and makes it easier for jobs to handle this. - -## Job Dispatchers -Dispatchers are a helper that redirect to different jobs given conditions. To create a job -dispatcher, use the `createDispatcher()` function: - -```typescript -import { jobs } from '@angular-devkit/core'; - -// A dispatcher that installs node modules given a user's preference. -const dispatcher = jobs.createDispatcher({ - name: 'node-install', - argument: { properties: { moduleName: { type: 'string' } } }, - output: { type: 'boolean' }, -}); - -const npmInstall = jobs.createJobHandler(/* ... */, { name: 'npm-install' }); -const yarnInstall = jobs.createJobHandler(/* ... */, { name: 'yarn-install' }); -const pnpmInstall = jobs.createJobHandler(/* ... */, { name: 'pnpm-install' }); - -declare const registry: jobs.SimpleJobRegistry; -registry.register(dispatcher); -registry.register(npmInstall); -registry.register(yarnInstall); -registry.register(pnpmInstall); - -// Default to npm. -dispatcher.setDefaultDelegate(npmInstall.name); -// If the user is asking for yarn over npm, uses it. -dispatcher.addConditionalDelegate(() => userWantsYarn, yarnInstall.name); -``` - -## Execution Strategy -Jobs are always run in parallel and will always start, but many helper functions are provided -when creating a job to help you control the execution strategy; - -1. `serialize()`. Multiple runs of this job will be queued with each others. -1. `memoize(replayMessages = false)` will create a job, or reuse the same job when inputs are -matching. If the inputs don't match, a new job will be started and its outputs will be stored. - -These strategies can be used when creating the job: - -```typescript -// Same input and output as above. - -export const add = jobs.strategy.memoize()( - jobs.createJobHandler( - argument => argument.reduce((total, curr) => total + curr, 0), - ), -); -``` - -Strategies can be reused to synchronize between jobs. For example, given jobs `jobA` and `jobB`, -you can reuse the strategy to serialize both jobs together; - -```typescript -const strategy = jobs.strategy.serialize(); -const jobA = strategy(jobs.createJobHandler(...)); -const jobB = strategy(jobs.createJobHandler(...)); -``` - -Even further, we can have package A and package B run in serialization, and B and C also be -serialized. Running A and C will run in parallel, while running B will wait for both A and C -to finish. - -```typescript -const strategy1 = jobs.strategy.serialize(); -const strategy2 = jobs.strategy.serialize(); -const jobA = strategy1(jobs.createJobHandler(...)); -const jobB = strategy1(strategy2(jobs.createJobHandler(...))); -const jobC = strategy2(jobs.createJobHandler(...)); -``` - -# Scheduling Jobs -Jobs can be scheduled using a `Scheduler` interface, which contains a `schedule()` method: - -```typescript -interface Scheduler { - /** - * Schedule a job to be run, using its name. - * @param name The name of job to be run. - * @param argument The argument to send to the job when starting it. - * @param options Scheduling options. - * @returns The Job being run. - */ - schedule( - name: JobName, - argument: I, - options?: ScheduleJobOptions, - ): Job; -} -``` - -The scheduler also has a `getDescription()` method to get a `JobDescription` object for a certain -name; that description contains schemas for the argument, input, output, and other channels: - -```typescript -interface Scheduler { - /** - * Get a job description for a named job. - * - * @param name The name of the job. - * @returns A description, or null if the job cannot be scheduled. - */ - getDescription(name: JobName): JobDescription | null; - - /** - * Returns true if the job name has been registered. - * @param name The name of the job. - * @returns True if the job exists, false otherwise. - */ - has(name: JobName): boolean; -} -``` - -Finally, the scheduler interface has a `pause()` method to stop scheduling. This will queue all -jobs and wait for the unpause function to be called before unblocking all the jobs scheduled. -This does not affect already running jobs. - -```typescript -interface Scheduler { - /** - * Pause the scheduler, temporary queueing _new_ jobs. Returns a resume function that should be - * used to resume execution. If multiple `pause()` were called, all their resume functions must - * be called before the Scheduler actually starts new jobs. Additional calls to the same resume - * function will have no effect. - * - * Jobs already running are NOT paused. This is pausing the scheduler only. - * - * @returns A function that can be run to resume the scheduler. If multiple `pause()` calls - * were made, all their return function must be called (in any order) before the - * scheduler can resume. - */ - pause(): () => void; -} -``` - -## Synchronizing and Dependencies -When scheduling jobs, it is often necessary to run jobs after certain other jobs are finished. -This is done through the `dependencies` options in the `schedule()` method. - -These jobs will also be passed to the job being scheduled, through its context. This can be -useful if, for example, the output of those jobs are of a known type, or have known side channels. - -An example of this would be a compiler that needs to know the output directory of other compilers -before it, in a tool chain. - -### Dependencies -When scheduling jobs, the user can add a `dependencies` field to the scheduling options. The -scheduler will wait for those dependencies to finish before running the job, and pass those jobs -in the context of the job. - -### Accessing Dependencies -Jobs are called with a `JobHandlerContext` as a second argument, which contains a -`dependencies: Job[]` member which contains all dependencies that were used when -scheduling the job. Those aren't fully typed as they are determined by the user, and not the job -itself. They also can contain jobs that are not finished, and the job should use the `state` -member of the job itself before trying to access its content. - -### Scheduler Sub Jobs -The `JobHandlerContext` also contains a `scheduler` member which can be used to schedule jobs -using the same scheduler that was used for the job. This allows jobs to call other jobs -and wait for them to end. - -## Available Schedulers -The Core Angular DevKit library provides 2 implementations for the `Scheduler` interface: - -## SimpleJobRegistry -Available in the jobs namespace. A registry that accept job registration, and can also schedule -jobs. - -```typescript -import { jobs } from '@angular-devkit/core'; - -const add = jobs.createJobHandler( - argument => argument.reduce((total, curr) => total + curr, 0), -); - -// Register the job in a SimpleJobRegistry. Different registries have different API. -const registry = new jobs.SimpleJobRegistry(); -const scheduler = new SimpleJobScheduler(registry); - -registry.register(add, { - name: 'add', - argument: { type: 'array', items: { type: 'number' } }, - output: { type: 'number' }, -}); - -scheduler.schedule('add', [1, 2, 3, 4]); -``` - -## NodeModuleJobRegistry -Available through `@angular-devkit/core/node`. - -A scheduler that loads jobs using their node package names. These jobs need to use the -`createJobHandler()` helper and report their argument/input/output schemas that way. - -```typescript -declare const registry: NodeModuleJobRegistry; -const scheduler = new SimpleJobScheduler(registry); - -scheduler.schedule('some-node-package#someExport', 'input'); -``` - -# Gotchas - -1. Deadlocking Dependencies - It is impossible to add dependencies to an already running job, but it is entirely possible to - get locked between jobs. Be aware of your own dependencies. - -1. Using `job.promise` - `job.promise` waits for the job to ends. Don't rely on it unless you know the job is not - watching and running for a long time. If you aren't sure, use - `job.output.pipe(first()).toPromise()` instead which will return the first next output, - regardless of whether the job watches and rerun or not. - - -# FAQ - -1. Laziness - A job is lazy until executed, but its messages will be replayed when resubscribed. - -1. Serialize Strategy vs Dependencies - Strategies are functions that transform the execution of a job, and can be used when - declaring the job, or registering it. Dependencies, on the other hand, are listed when - scheduling a job to order jobs during scheduling. - - A job has no control over the way it's scheduled, and its dependencies. It can, however, - declare that it shouldn't run at the same time as itself. Alternatively, a user could - schedule a job twice and imply that the second run should wait for the first to finish. In - practice, this would be equivalent to having the job be serialized, but the important detail - is in _whom_ is defining the rules; using the `serialize()` strategy, the job implementation - is, while when using dependencies, the user is. - - The user does not need to know how to job needs to synchronize with itself, and the job does - not need to know how it synchronizes with other jobs that it doesn't know about. That's part - of the strength of this system as every job can be developed in a vacuum, only caring about - its contracts (argument, input and output) and its own synchronization. diff --git a/packages/angular_devkit/core/src/experimental/jobs/api.ts b/packages/angular_devkit/core/src/experimental/jobs/api.ts deleted file mode 100644 index 2a5297833cf8..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/api.ts +++ /dev/null @@ -1,462 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Observable, Observer } from 'rxjs'; -import { JsonObject, JsonValue, schema } from '../../json/index'; -import { DeepReadonly } from '../../utils/index'; - -/** - * A job name is just a string (needs to be serializable). - */ -export type JobName = string; - - -/** - * The job handler function, which is a method that's executed for the job. - */ -export interface JobHandler< - ArgT extends JsonValue, - InputT extends JsonValue, - OutputT extends JsonValue, -> { - ( - argument: ArgT, - context: JobHandlerContext, - ): Observable>; - - jobDescription: Partial; -} - - -/** - * The context in which the job is run. - */ -export interface JobHandlerContext< - MinimumArgumentValueT extends JsonValue = JsonValue, - MinimumInputValueT extends JsonValue = JsonValue, - MinimumOutputValueT extends JsonValue = JsonValue, -> { - readonly description: JobDescription; - readonly scheduler: Scheduler; - - // In this context, JsonValue is comparable to `any`. - readonly dependencies: Job[]; - - readonly inboundBus: Observable>; -} - - -/** - * Metadata associated with a job. - */ -export interface JobDescription extends JsonObject { - readonly name: JobName; - - readonly argument: DeepReadonly; - readonly input: DeepReadonly; - readonly output: DeepReadonly; -} - -/** - * Messages that can be sent TO a job. The job needs to listen to those. - */ -export enum JobInboundMessageKind { - Ping = 'ip', - Stop = 'is', - - // Channel specific messages. - Input = 'in', - // Input channel does not allow completion / error. Erroring this will just close the Subject - // but not notify the job. -} - -/** Base interface for the all job inbound messages. */ -export interface JobInboundMessageBase extends JsonObject { - /** - * The kind of message this is. - */ - readonly kind: JobInboundMessageKind; -} - -/** - * A ping to the job. The job should reply with a pong as soon as possible. - */ -export interface JobInboundMessagePing extends JobInboundMessageBase { - readonly kind: JobInboundMessageKind.Ping; - - /** - * An ID that should be returned in the corresponding Pong. - */ - readonly id: number; -} - -/** - * Stop the job. This is handled by the job itself and jobs might not handle it. It will also - * unsubscribe from the Observable<>. - * This is equivalent to SIGTERM. - */ -export interface JobInboundMessageStop extends JobInboundMessageBase { - readonly kind: JobInboundMessageKind.Stop; -} - -/** - * A Job wants to send a message to a channel. This can be marshaled, and the Job object - * has helpers to transform this into an observable. The context also can create RxJS subjects that - * marshall messages through a channel. - */ -export interface JobInboundMessageInput extends JobInboundMessageBase { - readonly kind: JobInboundMessageKind.Input; - - /** - * The input being sent to the job. - */ - readonly value: InputT; -} - -export type JobInboundMessage = - JobInboundMessagePing - | JobInboundMessageStop - | JobInboundMessageInput - ; - -/** - * Kind of messages that can be outputted from a job. - */ -export enum JobOutboundMessageKind { - // Lifecycle specific messages. - OnReady = 'c', - Start = 's', - End = 'e', - Pong = 'p', - - // Feedback messages. - Output = 'o', - - // Channel specific messages. - ChannelCreate = 'cn', - ChannelMessage = 'cm', - ChannelError = 'ce', - ChannelComplete = 'cc', -} - -/** Base interface for the all job messages. */ -export interface JobOutboundMessageBase { - /** - * The job description. - */ - readonly description: JobDescription; - - /** - * The kind of message this is. - */ - readonly kind: JobOutboundMessageKind; -} - -/** - * The job has been created and will validate its input. - */ -export interface JobOutboundMessageOnReady extends JobOutboundMessageBase { - readonly kind: JobOutboundMessageKind.OnReady; -} - -/** - * The job started. This is done by the job itself. - */ -export interface JobOutboundMessageStart extends JobOutboundMessageBase { - readonly kind: JobOutboundMessageKind.Start; -} - -/** - * An output value is available. - */ -export interface JobOutboundMessageOutput< - OutputT extends JsonValue, -> extends JobOutboundMessageBase { - readonly kind: JobOutboundMessageKind.Output; - - /** - * The message being outputted from the job. - */ - readonly value: OutputT; -} - - -/** - * Base interface for all job message related to channels. - */ -export interface JobOutboundMessageChannelBase extends JobOutboundMessageBase { - /** - * The name of the channel. - */ - readonly name: string; -} - -/** - * A job wants to send a message to a channel. This can be marshaled, and the Job object - * has helpers to transform this into an observable. The context also can create RxJS subjects that - * marshall messages through a channel. - */ -export interface JobOutboundMessageChannelMessage extends JobOutboundMessageChannelBase { - readonly kind: JobOutboundMessageKind.ChannelMessage; - - /** - * The message being sent to the channel. - */ - readonly message: JsonValue; -} - -/** - * A job wants to send an error to one of its channel. This is the equivalent of throwing through - * an Observable. The side channel will not receive any more messages after this, and will not - * complete. - */ -export interface JobOutboundMessageChannelError extends JobOutboundMessageChannelBase { - readonly kind: JobOutboundMessageKind.ChannelError; - - /** - * The error message being sent to the channel. - */ - readonly error: JsonValue; -} - -/** - * A job wants to create a new channel. - */ -export interface JobOutboundMessageChannelCreate extends JobOutboundMessageChannelBase { - readonly kind: JobOutboundMessageKind.ChannelCreate; -} - -/** - * A job wants to close the channel, as completed. This is done automatically when the job ends, - * or can be done from the job to close it. A closed channel might be reopened, but the user - * need to recall getChannel(). - */ -export interface JobOutboundMessageChannelComplete extends JobOutboundMessageChannelBase { - readonly kind: JobOutboundMessageKind.ChannelComplete; -} - -/** - * OnEnd of the job run. - */ -export interface JobOutboundMessageEnd extends JobOutboundMessageBase { - readonly kind: JobOutboundMessageKind.End; -} - -/** - * A pong response from a ping input. The id is the same as the one passed in. - */ -export interface JobOutboundMessagePong extends JobOutboundMessageBase { - readonly kind: JobOutboundMessageKind.Pong; - - /** - * The ID that was passed in the `Ping` messages. - */ - readonly id: number; -} - -/** - * Generic message type. - */ -export type JobOutboundMessage = - JobOutboundMessageOnReady - | JobOutboundMessageStart - | JobOutboundMessageOutput - | JobOutboundMessageChannelCreate - | JobOutboundMessageChannelMessage - | JobOutboundMessageChannelError - | JobOutboundMessageChannelComplete - | JobOutboundMessageEnd - | JobOutboundMessagePong - ; - - -/** - * The state of a job. These are changed as the job reports a new state through its messages. - */ -export enum JobState { - /** - * The job was queued and is waiting to start. - */ - Queued = 'queued', - /** - * The job description was found, its dependencies (see "Synchronizing and Dependencies") - * are done running, and the job's argument is validated and the job's code will be executed. - */ - Ready = 'ready', - /** - * The job has been started. The job implementation is expected to send this as soon as its - * work is starting. - */ - Started = 'started', - /** - * The job has ended and is done running. - */ - Ended = 'ended', - /** - * An error occured and the job stopped because of internal state. - */ - Errored = 'errored', -} - - -/** - * A Job instance, returned from scheduling a job. A Job instance is _not_ serializable. - */ -export interface Job< - ArgumentT extends JsonValue = JsonValue, - InputT extends JsonValue = JsonValue, - OutputT extends JsonValue = JsonValue, -> { - /** - * Description of the job. Resolving the job's description can be done asynchronously, so this - * is an observable that will resolve when it's ready. - */ - readonly description: Observable; - - /** - * Argument sent when scheduling the job. This is a copy of the argument. - */ - readonly argument: ArgumentT; - - /** - * The input to the job. This goes through the input channel as messages. - */ - readonly input: Observer; - - /** - * Outputs of this job. - */ - readonly output: Observable; - - /** - * The current state of the job. - */ - readonly state: JobState; - - /** - * Get a channel that validates against the schema. Messages will be filtered by the schema. - * @param name The name of the channel. - * @param schema A schema to use to validate messages. - */ - getChannel(name: string, schema?: schema.JsonSchema): Observable; - - /** - * Pings the job and wait for the resulting Pong before completing. - */ - ping(): Observable; - - /** - * Stops the job from running. This is different than unsubscribing from the output as in it - * sends the JobInboundMessageKind.Stop raw input to the job. - */ - stop(): void; - - /** - * The JobInboundMessage messages TO the job. - */ - readonly inboundBus: Observer>; - - /** - * The JobOutboundMessage FROM the job. - */ - readonly outboundBus: Observable>; -} - -/** - * Options for scheduling jobs. - */ -export interface ScheduleJobOptions { - /** - * Jobs that need to finish before scheduling this job. These dependencies will be passed - * to the job itself in its context. - */ - dependencies?: Job | Job[]; -} - -export interface Registry< - MinimumArgumentValueT extends JsonValue = JsonValue, - MinimumInputValueT extends JsonValue = JsonValue, - MinimumOutputValueT extends JsonValue = JsonValue, -> { - /** - * Get a job handler. - * @param name The name of the job to get a handler from. - */ - get< - A extends MinimumArgumentValueT, - I extends MinimumInputValueT, - O extends MinimumOutputValueT, - >(name: JobName): Observable | null>; -} - -/** - * An interface that can schedule jobs. - */ -export interface Scheduler< - MinimumArgumentValueT extends JsonValue = JsonValue, - MinimumInputValueT extends JsonValue = JsonValue, - MinimumOutputValueT extends JsonValue = JsonValue, -> { - /** - * Get a job description for a named job. - * - * @param name The name of the job. - * @returns A description, or null if no description is available for this job. - */ - getDescription(name: JobName): Observable; - - /** - * Returns true if the job name has been registered. - * @param name The name of the job. - * @returns True if the job exists, false otherwise. - */ - has(name: JobName): Observable; - - /** - * Pause the scheduler, temporary queueing _new_ jobs. Returns a resume function that should be - * used to resume execution. If multiple `pause()` were called, all their resume functions must - * be called before the Scheduler actually starts new jobs. Additional calls to the same resume - * function will have no effect. - * - * Jobs already running are NOT paused. This is pausing the scheduler only. - * - * @returns A function that can be run to resume the scheduler. If multiple `pause()` calls - * were made, all their return function must be called (in any order) before the - * scheduler can resume. - */ - pause(): () => void; - - /** - * Schedule a job to be run, using its name. - * @param name The name of job to be run. - * @param argument The argument to send to the job when starting it. - * @param options Scheduling options. - * @returns The job being run. - */ - schedule< - A extends MinimumArgumentValueT, - I extends MinimumInputValueT, - O extends MinimumOutputValueT, - >( - name: JobName, - argument: A, - options?: ScheduleJobOptions, - ): Job; -} - - -export function isJobHandler< - A extends JsonValue, - I extends JsonValue, - O extends JsonValue, ->(value: unknown): value is JobHandler { - const job = value as JobHandler; - - return typeof job == 'function' - && typeof job.jobDescription == 'object' - && job.jobDescription !== null; -} diff --git a/packages/angular_devkit/core/src/experimental/jobs/dispatcher.ts b/packages/angular_devkit/core/src/experimental/jobs/dispatcher.ts deleted file mode 100644 index c1dce056ed30..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/dispatcher.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - * - */ -import { JsonValue } from '../../json/index'; -import { Readwrite } from '../../utils/index'; -import { - Job, - JobDescription, - JobHandler, - JobHandlerContext, - JobName, - isJobHandler, -} from './api'; -import { JobDoesNotExistException } from './exception'; - -/** - * A JobDispatcher can be used to dispatch between multiple jobs. - */ -export interface JobDispatcher< - A extends JsonValue, - I extends JsonValue, - O extends JsonValue, -> extends JobHandler { - /** - * Set the default job if all conditionals failed. - * @param name The default name if all conditions are false. - */ - setDefaultJob(name: JobName | null | JobHandler): void; - - /** - * Add a conditional job that will be selected if the input fits a predicate. - * @param predicate - * @param name - */ - addConditionalJob(predicate: (args: A) => boolean, name: string): void; -} - - -/** - * OnReady a dispatcher that can dispatch to a sub job, depending on conditions. - * @param options - */ -export function createDispatcher< - A extends JsonValue, - I extends JsonValue, - O extends JsonValue, ->( - options: Partial> = {}, -): JobDispatcher { - let defaultDelegate: JobName | null = null; - const conditionalDelegateList: [(args: JsonValue) => boolean, JobName][] = []; - - const job: JobHandler = Object.assign( - (argument: JsonValue, context: JobHandlerContext) => { - const maybeDelegate = conditionalDelegateList.find(([predicate]) => predicate(argument)); - let delegate: Job | null = null; - - if (maybeDelegate) { - delegate = context.scheduler.schedule(maybeDelegate[1], argument); - } else if (defaultDelegate) { - delegate = context.scheduler.schedule(defaultDelegate, argument); - } else { - throw new JobDoesNotExistException(''); - } - - context.inboundBus.subscribe(delegate.inboundBus); - - return delegate.outboundBus; - }, - { - jobDescription: options, - }, - ); - - return Object.assign(job, { - setDefaultJob(name: JobName | null | JobHandler) { - if (isJobHandler(name)) { - name = name.jobDescription.name === undefined ? null : name.jobDescription.name; - } - - defaultDelegate = name; - }, - addConditionalJob(predicate: (args: JsonValue) => boolean, name: JobName) { - conditionalDelegateList.push([predicate, name]); - }, - // TODO: Remove return-only generic from createDispatcher() API. - }) as unknown as JobDispatcher; -} diff --git a/packages/angular_devkit/core/src/experimental/jobs/exception.ts b/packages/angular_devkit/core/src/experimental/jobs/exception.ts deleted file mode 100644 index 0fcb761a978f..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/exception.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { BaseException } from '../../exception/index'; -import { JobName } from './api'; - -export class JobNameAlreadyRegisteredException extends BaseException { - constructor(name: JobName) { - super(`Job named ${JSON.stringify(name)} already exists.`); - } -} - -export class JobDoesNotExistException extends BaseException { - constructor(name: JobName) { - super(`Job name ${JSON.stringify(name)} does not exist.`); - } -} diff --git a/packages/angular_devkit/core/src/experimental/jobs/fallback-registry.ts b/packages/angular_devkit/core/src/experimental/jobs/fallback-registry.ts deleted file mode 100644 index 4d2fa84dd658..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/fallback-registry.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { Observable, from } from 'rxjs'; -import { concatMap, first } from 'rxjs/operators'; -import { JsonValue } from '../../json'; -import { JobHandler, JobName, Registry } from './api'; - - -/** - * A simple job registry that keep a map of JobName => JobHandler internally. - */ -export class FallbackRegistry< - MinimumArgumentValueT extends JsonValue = JsonValue, - MinimumInputValueT extends JsonValue = JsonValue, - MinimumOutputValueT extends JsonValue = JsonValue, -> implements Registry { - constructor(protected _fallbacks: Registry< - MinimumArgumentValueT, - MinimumInputValueT, - MinimumOutputValueT - >[] = []) {} - - addFallback(registry: Registry) { - this._fallbacks.push(registry); - } - - get< - A extends MinimumArgumentValueT = MinimumArgumentValueT, - I extends MinimumInputValueT = MinimumInputValueT, - O extends MinimumOutputValueT = MinimumOutputValueT, - >(name: JobName): Observable | null> { - return from(this._fallbacks).pipe( - concatMap(fb => fb.get(name)), - first(x => x !== null, null), - ); - } -} diff --git a/packages/angular_devkit/core/src/experimental/jobs/index.ts b/packages/angular_devkit/core/src/experimental/jobs/index.ts deleted file mode 100644 index 70210cb22e9f..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export * from './api'; -export * from './create-job-handler'; -export * from './exception'; -export * from './dispatcher'; -export * from './fallback-registry'; -export * from './simple-registry'; -export * from './simple-scheduler'; -export * from './strategy'; diff --git a/packages/angular_devkit/core/src/experimental/jobs/simple-scheduler.ts b/packages/angular_devkit/core/src/experimental/jobs/simple-scheduler.ts deleted file mode 100644 index 5bff342f66ed..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/simple-scheduler.ts +++ /dev/null @@ -1,545 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { - EMPTY, - MonoTypeOperatorFunction, - Observable, - Observer, - Subject, - Subscription, - concat, - from, - merge, - of, -} from 'rxjs'; -import { - concatMap, - filter, - first, - ignoreElements, - map, - shareReplay, - switchMap, - tap, -} from 'rxjs/operators'; -import { JsonValue, schema } from '../../json'; -import { - Job, - JobDescription, - JobHandler, - JobInboundMessage, - JobInboundMessageKind, - JobName, - JobOutboundMessage, - JobOutboundMessageKind, - JobOutboundMessageOutput, - JobState, - Registry, - ScheduleJobOptions, - Scheduler, -} from './api'; -import { JobDoesNotExistException } from './exception'; - - -export class JobArgumentSchemaValidationError extends schema.SchemaValidationException { - constructor(errors?: schema.SchemaValidatorError[]) { - super(errors, 'Job Argument failed to validate. Errors: '); - } -} -export class JobInboundMessageSchemaValidationError extends schema.SchemaValidationException { - constructor(errors?: schema.SchemaValidatorError[]) { - super(errors, 'Job Inbound Message failed to validate. Errors: '); - } -} -export class JobOutputSchemaValidationError extends schema.SchemaValidationException { - constructor(errors?: schema.SchemaValidatorError[]) { - super(errors, 'Job Output failed to validate. Errors: '); - } -} - - -interface JobHandlerWithExtra extends JobHandler { - jobDescription: JobDescription; - - argumentV: Observable; - outputV: Observable; - inputV: Observable; -} - - -function _jobShare(): MonoTypeOperatorFunction { - // This is the same code as a `shareReplay()` operator, but uses a dumber Subject rather than a - // ReplaySubject. - return (source: Observable): Observable => { - let refCount = 0; - let subject: Subject; - let hasError = false; - let isComplete = false; - let subscription: Subscription; - - return new Observable(subscriber => { - let innerSub: Subscription; - refCount++; - if (!subject) { - subject = new Subject(); - - innerSub = subject.subscribe(subscriber); - subscription = source.subscribe({ - next(value) { subject.next(value); }, - error(err) { - hasError = true; - subject.error(err); - }, - complete() { - isComplete = true; - subject.complete(); - }, - }); - } else { - innerSub = subject.subscribe(subscriber); - } - - return () => { - refCount--; - innerSub.unsubscribe(); - if (subscription && refCount === 0 && (isComplete || hasError)) { - subscription.unsubscribe(); - } - }; - }); - }; -} - - -/** - * Simple scheduler. Should be the base of all registries and schedulers. - */ -export class SimpleScheduler< - MinimumArgumentT extends JsonValue = JsonValue, - MinimumInputT extends JsonValue = JsonValue, - MinimumOutputT extends JsonValue = JsonValue, -> implements Scheduler { - private _internalJobDescriptionMap = new Map(); - private _queue: (() => void)[] = []; - private _pauseCounter = 0; - - constructor( - protected _jobRegistry: Registry, - protected _schemaRegistry: schema.SchemaRegistry = new schema.CoreSchemaRegistry(), - ) {} - - private _getInternalDescription(name: JobName): Observable { - const maybeHandler = this._internalJobDescriptionMap.get(name); - if (maybeHandler !== undefined) { - return of(maybeHandler); - } - - const handler = this._jobRegistry.get(name); - - return handler.pipe( - switchMap(handler => { - if (handler === null) { - return of(null); - } - - const description: JobDescription = { - // Make a copy of it to be sure it's proper JSON. - ...JSON.parse(JSON.stringify(handler.jobDescription)), - name: handler.jobDescription.name || name, - argument: handler.jobDescription.argument || true, - input: handler.jobDescription.input || true, - output: handler.jobDescription.output || true, - channels: handler.jobDescription.channels || {}, - }; - - const handlerWithExtra = Object.assign(handler.bind(undefined), { - jobDescription: description, - argumentV: this._schemaRegistry.compile(description.argument).pipe(shareReplay(1)), - inputV: this._schemaRegistry.compile(description.input).pipe(shareReplay(1)), - outputV: this._schemaRegistry.compile(description.output).pipe(shareReplay(1)), - }) as JobHandlerWithExtra; - this._internalJobDescriptionMap.set(name, handlerWithExtra); - - return of(handlerWithExtra); - }), - ); - } - - /** - * Get a job description for a named job. - * - * @param name The name of the job. - * @returns A description, or null if the job is not registered. - */ - getDescription(name: JobName) { - return concat( - this._getInternalDescription(name).pipe(map(x => x && x.jobDescription)), - of(null), - ).pipe( - first(), - ); - } - - /** - * Returns true if the job name has been registered. - * @param name The name of the job. - * @returns True if the job exists, false otherwise. - */ - has(name: JobName) { - return this.getDescription(name).pipe( - map(x => x !== null), - ); - } - - /** - * Pause the scheduler, temporary queueing _new_ jobs. Returns a resume function that should be - * used to resume execution. If multiple `pause()` were called, all their resume functions must - * be called before the Scheduler actually starts new jobs. Additional calls to the same resume - * function will have no effect. - * - * Jobs already running are NOT paused. This is pausing the scheduler only. - */ - pause() { - let called = false; - this._pauseCounter++; - - return () => { - if (!called) { - called = true; - if (--this._pauseCounter == 0) { - // Resume the queue. - const q = this._queue; - this._queue = []; - q.forEach(fn => fn()); - } - } - }; - } - - /** - * Schedule a job to be run, using its name. - * @param name The name of job to be run. - * @param argument The argument to send to the job when starting it. - * @param options Scheduling options. - * @returns The Job being run. - */ - schedule( - name: JobName, - argument: A, - options?: ScheduleJobOptions, - ): Job { - if (this._pauseCounter > 0) { - const waitable = new Subject(); - this._queue.push(() => waitable.complete()); - - return this._scheduleJob(name, argument, options || {}, waitable); - } - - return this._scheduleJob(name, argument, options || {}, EMPTY); - } - - /** - * Filter messages. - * @private - */ - private _filterJobOutboundMessages( - message: JobOutboundMessage, - state: JobState, - ) { - switch (message.kind) { - case JobOutboundMessageKind.OnReady: - return state == JobState.Queued; - case JobOutboundMessageKind.Start: - return state == JobState.Ready; - - case JobOutboundMessageKind.End: - return state == JobState.Started || state == JobState.Ready; - } - - return true; - } - - /** - * Return a new state. This is just to simplify the reading of the _createJob method. - * @private - */ - private _updateState( - message: JobOutboundMessage, - state: JobState, - ): JobState { - switch (message.kind) { - case JobOutboundMessageKind.OnReady: - return JobState.Ready; - case JobOutboundMessageKind.Start: - return JobState.Started; - case JobOutboundMessageKind.End: - return JobState.Ended; - } - - return state; - } - - /** - * Create the job. - * @private - */ - private _createJob( - name: JobName, - argument: A, - handler: Observable, - inboundBus: Observer>, - outboundBus: Observable>, - ): Job { - const schemaRegistry = this._schemaRegistry; - - const channelsSubject = new Map>(); - const channels = new Map>(); - - let state = JobState.Queued; - let pingId = 0; - - // Create the input channel by having a filter. - const input = new Subject(); - input.pipe( - concatMap(message => handler.pipe( - switchMap(handler => { - if (handler === null) { - throw new JobDoesNotExistException(name); - } else { - return handler.inputV.pipe( - switchMap(validate => validate(message)), - ); - } - }), - )), - filter(result => result.success), - map(result => result.data as I), - ).subscribe( - value => inboundBus.next({ kind: JobInboundMessageKind.Input, value }), - ); - - outboundBus = concat( - outboundBus, - // Add an End message at completion. This will be filtered out if the job actually send an - // End. - handler.pipe(switchMap(handler => { - if (handler) { - return of>({ - kind: JobOutboundMessageKind.End, description: handler.jobDescription, - }); - } else { - return EMPTY as Observable>; - } - })), - ).pipe( - filter(message => this._filterJobOutboundMessages(message, state)), - // Update internal logic and Job<> members. - tap(message => { - // Update the state. - state = this._updateState(message, state); - - switch (message.kind) { - case JobOutboundMessageKind.ChannelCreate: { - const maybeSubject = channelsSubject.get(message.name); - // If it doesn't exist or it's closed on the other end. - if (!maybeSubject) { - const s = new Subject(); - channelsSubject.set(message.name, s); - channels.set(message.name, s.asObservable()); - } - break; - } - - case JobOutboundMessageKind.ChannelMessage: { - const maybeSubject = channelsSubject.get(message.name); - if (maybeSubject) { - maybeSubject.next(message.message); - } - break; - } - - case JobOutboundMessageKind.ChannelComplete: { - const maybeSubject = channelsSubject.get(message.name); - if (maybeSubject) { - maybeSubject.complete(); - channelsSubject.delete(message.name); - } - break; - } - - case JobOutboundMessageKind.ChannelError: { - const maybeSubject = channelsSubject.get(message.name); - if (maybeSubject) { - maybeSubject.error(message.error); - channelsSubject.delete(message.name); - } - break; - } - } - }, () => { - state = JobState.Errored; - }), - - // Do output validation (might include default values so this might have side - // effects). We keep all messages in order. - concatMap(message => { - if (message.kind !== JobOutboundMessageKind.Output) { - return of(message); - } - - return handler.pipe( - switchMap(handler => { - if (handler === null) { - throw new JobDoesNotExistException(name); - } else { - return handler.outputV.pipe( - switchMap(validate => validate(message.value)), - switchMap(output => { - if (!output.success) { - throw new JobOutputSchemaValidationError(output.errors); - } - - return of({ - ...message, - output: output.data as O, - } as JobOutboundMessageOutput); - }), - ); - } - }), - ) as Observable>; - }), - _jobShare(), - ); - - const output = outboundBus.pipe( - filter(x => x.kind == JobOutboundMessageKind.Output), - map((x) => (x as JobOutboundMessageOutput).value), - shareReplay(1), - ); - - // Return the Job. - return { - get state() { return state; }, - argument, - description: handler.pipe( - switchMap(handler => { - if (handler === null) { - throw new JobDoesNotExistException(name); - } else { - return of(handler.jobDescription); - } - }), - ), - output, - getChannel( - name: JobName, - schema: schema.JsonSchema = true, - ): Observable { - let maybeObservable = channels.get(name); - if (!maybeObservable) { - const s = new Subject(); - channelsSubject.set(name, s as unknown as Subject); - channels.set(name, s.asObservable()); - - maybeObservable = s.asObservable(); - } - - return maybeObservable.pipe( - // Keep the order of messages. - concatMap( - message => { - return schemaRegistry.compile(schema).pipe( - switchMap(validate => validate(message)), - filter(x => x.success), - map(x => x.data as T), - ); - }, - ), - ); - }, - ping() { - const id = pingId++; - inboundBus.next({ kind: JobInboundMessageKind.Ping, id }); - - return outboundBus.pipe( - filter(x => x.kind === JobOutboundMessageKind.Pong && x.id == id), - first(), - ignoreElements(), - ); - }, - stop() { - inboundBus.next({ kind: JobInboundMessageKind.Stop }); - }, - input, - inboundBus, - outboundBus, - }; - } - - protected _scheduleJob< - A extends MinimumArgumentT, - I extends MinimumInputT, - O extends MinimumOutputT, - >( - name: JobName, - argument: A, - options: ScheduleJobOptions, - waitable: Observable, - ): Job { - // Get handler first, since this can error out if there's no handler for the job name. - const handler = this._getInternalDescription(name); - - const optionsDeps = (options && options.dependencies) || []; - const dependencies = Array.isArray(optionsDeps) ? optionsDeps : [optionsDeps]; - - const inboundBus = new Subject>(); - const outboundBus = concat( - // Wait for dependencies, make sure to not report messages from dependencies. Subscribe to - // all dependencies at the same time so they run concurrently. - merge(...dependencies.map(x => x.outboundBus)).pipe(ignoreElements()), - - // Wait for pause() to clear (if necessary). - waitable, - - from(handler).pipe( - switchMap(handler => new Observable>( - (subscriber: Observer>) => { - if (!handler) { - throw new JobDoesNotExistException(name); - } - - // Validate the argument. - return handler.argumentV.pipe( - switchMap(validate => validate(argument)), - switchMap(output => { - if (!output.success) { - throw new JobArgumentSchemaValidationError(output.errors); - } - - const argument: A = output.data as A; - const description = handler.jobDescription; - subscriber.next({ kind: JobOutboundMessageKind.OnReady, description }); - - const context = { - description, - dependencies: [...dependencies], - inboundBus: inboundBus.asObservable(), - scheduler: this as Scheduler, - }; - - return handler(argument, context); - }), - ).subscribe(subscriber as Observer>); - })), - ), - ); - - return this._createJob(name, argument, handler, inboundBus, outboundBus); - } -} diff --git a/packages/angular_devkit/core/src/experimental/jobs/simple-scheduler_spec.ts b/packages/angular_devkit/core/src/experimental/jobs/simple-scheduler_spec.ts deleted file mode 100644 index 7c174d8f93ff..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/simple-scheduler_spec.ts +++ /dev/null @@ -1,644 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function no-non-null-assertion -import { EMPTY, Observable, of, timer } from 'rxjs'; -import { map, take, toArray } from 'rxjs/operators'; -import { promisify } from 'util'; -import { JobHandlerContext, JobOutboundMessage, JobOutboundMessageKind, JobState } from './api'; -import { createJobHandler } from './create-job-handler'; -import { SimpleJobRegistry } from './simple-registry'; -import { SimpleScheduler } from './simple-scheduler'; - -const flush = promisify(setImmediate); - -describe('SimpleScheduler', () => { - let registry: SimpleJobRegistry; - let scheduler: SimpleScheduler; - - beforeEach(() => { - registry = new SimpleJobRegistry(); - scheduler = new SimpleScheduler(registry); - }); - - it('works for a simple case', async () => { - registry.register( - 'add', createJobHandler((arg: number[]) => arg.reduce((a, c) => a + c, 0)), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - }, - ); - - const sum = await (scheduler.schedule('add', [1, 2, 3, 4])).output.toPromise(); - expect(sum).toBe(10); - }); - - it('calls jobs in parallel', async () => { - let started = 0; - let finished = 0; - - registry.register( - 'add', - createJobHandler((argument: number[]) => { - started++; - - return new Promise( - resolve => setTimeout(() => { - finished++; - resolve(argument.reduce((a, c) => a + c, 0)); - }, 10), - ); - }), - { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - }, - ); - - const job1 = scheduler.schedule('add', [1, 2, 3, 4]); - const job2 = scheduler.schedule('add', [1, 2, 3, 4, 5]); - expect(started).toBe(0); - - const p1 = job1.output.toPromise(); - await flush(); - expect(started).toBe(1); - - const p2 = job2.output.toPromise(); - await flush(); - expect(started).toBe(2); - expect(finished).toBe(0); - - const [sum, sum2] = await Promise.all([p1, p2]); - expect(started).toBe(2); - expect(finished).toBe(2); - - expect(sum).toBe(10); - expect(sum2).toBe(15); - }); - - it('validates arguments', async () => { - registry.register( - 'add', createJobHandler((arg: number[]) => arg.reduce((a, c) => a + c, 0)), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - }, - ); - - await (scheduler.schedule('add', [1, 2, 3, 4])).output.toPromise(); - try { - await (scheduler.schedule('add', ['1', 2, 3, 4])).output.toPromise(); - expect(true).toBe(false); - } catch (e) { - // TODO: enable this when https://github.com/bazelbuild/rules_typescript/commit/37807e2c4 - // is released, otherwise this breaks because bazel downgrade to ES5 which does not support - // extending Error. - // expect(e instanceof JobInboundMessageSchemaValidationError).toBe(true); - expect(e.message).toMatch(/"\[0\]".*number/); - } - }); - - it('validates outputs', async () => { - registry.register( - 'add', createJobHandler(() => 'hello world'), { - output: { type: 'number' }, - }, - ); - - try { - await (scheduler.schedule('add', [1, 2, 3, 4])).output.toPromise(); - expect(true).toBe(false); - } catch (e) { - // TODO: enable this when https://github.com/bazelbuild/rules_typescript/commit/37807e2c4 - // is released, otherwise this breaks because bazel downgrade to ES5 which does not support - // extending Error. - // expect(e instanceof JobOutputSchemaValidationError).toBe(true); - expect(e.message).toMatch(/"".*number/); - } - }); - - it('works with dependencies', async () => { - const done: number[] = []; - - registry.register( - 'job', - createJobHandler((argument) => { - return new Promise(resolve => setImmediate(() => { - done.push(argument); - resolve(argument); - })); - }), - { argument: true, output: true }, - ); - - // Run jobs. - const job1 = scheduler.schedule('job', 1); - const job2 = scheduler.schedule('job', 2); - const job3 = scheduler.schedule('job', 3); - - // Run a job to wait for 1. - const job4 = scheduler.schedule('job', 4, { dependencies: job1 }); - - // Run a job to wait for 2. - const job5 = scheduler.schedule('job', 5, { dependencies: job2 }); - - // Run a job to wait for 3. - const job6 = scheduler.schedule('job', 6, { dependencies: job3 }); - - // Run a job to wait for 4, 5 and 6. - const job7 = scheduler.schedule('job', 7, { dependencies: [job4, job5, job6] }); - - expect(done.length).toBe(0); - - await job1.output.toPromise(); - expect(done).toContain(1); - expect(done).not.toContain(4); - expect(done).not.toContain(7); - - await job5.output.toPromise(); - expect(done).toContain(1); - expect(done).toContain(2); - expect(done).not.toContain(4); - expect(done).toContain(5); - expect(done).not.toContain(7); - - await job7.output.toPromise(); - expect(done.length).toBe(7); - // Might be out of order. - expect(done).toEqual(jasmine.arrayContaining([1, 2, 3, 4, 5, 6, 7])); - // Verify at least partial order. - expect(done[done.length - 1]).toBe(7); - expect(done.indexOf(4)).toBeGreaterThan(done.indexOf(1)); - expect(done.indexOf(5)).toBeGreaterThan(done.indexOf(2)); - expect(done.indexOf(6)).toBeGreaterThan(done.indexOf(3)); - }); - - it('does not start dependencies until the last one is subscribed to', async () => { - // This test creates the following graph of dependencies: - // 1 <-.-- 2 <-.-- 4 <-.---------.-- 6 - // +-- 3 <-+-- 5 <-' - // Which can result only in the execution orders: [1, 2, 3, 4, 5, 6] - // Only subscribe to the last one. - - const started: number[] = []; - const done: number[] = []; - - registry.register( - 'job', - createJobHandler((argument: number) => { - started.push(argument); - - return new Promise(resolve => setTimeout(() => { - done.push(argument); - resolve(argument); - }, 10)); - }), - { argument: true, output: true }, - ); - - // Run jobs. - const job1 = scheduler.schedule('job', 1); - const job2 = scheduler.schedule('job', 2, { dependencies: job1 }); - const job3 = scheduler.schedule('job', 3, { dependencies: job2 }); - const job4 = scheduler.schedule('job', 4, { dependencies: [job2, job3] }); - const job5 = scheduler.schedule('job', 5, { dependencies: [job1, job2, job4] }); - const job6 = scheduler.schedule('job', 6, { dependencies: [job4, job5] }); - - // Just subscribe to the last job in the lot. - job6.outboundBus.subscribe(); - await flush(); - // Expect the first one to start. - expect(started).toEqual([1]); - // Wait for the first one to finish. - await job1.output.toPromise(); - await flush(); - // Expect the second one to have started, and the first one to be done. - expect(started).toEqual([1, 2]); - expect(done).toEqual([1]); - - // Rinse and repeat. - await job2.output.toPromise(); - await flush(); - expect(started).toEqual([1, 2, 3]); - expect(done).toEqual([1, 2]); - - await job3.output.toPromise(); - await flush(); - expect(started).toEqual([1, 2, 3, 4]); - expect(done).toEqual([1, 2, 3]); - - await job4.output.toPromise(); - await flush(); - expect(started).toEqual([1, 2, 3, 4, 5]); - expect(done).toEqual([1, 2, 3, 4]); - - // Just skip job 5. - await job6.output.toPromise(); - await flush(); - expect(done).toEqual(started); - }); - - it('can be paused', async () => { - let resume: (() => void) | null = null; - - registry.register( - 'job', - createJobHandler((argument, context) => { - return Promise.resolve() - .then(() => { - expect(resume).toBeNull(); - resume = context.scheduler.pause(); - }) - .then(() => argument); - }), - ); - - // Run the job once. Wait for it to finish. We should have a `resume()` and the scheduler will - // be paused. - const p0 = (scheduler.schedule('job', 0)).output.toPromise(); - expect(await p0).toBe(0); - - // This will wait. - const p1 = (scheduler.schedule('job', 1)).output.toPromise(); - await Promise.resolve(); - - expect(resume).not.toBeNull(); - resume !(); - resume = null; - - // Running p1. - expect(await p1).toBe(1); - expect(resume).not.toBeNull(); - - const p2 = (scheduler.schedule('job', 2)).output.toPromise(); - - await Promise.resolve(); - resume !(); - resume = null; - expect(await p2).toBe(2); - expect(resume).not.toBeNull(); - - resume !(); - // Should not error since all jobs have run. - await Promise.resolve(); - }); - - it('can be paused (multiple)', async () => { - const done: number[] = []; - - registry.register( - 'jobA', - createJobHandler((argument: number) => { - done.push(argument); - - return Promise.resolve() - .then(() => argument); - }), - ); - - // Pause manually. - const resume = scheduler.pause(); - const p10 = (scheduler.schedule('jobA', 10)).output.toPromise(); - const p11 = (scheduler.schedule('jobA', 11)).output.toPromise(); - const p12 = (scheduler.schedule('jobA', 12)).output.toPromise(); - await flush(); - - expect(done).toEqual([]); - resume(); - await flush(); - expect(done).not.toEqual([]); - expect(await p10).toBe(10); - expect(await p11).toBe(11); - expect(await p12).toBe(12); - expect(done).toEqual([10, 11, 12]); - }); - - it('can be cancelled by unsubscribing from the raw output', async () => { - const done: number[] = []; - const resolves: (() => void)[] = []; - let keepGoing = true; - - registry.register( - 'job', - createJobHandler((argument: number) => { - return new Observable(observer => { - function fn() { - if (keepGoing) { - const p = new Promise(r => resolves.push(r)); - - observer.next(argument); - done.push(argument); - argument++; - - // tslint:disable-next-line:no-floating-promises - p.then(fn); - } else { - done.push(-1); - observer.complete(); - } - } - - setImmediate(fn); - - return () => { - keepGoing = false; - }; - }); - }), - ); - - const job = scheduler.schedule('job', 0); - await new Promise(r => setTimeout(r, 10)); - expect(job.state).toBe(JobState.Queued); - const subscription = job.output.subscribe(); - - await new Promise(r => setTimeout(r, 10)); - expect(job.state).toBe(JobState.Started); - expect(done).toEqual([0]); - expect(resolves.length).toBe(1); - resolves[0](); - - await new Promise(r => setTimeout(r, 10)); - expect(done).toEqual([0, 1]); - expect(resolves.length).toBe(2); - resolves[1](); - - await new Promise(r => setTimeout(r, 10)); - expect(done).toEqual([0, 1, 2]); - expect(resolves.length).toBe(3); - subscription.unsubscribe(); - resolves[2](); - - job.stop(); - await job.output.toPromise(); - expect(keepGoing).toBe(false); - expect(done).toEqual([0, 1, 2, -1]); - expect(job.state).toBe(JobState.Ended); - }); - - it('sequences raw outputs properly for all use cases', async () => { - registry.register('job-sync', createJobHandler(arg => arg + 1)); - registry.register('job-promise', createJobHandler(arg => { - return Promise.resolve(arg + 1); - })); - registry.register('job-obs-sync', createJobHandler(arg => of(arg + 1))); - registry.register('job-obs-async', createJobHandler(arg => { - return timer(1).pipe( - take(3), - take(1), - map(() => arg + 1), - ); - })); - - const job1 = scheduler.schedule('job-sync', 100); - const job1OutboundBus = await job1.outboundBus.pipe( - // Descriptions are going to differ, so get rid of those. - map(x => ({ ...x, description: null })), - toArray(), - ).toPromise(); - - const job2 = scheduler.schedule('job-promise', 100); - const job2OutboundBus = await job2.outboundBus.pipe( - // Descriptions are going to differ, so get rid of those. - map(x => ({ ...x, description: null })), - toArray(), - ).toPromise(); - - const job3 = scheduler.schedule('job-obs-sync', 100); - const job3OutboundBus = await job3.outboundBus.pipe( - // Descriptions are going to differ, so get rid of those. - map(x => ({ ...x, description: null })), - toArray(), - ).toPromise(); - - const job4 = scheduler.schedule('job-obs-async', 100); - const job4OutboundBus = await job4.outboundBus.pipe( - // Descriptions are going to differ, so get rid of those. - map(x => ({ ...x, description: null })), - toArray(), - ).toPromise(); - - // The should all report the same stuff. - expect(job1OutboundBus).toEqual(job4OutboundBus); - expect(job2OutboundBus).toEqual(job4OutboundBus); - expect(job3OutboundBus).toEqual(job4OutboundBus); - }); - - describe('channels', () => { - it('works', async () => { - registry.register( - 'job', - createJobHandler((argument, context) => { - const channel = context.createChannel('any'); - channel.next('hello world'); - channel.complete(); - - return 0; - }), - ); - - const job = scheduler.schedule('job', 0); - let sideValue = ''; - const c = job.getChannel('any') as Observable; - c.subscribe(x => sideValue = x); - - expect(await job.output.toPromise()).toBe(0); - expect(sideValue).toBe('hello world'); - }); - - it('validates', async () => { - registry.register( - 'job', - createJobHandler((argument, context) => { - const channel = context.createChannel('any'); - channel.next('hello world'); - channel.complete(); - - return 0; - }), { - argument: true, - output: true, - }, - ); - - const job = scheduler.schedule('job', 0); - let sideValue = ''; - const c = job.getChannel('any', { type: 'number' }) as Observable; - expect(c).toBeDefined(null); - - if (c) { - c.subscribe(x => sideValue = x); - } - - expect(await job.output.toPromise()).toBe(0); - expect(sideValue).not.toBe('hello world'); - }); - }); - - describe('lifecycle messages', () => { - it('sequences double start once', async () => { - const fn = (_: never, { description }: JobHandlerContext) => { - return new Observable>(observer => { - observer.next({ kind: JobOutboundMessageKind.Start, description }); - observer.next({ kind: JobOutboundMessageKind.Start, description }); - observer.next({ kind: JobOutboundMessageKind.End, description }); - observer.complete(); - }); - }; - - registry.register('job', Object.assign(fn, { jobDescription: {} })); - const allOutput = await scheduler.schedule('job', 0).outboundBus.pipe( - toArray(), - ).toPromise(); - - expect(allOutput.map(x => ({ ...x, description: null }))).toEqual([ - { kind: JobOutboundMessageKind.OnReady, description: null }, - { kind: JobOutboundMessageKind.Start, description: null }, - { kind: JobOutboundMessageKind.End, description: null }, - ]); - }); - - it('add an End if there is not one', async () => { - const fn = () => EMPTY; - - registry.register('job', Object.assign(fn, { jobDescription: {} })); - const allOutput = await scheduler.schedule('job', 0).outboundBus.pipe( - toArray(), - ).toPromise(); - - expect(allOutput.map(x => ({ ...x, description: null }))).toEqual([ - { kind: JobOutboundMessageKind.OnReady, description: null }, - { kind: JobOutboundMessageKind.End, description: null }, - ]); - }); - - it('only one End', async () => { - const fn = (_: never, { description }: JobHandlerContext) => { - return new Observable>(observer => { - observer.next({ kind: JobOutboundMessageKind.End, description }); - observer.next({ kind: JobOutboundMessageKind.End, description }); - observer.complete(); - }); - }; - - registry.register('job', Object.assign(fn, { jobDescription: {} })); - const allOutput = await scheduler.schedule('job', 0).outboundBus.pipe( - toArray(), - ).toPromise(); - - expect(allOutput.map(x => ({ ...x, description: null }))).toEqual([ - { kind: JobOutboundMessageKind.OnReady, description: null }, - { kind: JobOutboundMessageKind.End, description: null }, - ]); - }); - }); - - describe('input', () => { - it('works', async () => { - registry.register( - 'job', - createJobHandler((argument, context) => { - return new Observable(subscriber => { - context.input.subscribe(x => { - if (x === null) { - subscriber.complete(); - } else { - subscriber.next(parseInt('' + x) + argument); - } - }); - }); - }), - ); - - const job = scheduler.schedule('job', 100); - const outputs: number[] = []; - - job.output.subscribe(x => outputs.push(x as number)); - - job.input.next(1); - job.input.next('2'); - job.input.next(3); - job.input.next(null); - - expect(await job.output.toPromise()).toBe(103); - expect(outputs).toEqual([101, 102, 103]); - }); - - it('validates', async () => { - const handler = createJobHandler((argument, context) => { - return new Observable(subscriber => { - context.input.subscribe(x => { - if (x === null) { - subscriber.complete(); - } else { - subscriber.next(parseInt('' + x) + argument); - } - }); - }); - }, { - input: { anyOf: [{ type: 'number' }, { type: 'null' }] }, - }); - - registry.register('job', handler); - - const job = scheduler.schedule('job', 100); - const outputs: number[] = []; - - job.output.subscribe(x => outputs.push(x as number)); - - job.input.next(1); - job.input.next('2'); - job.input.next(3); - job.input.next(null); - - expect(await job.output.toPromise()).toBe(103); - expect(outputs).toEqual([101, 103]); - }); - - it('works deferred', async () => { - // This is a more complex test. The job returns an output deferred from the input - registry.register( - 'job', - createJobHandler((argument, context) => { - return new Observable(subscriber => { - context.input.subscribe(x => { - if (x === null) { - setTimeout(() => subscriber.complete(), 10); - } else { - setTimeout(() => subscriber.next(parseInt('' + x) + argument), x); - } - }); - }); - }), - ); - - const job = scheduler.schedule('job', 100); - const outputs: number[] = []; - - job.output.subscribe(x => outputs.push(x as number)); - - job.input.next(1); - job.input.next(2); - job.input.next(3); - job.input.next(null); - - expect(await job.output.toPromise()).toBe(103); - expect(outputs).toEqual(jasmine.arrayWithExactContents([101, 102, 103])); - }); - }); - - it('propagates errors', async () => { - registry.register('job', createJobHandler(() => { throw 1; })); - const job = scheduler.schedule('job', 0); - - try { - await job.output.toPromise(); - expect('THE ABOVE LINE SHOULD NOT ERROR').toBe('false'); - } catch (error) { - expect(error).toBe(1); - } - }); -}); diff --git a/packages/angular_devkit/core/src/experimental/jobs/strategy_spec.ts b/packages/angular_devkit/core/src/experimental/jobs/strategy_spec.ts deleted file mode 100644 index dba165d4be5e..000000000000 --- a/packages/angular_devkit/core/src/experimental/jobs/strategy_spec.ts +++ /dev/null @@ -1,298 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { promisify } from 'util'; -import { JobState } from './api'; -import { createJobHandler } from './create-job-handler'; -import { SimpleJobRegistry } from './simple-registry'; -import { SimpleScheduler } from './simple-scheduler'; -import { strategy } from './strategy'; - -const flush = promisify(setImmediate); - -describe('strategy.serialize()', () => { - let registry: SimpleJobRegistry; - let scheduler: SimpleScheduler; - - beforeEach(() => { - registry = new SimpleJobRegistry(); - scheduler = new SimpleScheduler(registry); - }); - - it('works', async () => { - let started = 0; - let finished = 0; - - registry.register(strategy.serialize()(createJobHandler((input: number[]) => { - started++; - - return new Promise( - resolve => setTimeout(() => { - finished++; - resolve(input.reduce((a, c) => a + c, 0)); - }, 10), - ); - // tslint:disable-next-line:no-any - }) as any), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - name: 'add', - }); - - const job1 = scheduler.schedule('add', [1, 2, 3, 4]); - const job2 = scheduler.schedule('add', [1, 2, 3, 4, 5]); - expect(started).toBe(0); - expect(finished).toBe(0); - - job1.output.subscribe(); - await flush(); - expect(started).toBe(1); - - job2.output.subscribe(); - await flush(); - expect(started).toBe(1); // Job2 starts when Job1 ends. - - expect(finished).toBe(0); - - await Promise.all([ - job1.output.toPromise().then(s => { - expect(finished).toBe(1); - expect(s).toBe(10); - }), - job2.output.toPromise().then(s => { - expect(finished).toBe(2); - expect(s).toBe(15); - }), - ]); - - expect(started).toBe(2); - expect(finished).toBe(2); - }); - - it('works across jobs', async () => { - let started = 0; - let finished = 0; - - const strategy1 = strategy.serialize(); - - registry.register(strategy1(createJobHandler((input: number[]) => { - started++; - - return new Promise( - resolve => setTimeout(() => { - finished++; - resolve(input.reduce((a, c) => a + c, 0)); - }, 10), - ); - // tslint:disable-next-line:no-any - }) as any), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - name: 'add', - }); - registry.register(strategy1(createJobHandler((input: number[]) => { - started++; - - return new Promise( - resolve => setTimeout(() => { - finished++; - resolve(input.reduce((a, c) => a + c, 100)); - }, 10), - ); - // tslint:disable-next-line:no-any - }) as any), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - name: 'add100', - }); - - const job1 = scheduler.schedule('add', [1, 2, 3, 4]); - const job2 = scheduler.schedule('add100', [1, 2, 3, 4, 5]); - expect(started).toBe(0); - expect(finished).toBe(0); - - job1.output.subscribe(); - await flush(); - expect(started).toBe(1); - - job2.output.subscribe(); - await flush(); - expect(started).toBe(1); // Job2 starts when Job1 ends. - - expect(finished).toBe(0); - - await Promise.all([ - job1.output.toPromise().then(s => { - expect(finished).toBe(1); - expect(s).toBe(10); - }), - job2.output.toPromise().then(s => { - expect(finished).toBe(2); - expect(s).toBe(115); - }), - ]); - - expect(started).toBe(2); - expect(finished).toBe(2); - }); -}); - -describe('strategy.reuse()', () => { - let registry: SimpleJobRegistry; - let scheduler: SimpleScheduler; - - beforeEach(() => { - registry = new SimpleJobRegistry(); - scheduler = new SimpleScheduler(registry); - }); - - it('works', async () => { - let started = 0; - let finished = 0; - - registry.register(strategy.reuse()(createJobHandler((input: number[]) => { - started++; - - return new Promise( - resolve => setTimeout(() => { - finished++; - resolve(input.reduce((a, c) => a + c, 0)); - }, 10), - ); - // tslint:disable-next-line:no-any - }) as any), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - name: 'add', - }); - - const job1 = scheduler.schedule('add', [1, 2, 3, 4]); - const job2 = scheduler.schedule('add', []); - expect(started).toBe(0); - expect(finished).toBe(0); - - job1.output.subscribe(); - await flush(); - expect(started).toBe(1); - expect(finished).toBe(0); - - job2.output.subscribe(); - expect(started).toBe(1); // job2 is reusing job1. - expect(finished).toBe(0); - - let result = await job1.output.toPromise(); - expect(result).toBe(10); - expect(started).toBe(1); - expect(finished).toBe(1); - expect(job1.state).toBe(JobState.Ended); - expect(job2.state).toBe(JobState.Ended); - - const job3 = scheduler.schedule('add', [1, 2, 3, 4, 5]); - const job4 = scheduler.schedule('add', []); - job3.output.subscribe(); - await flush(); - expect(started).toBe(2); - expect(finished).toBe(1); - - job4.output.subscribe(); - expect(started).toBe(2); // job4 is reusing job3. - expect(finished).toBe(1); - - result = await job3.output.toPromise(); - expect(result).toBe(15); - expect(started).toBe(2); - expect(finished).toBe(2); - expect(job3.state).toBe(JobState.Ended); - expect(job4.state).toBe(JobState.Ended); - }); -}); - -describe('strategy.memoize()', () => { - let registry: SimpleJobRegistry; - let scheduler: SimpleScheduler; - - beforeEach(() => { - registry = new SimpleJobRegistry(); - scheduler = new SimpleScheduler(registry); - }); - - it('works', async () => { - let started = 0; - let finished = 0; - - registry.register(strategy.memoize()(createJobHandler((input: number[]) => { - started++; - - return new Promise( - resolve => setTimeout(() => { - finished++; - resolve(input.reduce((a, c) => a + c, 0)); - }, 10), - ); - // tslint:disable-next-line:no-any - }) as any), { - argument: { items: { type: 'number' } }, - output: { type: 'number' }, - name: 'add', - }); - - const job1 = scheduler.schedule('add', [1, 2, 3, 4]); - const job2 = scheduler.schedule('add', [1, 2, 3, 4]); - const job3 = scheduler.schedule('add', [1, 2, 3, 4, 5]); - const job4 = scheduler.schedule('add', [1, 2, 3, 4, 5]); - expect(started).toBe(0); - expect(finished).toBe(0); - - job1.output.subscribe(); - await flush(); - expect(started).toBe(1); - expect(finished).toBe(0); - - job2.output.subscribe(); - expect(started).toBe(1); // job2 is reusing job1. - expect(finished).toBe(0); - - job3.output.subscribe(); - await flush(); - expect(started).toBe(2); - expect(finished).toBe(0); - - job4.output.subscribe(); - expect(started).toBe(2); // job4 is reusing job3. - expect(finished).toBe(0); - - await Promise.all([ - job1.output.toPromise().then(s => { - // This is hard since job3 and job1 might finish out of order. - expect(finished).toBeGreaterThanOrEqual(1); - expect(s).toBe(10); - }), - job2.output.toPromise().then(s => { - // This is hard since job3 and job1 might finish out of order. - expect(finished).toBeGreaterThanOrEqual(1); - expect(job1.state).toBe(JobState.Ended); - expect(job2.state).toBe(JobState.Ended); - expect(s).toBe(10); - }), - job3.output.toPromise().then(s => { - // This is hard since job3 and job1 might finish out of order. - expect(finished).toBeGreaterThanOrEqual(1); - expect(s).toBe(15); - }), - job4.output.toPromise().then(s => { - expect(job3.state).toBe(JobState.Ended); - expect(job4.state).toBe(JobState.Ended); - // This is hard since job3 and job1 might finish out of order. - expect(finished).toBeGreaterThanOrEqual(1); - expect(s).toBe(15); - }), - ]); - - expect(started).toBe(2); - expect(finished).toBe(2); - }); -}); diff --git a/packages/angular_devkit/core/src/index.ts b/packages/angular_devkit/core/src/index.ts index 2df15fc70f9f..4f85dca195e7 100644 --- a/packages/angular_devkit/core/src/index.ts +++ b/packages/angular_devkit/core/src/index.ts @@ -1,25 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as analytics from './analytics'; -import * as experimental from './experimental'; + import * as json from './json/index'; import * as logging from './logger/index'; import * as workspaces from './workspace'; -export * from './exception/exception'; +export * from './exception'; export * from './json/index'; export * from './utils/index'; export * from './virtual-fs/index'; -export { - analytics, - experimental, - json, - logging, - workspaces, -}; +export { json, logging, workspaces }; diff --git a/packages/angular_devkit/core/src/json/index.ts b/packages/angular_devkit/core/src/json/index.ts index 16bc0d1ff55c..bb03c76a0907 100644 --- a/packages/angular_devkit/core/src/json/index.ts +++ b/packages/angular_devkit/core/src/json/index.ts @@ -1,12 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -export * from './interface'; -export * from './parser'; import * as schema from './schema/index'; + +export * from './utils'; export { schema }; diff --git a/packages/angular_devkit/core/src/json/interface.ts b/packages/angular_devkit/core/src/json/interface.ts deleted file mode 100644 index 8769239695cf..000000000000 --- a/packages/angular_devkit/core/src/json/interface.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -export interface Position { - readonly offset: number; - - readonly line: number; - readonly character: number; -} - - -export type JsonAstNode = JsonAstNumber - | JsonAstString - | JsonAstIdentifier - | JsonAstArray - | JsonAstObject - | JsonAstConstantFalse - | JsonAstConstantNull - | JsonAstConstantTrue; - - -export interface JsonAstNodeBase { - readonly start: Position; - readonly end: Position; - readonly text: string; - - readonly comments?: (JsonAstComment | JsonAstMultilineComment)[]; -} - - -export interface JsonAstNumber extends JsonAstNodeBase { - readonly kind: 'number'; - readonly value: number; -} - - -export interface JsonAstString extends JsonAstNodeBase { - readonly kind: 'string'; - readonly value: string; -} - - -export interface JsonAstIdentifier extends JsonAstNodeBase { - readonly kind: 'identifier'; - readonly value: string; -} - - -export interface JsonArray extends Array {} - - -export interface JsonAstArray extends JsonAstNodeBase { - readonly kind: 'array'; - readonly elements: JsonAstNode[]; - readonly value: JsonArray; -} - - -export interface JsonObject { - [prop: string]: JsonValue; -} - - -export interface JsonAstKeyValue extends JsonAstNodeBase { - readonly kind: 'keyvalue'; - readonly key: JsonAstString | JsonAstIdentifier; - readonly value: JsonAstNode; -} - - -export interface JsonAstObject extends JsonAstNodeBase { - readonly kind: 'object'; - readonly properties: JsonAstKeyValue[]; - readonly value: JsonObject; -} - - -export interface JsonAstConstantFalse extends JsonAstNodeBase { - readonly kind: 'false'; - readonly value: false; -} - - -export interface JsonAstConstantNull extends JsonAstNodeBase { - readonly kind: 'null'; - readonly value: null; -} - - -export interface JsonAstConstantTrue extends JsonAstNodeBase { - readonly kind: 'true'; - readonly value: true; -} - - -// Loose mode AST. -export interface JsonAstMultilineComment extends JsonAstNodeBase { - readonly kind: 'multicomment'; - readonly content: string; -} -export interface JsonAstComment extends JsonAstNodeBase { - readonly kind: 'comment'; - readonly content: string; -} - - -export type JsonValue = JsonAstNode['value']; - -export function isJsonObject(value: JsonValue): value is JsonObject { - return value != null && typeof value === 'object' && !Array.isArray(value); -} - -export function isJsonArray(value: JsonValue): value is JsonArray { - return Array.isArray(value); -} diff --git a/packages/angular_devkit/core/src/json/parser.ts b/packages/angular_devkit/core/src/json/parser.ts deleted file mode 100644 index 243b51c4f0ea..000000000000 --- a/packages/angular_devkit/core/src/json/parser.ts +++ /dev/null @@ -1,917 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { BaseException } from '../exception'; -import { - JsonArray, - JsonAstArray, - JsonAstComment, - JsonAstConstantFalse, - JsonAstConstantNull, - JsonAstConstantTrue, - JsonAstIdentifier, - JsonAstKeyValue, - JsonAstMultilineComment, - JsonAstNode, - JsonAstNumber, - JsonAstObject, - JsonAstString, - JsonObject, - JsonValue, - Position, -} from './interface'; - -export class JsonException extends BaseException {} - -/** - * A character was invalid in this context. - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - */ -export class InvalidJsonCharacterException extends JsonException { - invalidChar: string; - line: number; - character: number; - offset: number; - - constructor(context: JsonParserContext) { - const pos = context.previous; - const invalidChar = JSON.stringify(_peek(context)); - super(`Invalid JSON character: ${invalidChar} at ${pos.line}:${pos.character}.`); - - this.invalidChar = invalidChar; - this.line = pos.line; - this.offset = pos.offset; - this.character = pos.character; - } -} - - -/** - * More input was expected, but we reached the end of the stream. - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - */ -export class UnexpectedEndOfInputException extends JsonException { - constructor(_context: JsonParserContext) { - super(`Unexpected end of file.`); - } -} - -/** - * An error happened within a file. - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - */ -export class PathSpecificJsonException extends JsonException { - constructor(public path: string, public exception: JsonException) { - super(`An error happened at file path ${JSON.stringify(path)}: ${exception.message}`); - } -} - -/** - * Context passed around the parser with information about where we currently are in the parse. - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - */ -export interface JsonParserContext { - position: Position; - previous: Position; - readonly original: string; - readonly mode: JsonParseMode; -} - - -/** - * Peek and return the next character from the context. - * @private - */ -function _peek(context: JsonParserContext): string | undefined { - return context.original[context.position.offset]; -} - - -/** - * Move the context to the next character, including incrementing the line if necessary. - * @private - */ -function _next(context: JsonParserContext) { - context.previous = context.position; - - let {offset, line, character} = context.position; - const char = context.original[offset]; - offset++; - if (char == '\n') { - line++; - character = 0; - } else { - character++; - } - context.position = {offset, line, character}; -} - - -/** - * Read a single character from the input. If a `valid` string is passed, validate that the - * character is included in the valid string. - * @private - */ -function _token(context: JsonParserContext, valid: string): string; -function _token(context: JsonParserContext): string | undefined; -function _token(context: JsonParserContext, valid?: string): string | undefined { - const char = _peek(context); - if (valid) { - if (!char) { - throw new UnexpectedEndOfInputException(context); - } - if (valid.indexOf(char) == -1) { - throw new InvalidJsonCharacterException(context); - } - } - - // Move the position of the context to the next character. - _next(context); - - return char; -} - - -/** - * Read the exponent part of a number. The exponent part is looser for JSON than the number - * part. `str` is the string of the number itself found so far, and start the position - * where the full number started. Returns the node found. - * @private - */ -function _readExpNumber(context: JsonParserContext, - start: Position, - str: string, - comments: (JsonAstComment | JsonAstMultilineComment)[]): JsonAstNumber { - let char; - let signed = false; - - while (true) { - char = _token(context); - if (char == '+' || char == '-') { - if (signed) { - break; - } - signed = true; - str += char; - } else if (char == '0' || char == '1' || char == '2' || char == '3' || char == '4' - || char == '5' || char == '6' || char == '7' || char == '8' || char == '9') { - signed = true; - str += char; - } else { - break; - } - } - - // We're done reading this number. - context.position = context.previous; - - return { - kind: 'number', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - value: Number.parseFloat(str), - comments: comments, - }; -} - - -/** - * Read the hexa part of a 0xBADCAFE hexadecimal number. - * @private - */ -function _readHexaNumber(context: JsonParserContext, - isNegative: boolean, - start: Position, - comments: (JsonAstComment | JsonAstMultilineComment)[]): JsonAstNumber { - // Read an hexadecimal number, until it's not hexadecimal. - let hexa = ''; - const valid = '0123456789abcdefABCDEF'; - - for (let ch = _peek(context); ch && valid.includes(ch); ch = _peek(context)) { - // Add it to the hexa string. - hexa += ch; - // Move the position of the context to the next character. - _next(context); - } - - const value = Number.parseInt(hexa, 16); - - // We're done reading this number. - return { - kind: 'number', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - value: isNegative ? -value : value, - comments, - }; -} - -/** - * Read a number from the context. - * @private - */ -function _readNumber(context: JsonParserContext, comments = _readBlanks(context)): JsonAstNumber { - let str = ''; - let dotted = false; - const start = context.position; - - // read until `e` or end of line. - while (true) { - const char = _token(context); - - // Read tokens, one by one. - if (char == '-') { - if (str != '') { - throw new InvalidJsonCharacterException(context); - } - } else if (char == 'I' - && (str == '-' || str == '' || str == '+') - && (context.mode & JsonParseMode.NumberConstantsAllowed) != 0) { - // Infinity? - // _token(context, 'I'); Already read. - _token(context, 'n'); - _token(context, 'f'); - _token(context, 'i'); - _token(context, 'n'); - _token(context, 'i'); - _token(context, 't'); - _token(context, 'y'); - - str += 'Infinity'; - break; - } else if (char == '0') { - if (str == '0' || str == '-0') { - throw new InvalidJsonCharacterException(context); - } - } else if (char == '1' || char == '2' || char == '3' || char == '4' || char == '5' - || char == '6' || char == '7' || char == '8' || char == '9') { - if (str == '0' || str == '-0') { - throw new InvalidJsonCharacterException(context); - } - } else if (char == '+' && str == '') { - // Pass over. - } else if (char == '.') { - if (dotted) { - throw new InvalidJsonCharacterException(context); - } - dotted = true; - } else if (char == 'e' || char == 'E') { - return _readExpNumber(context, start, str + char, comments); - } else if (char == 'x' && (str == '0' || str == '-0') - && (context.mode & JsonParseMode.HexadecimalNumberAllowed) != 0) { - return _readHexaNumber(context, str == '-0', start, comments); - } else { - // We read one too many characters, so rollback the last character. - context.position = context.previous; - break; - } - - str += char; - } - - // We're done reading this number. - if (str.endsWith('.') && (context.mode & JsonParseMode.HexadecimalNumberAllowed) == 0) { - throw new InvalidJsonCharacterException(context); - } - - return { - kind: 'number', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - value: Number.parseFloat(str), - comments, - }; -} - - -/** - * Read a string from the context. Takes the comments of the string or read the blanks before the - * string. - * @private - */ -function _readString(context: JsonParserContext, comments = _readBlanks(context)): JsonAstString { - const start = context.position; - - // Consume the first string delimiter. - const delim = _token(context); - if ((context.mode & JsonParseMode.SingleQuotesAllowed) == 0) { - if (delim == '\'') { - throw new InvalidJsonCharacterException(context); - } - } - - let str = ''; - while (true) { - let char = _token(context); - if (char == delim) { - return { - kind: 'string', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - value: str, - comments: comments, - }; - } else if (char == '\\') { - char = _token(context); - switch (char) { - case '\\': - case '\/': - case '"': - case delim: - str += char; - break; - - case 'b': str += '\b'; break; - case 'f': str += '\f'; break; - case 'n': str += '\n'; break; - case 'r': str += '\r'; break; - case 't': str += '\t'; break; - case 'u': - const [c0] = _token(context, '0123456789abcdefABCDEF'); - const [c1] = _token(context, '0123456789abcdefABCDEF'); - const [c2] = _token(context, '0123456789abcdefABCDEF'); - const [c3] = _token(context, '0123456789abcdefABCDEF'); - str += String.fromCharCode(parseInt(c0 + c1 + c2 + c3, 16)); - break; - - case undefined: - throw new UnexpectedEndOfInputException(context); - - case '\n': - // Only valid when multiline strings are allowed. - if ((context.mode & JsonParseMode.MultiLineStringAllowed) == 0) { - throw new InvalidJsonCharacterException(context); - } - str += char; - break; - - default: - throw new InvalidJsonCharacterException(context); - } - } else if (char === undefined) { - throw new UnexpectedEndOfInputException(context); - } else if (char == '\b' || char == '\f' || char == '\n' || char == '\r' || char == '\t') { - throw new InvalidJsonCharacterException(context); - } else { - str += char; - } - } -} - - -/** - * Read the constant `true` from the context. - * @private - */ -function _readTrue(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstConstantTrue { - const start = context.position; - _token(context, 't'); - _token(context, 'r'); - _token(context, 'u'); - _token(context, 'e'); - - const end = context.position; - - return { - kind: 'true', - start, - end, - text: context.original.substring(start.offset, end.offset), - value: true, - comments, - }; -} - - -/** - * Read the constant `false` from the context. - * @private - */ -function _readFalse(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstConstantFalse { - const start = context.position; - _token(context, 'f'); - _token(context, 'a'); - _token(context, 'l'); - _token(context, 's'); - _token(context, 'e'); - - const end = context.position; - - return { - kind: 'false', - start, - end, - text: context.original.substring(start.offset, end.offset), - value: false, - comments, - }; -} - - -/** - * Read the constant `null` from the context. - * @private - */ -function _readNull(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstConstantNull { - const start = context.position; - - _token(context, 'n'); - _token(context, 'u'); - _token(context, 'l'); - _token(context, 'l'); - - const end = context.position; - - return { - kind: 'null', - start, - end, - text: context.original.substring(start.offset, end.offset), - value: null, - comments: comments, - }; -} - - -/** - * Read the constant `NaN` from the context. - * @private - */ -function _readNaN(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstNumber { - const start = context.position; - - _token(context, 'N'); - _token(context, 'a'); - _token(context, 'N'); - - const end = context.position; - - return { - kind: 'number', - start, - end, - text: context.original.substring(start.offset, end.offset), - value: NaN, - comments: comments, - }; -} - - -/** - * Read an array of JSON values from the context. - * @private - */ -function _readArray(context: JsonParserContext, comments = _readBlanks(context)): JsonAstArray { - const start = context.position; - - // Consume the first delimiter. - _token(context, '['); - const value: JsonArray = []; - const elements: JsonAstNode[] = []; - - _readBlanks(context); - if (_peek(context) != ']') { - const node = _readValue(context); - elements.push(node); - value.push(node.value); - } - - while (_peek(context) != ']') { - _token(context, ','); - - const valueComments = _readBlanks(context); - if ((context.mode & JsonParseMode.TrailingCommasAllowed) !== 0 && _peek(context) === ']') { - break; - } - const node = _readValue(context, valueComments); - elements.push(node); - value.push(node.value); - } - - _token(context, ']'); - - return { - kind: 'array', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - value, - elements, - comments, - }; -} - - -/** - * Read an identifier from the context. An identifier is a valid JavaScript identifier, and this - * function is only used in Loose mode. - * @private - */ -function _readIdentifier(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstIdentifier { - const start = context.position; - - let char = _peek(context); - if (char && '0123456789'.indexOf(char) != -1) { - const identifierNode = _readNumber(context); - - return { - kind: 'identifier', - start, - end: identifierNode.end, - text: identifierNode.text, - value: identifierNode.value.toString(), - }; - } - - const identValidFirstChar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ'; - const identValidChar = '_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ0123456789'; - let first = true; - let value = ''; - - while (true) { - char = _token(context); - if (char == undefined - || (first ? identValidFirstChar.indexOf(char) : identValidChar.indexOf(char)) == -1) { - context.position = context.previous; - - return { - kind: 'identifier', - start, - end: context.position, - text: context.original.substr(start.offset, context.position.offset), - value, - comments, - }; - } - - value += char; - first = false; - } -} - - -/** - * Read a property from the context. A property is a string or (in Loose mode only) a number or - * an identifier, followed by a colon `:`. - * @private - */ -function _readProperty(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstKeyValue { - const start = context.position; - - let key; - if ((context.mode & JsonParseMode.IdentifierKeyNamesAllowed) != 0) { - const top = _peek(context); - if (top == '"' || top == '\'') { - key = _readString(context); - } else { - key = _readIdentifier(context); - } - } else { - key = _readString(context); - } - - _readBlanks(context); - _token(context, ':'); - const value = _readValue(context); - const end = context.position; - - return { - kind: 'keyvalue', - key, - value, - start, - end, - text: context.original.substring(start.offset, end.offset), - comments, - }; -} - - -/** - * Read an object of properties -> JSON values from the context. - * @private - */ -function _readObject(context: JsonParserContext, - comments = _readBlanks(context)): JsonAstObject { - const start = context.position; - // Consume the first delimiter. - _token(context, '{'); - const value: JsonObject = {}; - const properties: JsonAstKeyValue[] = []; - - _readBlanks(context); - if (_peek(context) != '}') { - const property = _readProperty(context); - value[property.key.value] = property.value.value; - properties.push(property); - - while (_peek(context) != '}') { - _token(context, ','); - - const propertyComments = _readBlanks(context); - if ((context.mode & JsonParseMode.TrailingCommasAllowed) !== 0 && _peek(context) === '}') { - break; - } - const property = _readProperty(context, propertyComments); - value[property.key.value] = property.value.value; - properties.push(property); - } - } - - _token(context, '}'); - - return { - kind: 'object', - properties, - start, - end: context.position, - value, - text: context.original.substring(start.offset, context.position.offset), - comments, - }; -} - - -/** - * Remove any blank character or comments (in Loose mode) from the context, returning an array - * of comments if any are found. - * @private - */ -function _readBlanks(context: JsonParserContext): (JsonAstComment | JsonAstMultilineComment)[] { - if ((context.mode & JsonParseMode.CommentsAllowed) != 0) { - const comments: (JsonAstComment | JsonAstMultilineComment)[] = []; - while (true) { - const char = context.original[context.position.offset]; - if (char == '/' && context.original[context.position.offset + 1] == '*') { - const start = context.position; - // Multi line comment. - _next(context); - _next(context); - - while (context.original[context.position.offset] != '*' - || context.original[context.position.offset + 1] != '/') { - _next(context); - if (context.position.offset >= context.original.length) { - throw new UnexpectedEndOfInputException(context); - } - } - // Remove "*/". - _next(context); - _next(context); - - comments.push({ - kind: 'multicomment', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - content: context.original.substring(start.offset + 2, context.position.offset - 2), - }); - } else if (char == '/' && context.original[context.position.offset + 1] == '/') { - const start = context.position; - // Multi line comment. - _next(context); - _next(context); - - while (context.original[context.position.offset] != '\n') { - _next(context); - if (context.position.offset >= context.original.length) { - break; - } - } - - // Remove "\n". - if (context.position.offset < context.original.length) { - _next(context); - } - comments.push({ - kind: 'comment', - start, - end: context.position, - text: context.original.substring(start.offset, context.position.offset), - content: context.original.substring(start.offset + 2, context.position.offset - 1), - }); - } else if (char == ' ' || char == '\t' || char == '\n' || char == '\r' || char == '\f') { - _next(context); - } else { - break; - } - } - - return comments; - } else { - let char = context.original[context.position.offset]; - while (char == ' ' || char == '\t' || char == '\n' || char == '\r' || char == '\f') { - _next(context); - char = context.original[context.position.offset]; - } - - return []; - } -} - - -/** - * Read a JSON value from the context, which can be any form of JSON value. - * @private - */ -function _readValue(context: JsonParserContext, comments = _readBlanks(context)): JsonAstNode { - let result: JsonAstNode; - - // Clean up before. - const char = _peek(context); - switch (char) { - case undefined: - throw new UnexpectedEndOfInputException(context); - - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - result = _readNumber(context, comments); - break; - - case '.': - case '+': - if ((context.mode & JsonParseMode.LaxNumberParsingAllowed) == 0) { - throw new InvalidJsonCharacterException(context); - } - result = _readNumber(context, comments); - break; - - case '\'': - case '"': - result = _readString(context, comments); - break; - - case 'I': - if ((context.mode & JsonParseMode.NumberConstantsAllowed) == 0) { - throw new InvalidJsonCharacterException(context); - } - result = _readNumber(context, comments); - break; - - case 'N': - if ((context.mode & JsonParseMode.NumberConstantsAllowed) == 0) { - throw new InvalidJsonCharacterException(context); - } - result = _readNaN(context, comments); - break; - - case 't': - result = _readTrue(context, comments); - break; - case 'f': - result = _readFalse(context, comments); - break; - case 'n': - result = _readNull(context, comments); - break; - - case '[': - result = _readArray(context, comments); - break; - - case '{': - result = _readObject(context, comments); - break; - - default: - throw new InvalidJsonCharacterException(context); - } - - // Clean up after. - _readBlanks(context); - - return result; -} - - -/** - * The Parse mode used for parsing the JSON string. - */ -export enum JsonParseMode { - Strict = 0, // Standard JSON. - CommentsAllowed = 1 << 0, // Allows comments, both single or multi lines. - SingleQuotesAllowed = 1 << 1, // Allow single quoted strings. - IdentifierKeyNamesAllowed = 1 << 2, // Allow identifiers as objectp properties. - TrailingCommasAllowed = 1 << 3, - HexadecimalNumberAllowed = 1 << 4, - MultiLineStringAllowed = 1 << 5, - LaxNumberParsingAllowed = 1 << 6, // Allow `.` or `+` as the first character of a number. - NumberConstantsAllowed = 1 << 7, // Allow -Infinity, Infinity and NaN. - - Default = Strict, - Loose = CommentsAllowed | SingleQuotesAllowed | - IdentifierKeyNamesAllowed | TrailingCommasAllowed | - HexadecimalNumberAllowed | MultiLineStringAllowed | - LaxNumberParsingAllowed | NumberConstantsAllowed, - - Json = Strict, - Json5 = Loose, -} - - -/** - * Parse the JSON string and return its AST. The AST may be losing data (end comments are - * discarded for example, and space characters are not represented in the AST), but all values - * will have a single node in the AST (a 1-to-1 mapping). - * - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - * @param input The string to use. - * @param mode The mode to parse the input with. {@see JsonParseMode}. - * @returns {JsonAstNode} The root node of the value of the AST. - */ -export function parseJsonAst(input: string, mode = JsonParseMode.Default): JsonAstNode { - if (mode == JsonParseMode.Default) { - mode = JsonParseMode.Strict; - } - - const context = { - position: { offset: 0, line: 0, character: 0 }, - previous: { offset: 0, line: 0, character: 0 }, - original: input, - comments: undefined, - mode, - }; - - const ast = _readValue(context); - if (context.position.offset < input.length) { - const rest = input.substr(context.position.offset); - const i = rest.length > 20 ? rest.substr(0, 20) + '...' : rest; - throw new Error(`Expected end of file, got "${i}" at ` - + `${context.position.line}:${context.position.character}.`); - } - - return ast; -} - - -/** - * Options for the parseJson() function. - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - */ -export interface ParseJsonOptions { - /** - * If omitted, will only emit errors related to the content of the JSON. If specified, any - * JSON errors will also include the path of the file that caused the error. - */ - path?: string; -} - - -/** - * Parse a JSON string into its value. This discards the AST and only returns the value itself. - * - * If a path option is pass, it also absorbs JSON parsing errors and return a new error with the - * path in it. Useful for showing errors when parsing from a file. - * - * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead. - * @param input The string to parse. - * @param mode The mode to parse the input with. {@see JsonParseMode}. - * @param options Additional optinos for parsing. - * @returns {JsonValue} The value represented by the JSON string. - */ -export function parseJson( - input: string, - mode = JsonParseMode.Default, - options?: ParseJsonOptions, -): JsonValue { - try { - // Try parsing for the fastest path available, if error, uses our own parser for better errors. - if (mode == JsonParseMode.Strict) { - try { - return JSON.parse(input); - } catch (err) { - return parseJsonAst(input, mode).value; - } - } - - return parseJsonAst(input, mode).value; - } catch (e) { - if (options && options.path && e instanceof JsonException) { - throw new PathSpecificJsonException(options.path, e); - } - throw e; - } -} diff --git a/packages/angular_devkit/core/src/json/parser_spec.ts b/packages/angular_devkit/core/src/json/parser_spec.ts deleted file mode 100644 index 56405eb03aed..000000000000 --- a/packages/angular_devkit/core/src/json/parser_spec.ts +++ /dev/null @@ -1,347 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-big-function -import { JsonParseMode, parseJson, parseJsonAst } from './parser'; - - -// Node 6 compatibility. -function entries(x: {[key: string]: any}): any { // tslint:disable-line:no-any - return Object.keys(x).map(k => [k, x[k]]); -} - - -describe('parseJson and parseJsonAst', () => { - describe('generic', () => { - const numbers = { - }; - const errors = [ - '', - '-abcdefghijklmnopqrstuvwxyz', - ]; - - for (const [n, [start, end, text]] of entries(numbers)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(JSON.parse(n)); - expect(parseJson(n)).toEqual(JSON.parse(n)); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n)).toThrow(); - expect(() => parseJson(n)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('numbers', () => { - const numbers = { - '1234': [[0, 0, 0], [4, 0, 4]], - '12E34': [[0, 0, 0], [5, 0, 5]], - '12E+4': [[0, 0, 0], [5, 0, 5]], - '12E-4': [[0, 0, 0], [5, 0, 5]], - '12E-0004': [[0, 0, 0], [8, 0, 8]], - ' 1234 ': [[3, 0, 3], [7, 0, 7], '1234'], - '\r1234\t': [[1, 0, 1], [5, 0, 5], '1234'], - '\n1234\n': [[1, 1, 0], [5, 1, 4], '1234'], - '0.123': [[0, 0, 0], [5, 0, 5]], - '0': [[0, 0, 0], [1, 0, 1]], - '\n\n\n\n\n0': [[5, 5, 0], [6, 5, 1], '0'], - }; - const errors = [ - '000', - '01', - '1E1+1', - '--', - '0-0', - '-0-0', - '0.0.0', - '0\n.0\n.0', - '0.', - '+1', - 'Infinity', - 'NaN', - '-Infinity', - '+Infinity', - ]; - - for (const [n, [start, end, text]] of entries(numbers)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n); - expect(ast.kind).toBe('number'); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(JSON.parse(n)); - expect(parseJson(n)).toEqual(JSON.parse(n)); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n)).toThrow(); - expect(() => parseJson(n)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('strings', () => { - const strings = { - '""': [[0, 0, 0], [2, 0, 2]], - '"hello"': [[0, 0, 0], [7, 0, 7]], - '"a\\nb"': [[0, 0, 0], [6, 0, 6]], - '"a\\nb\\tc\\rd\\\\e\\/f\\\"g\\bh\\fi"': [[0, 0, 0], [27, 0, 27]], - '"a\\u1234b"': [[0, 0, 0], [10, 0, 10]], - }; - const errors = [ - '"\\z"', - '\'hello\'', - '"\\', - '"a\\zb"', - '"a', - '"a\nb"', - '"\\\n "', - ]; - - for (const [n, [start, end, text]] of entries(strings)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n); - expect(ast.kind).toBe('string'); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(JSON.parse(n)); - expect(parseJson(n)).toEqual(JSON.parse(n)); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n)).toThrow(); - expect(() => parseJson(n)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('constants', () => { - const strings = { - 'true': ['true', [0, 0, 0], [4, 0, 4], true], - 'false': ['false', [0, 0, 0], [5, 0, 5], false], - 'null': ['null', [0, 0, 0], [4, 0, 4], null], - }; - const errors = [ - 'undefined', - ]; - - for (const [n, [kind, start, end, value, text]] of entries(strings)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n); - expect(ast.kind).toBe(kind); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(value); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n)).toThrow(); - expect(() => parseJson(n)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('arrays', () => { - const strings = { - '[0,1,2,3]': [[0, 0, 0], [9, 0, 9]], - '[[0],1,2,3]': [[0, 0, 0], [11, 0, 11]], - '[0\n,\n1,2,3]': [[0, 0, 0], [11, 2, 6]], - '[]': [[0, 0, 0], [2, 0, 2]], - '\n[\n]\n': [[1, 1, 0], [4, 2, 1], '[\n]'], - '[\n]': [[0, 0, 0], [3, 1, 1]], - '[\n\n]': [[0, 0, 0], [4, 2, 1]], - }; - const errors = [ - '[', - '[,]', - '[0,]', - '[,0]', - ]; - - for (const [n, [start, end, text]] of entries(strings)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n); - expect(ast.kind).toBe('array'); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(JSON.parse(n)); - expect(parseJson(n)).toEqual(JSON.parse(n)); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n)).toThrow(); - expect(() => parseJson(n)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('objects', () => { - const strings = { - '{}': [[0, 0, 0], [2, 0, 2]], - '{\n}': [[0, 0, 0], [3, 1, 1]], - '{"hello": "world"}': [[0, 0, 0], [18, 0, 18]], - '{"hello": 0, "world": 1}': [[0, 0, 0], [24, 0, 24]], - '{"hello": {"hello": {"hello": "world"}}}': [[0, 0, 0], [40, 0, 40]], - }; - const errors = [ - '{', - '{,}', - '{"hello": 0', - ]; - - for (const [n, [start, end, text]] of entries(strings)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n); - expect(ast.kind).toBe('object'); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(JSON.parse(n)); - expect(parseJson(n)).toEqual(JSON.parse(n)); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n)).toThrow(); - expect(() => parseJson(n)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('loose', () => { - const strings = { - '{\'hello\': 0}': [[0, 0, 0], [12, 0, 12], {'hello': 0}], - '{hello: 0}': [[0, 0, 0], [10, 0, 10], {hello: 0}], - '{1: 0}': [[0, 0, 0], [6, 0, 6], {1: 0}], - '{hello\n:/**/ 0}': [[0, 0, 0], [15, 1, 8], {hello: 0}], - '{\n// hello\n}': [[0, 0, 0], [12, 2, 1], {}], - '{\n/* hello\n*/ }': [[0, 0, 0], [15, 2, 4], {}], - '{}// ': [[0, 0, 0], [2, 0, 2], {}, '{}'], - '{}//': [[0, 0, 0], [2, 0, 2], {}, '{}'], - '{hello:0,}': [[0, 0, 0], [10, 0, 10], {hello: 0}], - '{hello:0/**/,}': [[0, 0, 0], [14, 0, 14], {hello: 0}], - '{hello:0,/**/}': [[0, 0, 0], [14, 0, 14], {hello: 0}], - '{hi:["hello",]}': [[0, 0, 0], [15, 0, 15], {hi: ['hello']}], - '{hi:["hello",/* */]}': [[0, 0, 0], [20, 0, 20], {hi: ['hello']}], - '{hi:["hello"/* */,]}': [[0, 0, 0], [20, 0, 20], {hi: ['hello']}], - '{hi:["hello" , ] , }': [[0, 0, 0], [20, 0, 20], {hi: ['hello']}], - '{hi:"\\\n "}': [[0, 0, 0], [10, 1, 3], {hi: '\n '}], - '{d: -0xdecaf, e: Infinity, f: -Infinity, g: +Infinity, h: NaN,}': [[0, 0, 0], [63, 0, 63], { - d: -0xdecaf, - e: Infinity, - f: -Infinity, - g: Infinity, - h: NaN, - }], - }; - const errors = [ - '{1b: 0}', - ' /*', - '', - '.Infinity', - ]; - - for (const [n, [start, end, value, text]] of entries(strings)) { - it(`works for ${JSON.stringify(n)}`, () => { - const ast = parseJsonAst(n, JsonParseMode.Loose); - expect(ast.kind).toBe('object'); - expect(ast.start).toEqual({offset: start[0], line: start[1], character: start[2]}); - expect(ast.end).toEqual({offset: end[0], line: end[1], character: end[2]}); - expect(ast.value).toEqual(value); - expect(parseJson(n, JsonParseMode.Loose)).toEqual(value); - expect(ast.text).toBe(text === undefined ? n : text); - }); - } - - for (const n of errors) { - it(`errors for ${JSON.stringify(n)}`, () => { - expect(() => parseJsonAst(n, JsonParseMode.Loose)).toThrow(); - expect(() => parseJson(n, JsonParseMode.Loose)).toThrow(); - expect(() => JSON.parse(n)).toThrow(); - }); - } - }); - - describe('complex', () => { - it('strips comments', () => { - expect(parseJson(` - // THIS IS A COMMENT - { - /* THIS IS ALSO A COMMENT */ // IGNORED BECAUSE COMMENT - // AGAIN, COMMENT /* THIS SHOULD NOT BE WEIRD - "a": "this // should not be a comment", - "a2": "this /* should also not be a comment", - /* MULTIPLE - LINE - COMMENT - \o/ */ - "b" /* COMMENT */: /* YOU GUESSED IT */ 1 // COMMENT - , /* STILL VALID */ - "c": 2 - } - `, JsonParseMode.Loose)).toEqual({ - a: 'this // should not be a comment', - a2: 'this /* should also not be a comment', - b: 1, - c: 2, - }); - }); - - it('works with json5.org example', () => { - const input = `{ - // comments - unquoted: 'and you can quote me on that', - 'singleQuotes': 'I can use "double quotes" here', - lineBreaks: "Look, Mom! \\ -No \\\\n's!", - hexadecimal: 0xdecaf, - leadingDecimalPoint: .8675309, andTrailing: 8675309., - positiveSign: +1, - trailingComma: 'in objects', andIn: ['arrays',], - "backwardsCompatible": "with JSON", - }`; - - expect(parseJson(input, JsonParseMode.Json5)).toEqual({ - unquoted: 'and you can quote me on that', - singleQuotes: 'I can use "double quotes" here', - lineBreaks: "Look, Mom! \nNo \\n's!", - hexadecimal: 0xdecaf, - leadingDecimalPoint: .8675309, andTrailing: 8675309., - positiveSign: +1, - trailingComma: 'in objects', - andIn: ['arrays'], - backwardsCompatible: 'with JSON', - }); - }); - }); -}); diff --git a/packages/angular_devkit/core/src/json/schema/index.ts b/packages/angular_devkit/core/src/json/schema/index.ts index 92a977ffe736..6e3f3c14e4f4 100644 --- a/packages/angular_devkit/core/src/json/schema/index.ts +++ b/packages/angular_devkit/core/src/json/schema/index.ts @@ -1,10 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import * as transforms from './transforms'; + export * from './interface'; export * from './pointer'; export * from './registry'; @@ -12,6 +15,4 @@ export * from './schema'; export * from './visitor'; export * from './utility'; -import * as transforms from './transforms'; - export { transforms }; diff --git a/packages/angular_devkit/core/src/json/schema/interface.ts b/packages/angular_devkit/core/src/json/schema/interface.ts index 42c581e8d026..8b8dac6debdf 100644 --- a/packages/angular_devkit/core/src/json/schema/interface.ts +++ b/packages/angular_devkit/core/src/json/schema/interface.ts @@ -1,13 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { ErrorObject, Format } from 'ajv'; import { Observable, SubscribableOrPromise } from 'rxjs'; -import { JsonArray, JsonObject, JsonValue } from '../interface'; +import { JsonArray, JsonObject, JsonValue } from '../utils'; export type JsonPointer = string & { __PRIVATE_DEVKIT_JSON_POINTER: void; @@ -18,7 +19,7 @@ export interface SchemaValidatorResult { errors?: SchemaValidatorError[]; } -export type SchemaValidatorError = ErrorObject; +export type SchemaValidatorError = Partial; export interface SchemaValidatorOptions { applyPreTransforms?: boolean; @@ -59,15 +60,16 @@ export interface PromptDefinition { default?: string | string[] | number | boolean | null; validator?: (value: JsonValue) => boolean | string | Promise; - items?: Array; + items?: Array; raw?: string | JsonObject; multiselect?: boolean; propertyTypes: Set; } -export type PromptProvider = (definitions: Array) - => SubscribableOrPromise<{ [id: string]: JsonValue }>; +export type PromptProvider = ( + definitions: Array, +) => SubscribableOrPromise<{ [id: string]: JsonValue }>; export interface SchemaRegistry { compile(schema: Object): Observable; @@ -109,10 +111,7 @@ export interface JsonSchemaVisitor { } export interface JsonVisitor { - ( - value: JsonValue, - pointer: JsonPointer, - schema?: JsonObject, - root?: JsonObject | JsonArray, - ): Observable | JsonValue; + (value: JsonValue, pointer: JsonPointer, schema?: JsonObject, root?: JsonObject | JsonArray): + | Observable + | JsonValue; } diff --git a/packages/angular_devkit/core/src/json/schema/pointer.ts b/packages/angular_devkit/core/src/json/schema/pointer.ts index 39d054169801..9c1223c8c1f2 100644 --- a/packages/angular_devkit/core/src/json/schema/pointer.ts +++ b/packages/angular_devkit/core/src/json/schema/pointer.ts @@ -1,20 +1,20 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { JsonPointer } from './interface'; +import { JsonPointer } from './interface'; export function buildJsonPointer(fragments: string[]): JsonPointer { - return ( - '/' + fragments.map(f => { - return f.replace(/~/g, '~0') - .replace(/\//g, '~1'); - }).join('/') - ) as JsonPointer; + return ('/' + + fragments + .map((f) => { + return f.replace(/~/g, '~0').replace(/\//g, '~1'); + }) + .join('/')) as JsonPointer; } export function joinJsonPointer(root: JsonPointer, ...others: string[]): JsonPointer { if (root == '/') { @@ -24,8 +24,15 @@ export function joinJsonPointer(root: JsonPointer, ...others: string[]): JsonPoi return (root + buildJsonPointer(others)) as JsonPointer; } export function parseJsonPointer(pointer: JsonPointer): string[] { - if (pointer === '') { return []; } - if (pointer.charAt(0) !== '/') { throw new Error('Relative pointer: ' + pointer); } + if (pointer === '') { + return []; + } + if (pointer.charAt(0) !== '/') { + throw new Error('Relative pointer: ' + pointer); + } - return pointer.substring(1).split(/\//).map(str => str.replace(/~1/g, '/').replace(/~0/g, '~')); + return pointer + .substring(1) + .split(/\//) + .map((str) => str.replace(/~1/g, '/').replace(/~0/g, '~')); } diff --git a/packages/angular_devkit/core/src/json/schema/prompt_spec.ts b/packages/angular_devkit/core/src/json/schema/prompt_spec.ts index b22b26b7a567..2746798f4299 100644 --- a/packages/angular_devkit/core/src/json/schema/prompt_spec.ts +++ b/packages/angular_devkit/core/src/json/schema/prompt_spec.ts @@ -1,25 +1,25 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any no-big-function + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { map, mergeMap } from 'rxjs/operators'; import { CoreSchemaRegistry } from './registry'; - describe('Prompt Provider', () => { - it('sets properties with answer', done => { + it('sets properties with answer', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { return { [definitions[0].id]: true }; }); - registry + await registry .compile({ properties: { test: { @@ -29,19 +29,19 @@ describe('Prompt Provider', () => { }, }) .pipe( - mergeMap(validator => validator(data)), + mergeMap((validator) => validator(data)), map(() => { expect(data.test).toBe(true); }), ) - .toPromise().then(done, done.fail); - }); + .toPromise(); + }); - it('supports mixed schema references', done => { + it('supports mixed schema references', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { return { '/bool': true, '/test': 'two', @@ -49,7 +49,7 @@ describe('Prompt Provider', () => { }; }); - registry + await registry .compile({ properties: { bool: { @@ -57,11 +57,7 @@ describe('Prompt Provider', () => { }, test: { type: 'string', - enum: [ - 'one', - 'two', - 'three', - ], + enum: ['one', 'two', 'three'], 'x-prompt': { type: 'list', 'message': 'other-message', @@ -91,30 +87,30 @@ describe('Prompt Provider', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(true); expect(data.bool).toBe(true); expect(data.test).toBe('two'); expect(data.obj.deep.three).toEqual('test3-answer'); }), ) - .toPromise().then(done, done.fail); + .toPromise(); }); describe('with shorthand', () => { - it('supports message value', done => { + it('supports message value', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].message).toBe('test-message'); return {}; }); - registry + await registry .compile({ properties: { test: { @@ -123,53 +119,41 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes enums', done => { + it('analyzes enums', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); - expect(definitions[0].items).toEqual([ - 'one', - 'two', - 'three', - ]); + expect(definitions[0].items).toEqual(['one', 'two', 'three']); return {}; }); - registry + await registry .compile({ properties: { test: { type: 'string', - enum: [ - 'one', - 'two', - 'three', - ], + enum: ['one', 'two', 'three'], 'x-prompt': 'test-message', }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes boolean properties', done => { + it('analyzes boolean properties', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('confirmation'); expect(definitions[0].items).toBeUndefined(); @@ -177,7 +161,7 @@ describe('Prompt Provider', () => { return {}; }); - registry + await registry .compile({ properties: { test: { @@ -186,27 +170,24 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - }); describe('with longhand', () => { - it('supports message option', done => { + it('supports message option', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].message).toBe('test-message'); return {}; }); - registry + await registry .compile({ properties: { test: { @@ -217,38 +198,28 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes enums WITH explicit list type', done => { + it('analyzes enums WITH explicit list type', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); - expect(definitions[0].items).toEqual([ - 'one', - 'two', - 'three', - ]); + expect(definitions[0].items).toEqual(['one', 'two', 'three']); return { [definitions[0].id]: 'one' }; }); - registry + await registry .compile({ properties: { test: { type: 'string', - enum: [ - 'one', - 'two', - 'three', - ], + enum: ['one', 'two', 'three'], 'x-prompt': { 'type': 'list', 'message': 'test-message', @@ -256,17 +227,15 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes list with true multiselect option and object items', done => { + it('analyzes list with true multiselect option and object items', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); expect(definitions[0].multiselect).toBe(true); @@ -278,7 +247,7 @@ describe('Prompt Provider', () => { return { [definitions[0].id]: { 'value': 'one', 'label': 'one' } }; }); - registry + await registry .compile({ properties: { test: { @@ -295,17 +264,15 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes list with false multiselect option and object items', done => { + it('analyzes list with false multiselect option and object items', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); expect(definitions[0].multiselect).toBe(false); @@ -317,7 +284,7 @@ describe('Prompt Provider', () => { return { [definitions[0].id]: { 'value': 'one', 'label': 'one' } }; }); - registry + await registry .compile({ properties: { test: { @@ -334,17 +301,15 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes list without multiselect option and object items', done => { + it('analyzes list without multiselect option and object items', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); expect(definitions[0].multiselect).toBe(true); @@ -356,7 +321,7 @@ describe('Prompt Provider', () => { return { [definitions[0].id]: { 'value': 'two', 'label': 'two' } }; }); - registry + await registry .compile({ properties: { test: { @@ -372,95 +337,73 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes enums WITHOUT explicit list type', done => { + it('analyzes enums WITHOUT explicit list type', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); expect(definitions[0].multiselect).toBeFalsy(); - expect(definitions[0].items).toEqual([ - 'one', - 'two', - 'three', - ]); + expect(definitions[0].items).toEqual(['one', 'two', 'three']); return {}; }); - registry + await registry .compile({ properties: { test: { type: 'string', - enum: [ - 'one', - 'two', - 'three', - ], + enum: ['one', 'two', 'three'], 'x-prompt': { 'message': 'test-message', }, }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes enums WITHOUT explicit list type and multiselect', done => { + it('analyzes enums WITHOUT explicit list type and multiselect', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('list'); expect(definitions[0].multiselect).toBe(true); - expect(definitions[0].items).toEqual([ - 'one', - 'two', - 'three', - ]); + expect(definitions[0].items).toEqual(['one', 'two', 'three']); return {}; }); - registry + await registry .compile({ properties: { test: { type: 'array', items: { - enum: [ - 'one', - 'two', - 'three', - ], + enum: ['one', 'two', 'three'], }, 'x-prompt': 'test-message', }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('analyzes boolean properties', done => { + it('analyzes boolean properties', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('confirmation'); expect(definitions[0].items).toBeUndefined(); @@ -468,7 +411,7 @@ describe('Prompt Provider', () => { return {}; }); - registry + await registry .compile({ properties: { test: { @@ -479,17 +422,15 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - it('allows prompt type override', done => { + it('allows prompt type override', async () => { const registry = new CoreSchemaRegistry(); const data: any = {}; - registry.usePromptProvider(async definitions => { + registry.usePromptProvider(async (definitions) => { expect(definitions.length).toBe(1); expect(definitions[0].type).toBe('input'); expect(definitions[0].items).toBeUndefined(); @@ -497,7 +438,7 @@ describe('Prompt Provider', () => { return {}; }); - registry + await registry .compile({ properties: { test: { @@ -509,12 +450,8 @@ describe('Prompt Provider', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) - .toPromise().then(done, done.fail); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); }); - }); - }); diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts index 1b4bd474373a..d22f0be623c9 100644 --- a/packages/angular_devkit/core/src/json/schema/registry.ts +++ b/packages/angular_devkit/core/src/json/schema/registry.ts @@ -1,20 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import Ajv, { ValidateFunction } from 'ajv'; + +import Ajv, { SchemaObjCxt, ValidateFunction } from 'ajv'; import ajvAddFormats from 'ajv-formats'; import * as http from 'http'; import * as https from 'https'; import { Observable, from, isObservable } from 'rxjs'; import { map } from 'rxjs/operators'; import * as Url from 'url'; -import { BaseException } from '../../exception/exception'; +import { BaseException } from '../../exception'; import { PartiallyOrderedSet, deepCopy } from '../../utils'; -import { JsonArray, JsonObject, JsonValue, isJsonObject } from '../interface'; +import { JsonArray, JsonObject, JsonValue, isJsonObject } from '../utils'; import { JsonPointer, JsonVisitor, @@ -32,8 +33,9 @@ import { JsonSchema } from './schema'; import { getTypesOfSchema } from './utility'; import { visitJson, visitJsonSchema } from './visitor'; -export type UriHandler = (uri: string) => - Observable | Promise | null | undefined; +export type UriHandler = ( + uri: string, +) => Observable | Promise | null | undefined; export class SchemaValidationException extends BaseException { public readonly errors: SchemaValidatorError[]; @@ -61,8 +63,18 @@ export class SchemaValidationException extends BaseException { const messages = errors.map((err) => { let message = `Data path ${JSON.stringify(err.instancePath)} ${err.message}`; - if (err.keyword === 'additionalProperties') { - message += `(${err.params.additionalProperty})`; + if (err.params) { + switch (err.keyword) { + case 'additionalProperties': + message += `(${err.params.additionalProperty})`; + break; + + case 'enum': + message += `. Allowed values are: ${(err.params.allowedValues as string[] | undefined) + ?.map((v) => `"${v}"`) + .join(', ')}`; + break; + } } return message + '.'; @@ -132,7 +144,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { return new Promise((resolve, reject) => { const url = new Url.URL(uri); const client = url.protocol === 'https:' ? https : http; - client.get(url, res => { + client.get(url, (res) => { if (!res.statusCode || res.statusCode >= 300) { // Consume the rest of the data to free memory. res.resume(); @@ -140,7 +152,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { } else { res.setEncoding('utf8'); let data = ''; - res.on('data', chunk => { + res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { @@ -180,7 +192,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { protected _resolver( ref: string, validate?: ValidateFunction, - ): { context?: ValidateFunction, schema?: JsonObject } { + ): { context?: ValidateFunction; schema?: JsonObject } { if (!validate || !ref) { return {}; } @@ -197,9 +209,6 @@ export class CoreSchemaRegistry implements SchemaRegistry { } } - if (fullReference.startsWith('#')) { - fullReference = fullReference.slice(0, -1); - } const resolvedSchema = this._ajv.getSchema(fullReference); return { @@ -223,12 +232,12 @@ export class CoreSchemaRegistry implements SchemaRegistry { } private async _flatten(schema: JsonObject): Promise { - this._replaceDeprecatedSchemaIdKeyword(schema); this._ajv.removeSchema(schema); this._currentCompilationSchemaInfo = undefined; const validate = await this._ajv.compileAsync(schema); + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; function visitor( @@ -237,14 +246,15 @@ export class CoreSchemaRegistry implements SchemaRegistry { parentSchema?: JsonObject | JsonArray, index?: string, ) { - if (current - && parentSchema - && index - && isJsonObject(current) - && current.hasOwnProperty('$ref') - && typeof current['$ref'] == 'string' + if ( + current && + parentSchema && + index && + isJsonObject(current) && + Object.prototype.hasOwnProperty.call(current, '$ref') && + typeof current['$ref'] == 'string' ) { - const resolved = self._resolver(current['$ref'] as string, validate); + const resolved = self._resolver(current['$ref'], validate); if (resolved.schema) { (parentSchema as JsonObject)[index] = resolved.schema; @@ -267,28 +277,36 @@ export class CoreSchemaRegistry implements SchemaRegistry { */ compile(schema: JsonSchema): Observable { return from(this._compile(schema)).pipe( - map(validate => (value, options) => from(validate(value, options))), + map((validate) => (value, options) => from(validate(value, options))), ); } - private async _compile(schema: JsonSchema): - Promise<(data: JsonValue, options?: SchemaValidatorOptions) => Promise> { + private async _compile( + schema: JsonSchema, + ): Promise< + (data: JsonValue, options?: SchemaValidatorOptions) => Promise + > { if (typeof schema === 'boolean') { - return async data => ({ success: schema, data }); + return async (data) => ({ success: schema, data }); } - this._replaceDeprecatedSchemaIdKeyword(schema); - const schemaInfo: SchemaInfo = { smartDefaultRecord: new Map(), promptDefinitions: [], }; this._ajv.removeSchema(schema); - let validator: ValidateFunction; + try { this._currentCompilationSchemaInfo = schemaInfo; + validator = this._ajv.compile(schema); + } catch (e) { + // This should eventually be refactored so that we we handle race condition where the same schema is validated at the same time. + if (!(e instanceof Ajv.MissingRefError)) { + throw e; + } + validator = await this._ajv.compileAsync(schema); } finally { this._currentCompilationSchemaInfo = undefined; @@ -308,7 +326,13 @@ export class CoreSchemaRegistry implements SchemaRegistry { // Apply pre-validation transforms if (validationOptions.applyPreTransforms) { for (const visitor of this._pre.values()) { - data = await visitJson(data, visitor, schema, this._resolver.bind(this), validator).toPromise(); + data = await visitJson( + data, + visitor, + schema, + this._resolver.bind(this), + validator, + ).toPromise(); } } @@ -328,8 +352,9 @@ export class CoreSchemaRegistry implements SchemaRegistry { await visitJson(data, visitor, schema, this._resolver.bind(this), validator).toPromise(); } - const definitions = schemaInfo.promptDefinitions - .filter(def => !validationContext.promptFieldsWithValue.has(def.id)); + const definitions = schemaInfo.promptDefinitions.filter( + (def) => !validationContext.promptFieldsWithValue.has(def.id), + ); if (definitions.length > 0) { await this._applyPrompts(data, definitions); @@ -337,15 +362,30 @@ export class CoreSchemaRegistry implements SchemaRegistry { } // Validate using ajv - const success = await validator.call(validationContext, data); - if (!success) { - return { data, success, errors: validator.errors ?? [] }; + try { + const success = await validator.call(validationContext, data); + + if (!success) { + return { data, success, errors: validator.errors ?? [] }; + } + } catch (error) { + if (error instanceof Ajv.ValidationError) { + return { data, success: false, errors: error.errors }; + } + + throw error; } // Apply post-validation transforms if (validationOptions.applyPostTransforms) { for (const visitor of this._post.values()) { - data = await visitJson(data, visitor, schema, this._resolver.bind(this), validator).toPromise(); + data = await visitJson( + data, + visitor, + schema, + this._resolver.bind(this), + validator, + ).toPromise(); } } @@ -362,7 +402,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { throw new Error(source); } - this._sourceMap.set(source, provider); + this._sourceMap.set(source, provider as unknown as SmartDefaultProvider<{}>); if (!this._smartDefaultKeyword) { this._smartDefaultKeyword = true; @@ -378,14 +418,8 @@ export class CoreSchemaRegistry implements SchemaRegistry { } // We cheat, heavily. - const pathArray = it.dataPathArr - .slice(1, it.dataLevel + 1) - .map(p => typeof p === 'number' ? p : p.str.slice(1, -1)); - - compilationSchemInfo.smartDefaultRecord.set( - JSON.stringify(pathArray), - schema, - ); + const pathArray = this.normalizeDataPathArr(it); + compilationSchemInfo.smartDefaultRecord.set(JSON.stringify(pathArray), schema); return () => true; }, @@ -424,12 +458,10 @@ export class CoreSchemaRegistry implements SchemaRegistry { return () => true; } - const path = '/' + it.dataPathArr - .slice(1, it.dataLevel + 1) - .map(p => typeof p === 'number' ? p : p.str.slice(1, -1)).join('/'); + const path = '/' + this.normalizeDataPathArr(it).join('/'); let type: string | undefined; - let items: Array | undefined; + let items: Array | undefined; let message: string; if (typeof schema == 'string') { message = schema; @@ -465,7 +497,8 @@ export class CoreSchemaRegistry implements SchemaRegistry { : schema.multiselect; const enumValues = multiselect - ? (parentSchema as JsonObject).items && ((parentSchema as JsonObject).items as JsonObject).enum + ? (parentSchema as JsonObject).items && + ((parentSchema as JsonObject).items as JsonObject).enum : (parentSchema as JsonObject).enum; if (!items && Array.isArray(enumValues)) { items = []; @@ -491,24 +524,25 @@ export class CoreSchemaRegistry implements SchemaRegistry { propertyTypes, default: typeof (parentSchema as JsonObject).default == 'object' && - (parentSchema as JsonObject).default !== null && - !Array.isArray((parentSchema as JsonObject).default) + (parentSchema as JsonObject).default !== null && + !Array.isArray((parentSchema as JsonObject).default) ? undefined - : (parentSchema as JsonObject).default as string[], - async validator(data: JsonValue) { + : ((parentSchema as JsonObject).default as string[]), + async validator(data: JsonValue): Promise { try { const result = await it.self.validate(parentSchema, data); // If the schema is sync then false will be returned on validation failure if (result) { - return result; + return result as boolean | string; } else if (it.self.errors?.length) { // Validation errors will be present on the Ajv instance when sync - return it.self.errors[0].message; + return it.self.errors[0].message as string; } } catch (e) { + const validationError = e as { errors?: Error[] }; // If the schema is async then an error will be thrown on validation failure - if (Array.isArray(e.errors) && e.errors.length) { - return e.errors[0].message; + if (Array.isArray(validationError.errors) && validationError.errors.length) { + return validationError.errors[0].message; } } @@ -555,63 +589,47 @@ export class CoreSchemaRegistry implements SchemaRegistry { for (const path in answers) { const pathFragments = path.split('/').slice(1); - CoreSchemaRegistry._set( - data, - pathFragments, - answers[path] as {}, - null, - undefined, - true, - ); + CoreSchemaRegistry._set(data, pathFragments, answers[path], null, undefined, true); } } private static _set( - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any, fragments: string[], - value: {}, - // tslint:disable-next-line:no-any - parent: any | null = null, + value: unknown, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + parent: any = null, parentProperty?: string, force?: boolean, ): void { - for (let i = 0; i < fragments.length; i++) { - const f = fragments[i]; - - if (f[0] == 'i') { + for (let index = 0; index < fragments.length; index++) { + const fragment = fragments[index]; + if (/^i\d+$/.test(fragment)) { if (!Array.isArray(data)) { return; } - for (let j = 0; j < data.length; j++) { - CoreSchemaRegistry._set(data[j], fragments.slice(i + 1), value, data, '' + j); - } - - return; - } - - if (f.startsWith('key')) { - if (typeof data !== 'object') { - return; - } - - for (const property in data) { - CoreSchemaRegistry._set(data[property], fragments.slice(i + 1), value, data, property); + for (let dataIndex = 0; dataIndex < data.length; dataIndex++) { + CoreSchemaRegistry._set( + data[dataIndex], + fragments.slice(index + 1), + value, + data, + `${dataIndex}`, + ); } return; } - - // We know we need an object because the fragment is a property key. if (!data && parent !== null && parentProperty) { data = parent[parentProperty] = {}; } - parent = data; - parentProperty = f; - data = data[f]; + parent = data; + parentProperty = fragment; + data = data[fragment]; } if (parent && parentProperty && (force || parent[parentProperty] === undefined)) { @@ -625,13 +643,13 @@ export class CoreSchemaRegistry implements SchemaRegistry { ): Promise { for (const [pointer, schema] of smartDefaults.entries()) { const fragments = JSON.parse(pointer); - const source = this._sourceMap.get((schema as JsonObject).$source as string); + const source = this._sourceMap.get(schema.$source as string); if (!source) { continue; } let value = source(schema); - if (isObservable(value)) { + if (isObservable<{}>(value)) { value = await value.toPromise(); } @@ -644,7 +662,11 @@ export class CoreSchemaRegistry implements SchemaRegistry { keyword: 'x-deprecated', validate: (schema, _data, _parentSchema, dataCxt) => { if (schema) { - onUsage(`Option "${dataCxt?.parentDataProperty}" is deprecated${typeof schema == 'string' ? ': ' + schema : '.'}`); + onUsage( + `Option "${dataCxt?.parentDataProperty}" is deprecated${ + typeof schema == 'string' ? ': ' + schema : '.' + }`, + ); } return true; @@ -653,17 +675,9 @@ export class CoreSchemaRegistry implements SchemaRegistry { }); } - /** - * Workaround to avoid a breaking change in downstream schematics. - * @deprecated will be removed in version 13. - */ - private _replaceDeprecatedSchemaIdKeyword(schema: JsonObject): void { - if (typeof schema.id === 'string') { - schema.$id = schema.id; - delete schema.id; - - // tslint:disable-next-line:no-console - console.warn(`"${schema.$id}" schema is using the keyword "id" which its support is deprecated. Use "$id" for schema ID.`); - } + private normalizeDataPathArr(it: SchemaObjCxt): (number | string)[] { + return it.dataPathArr + .slice(1, it.dataLevel + 1) + .map((p) => (typeof p === 'number' ? p : p.str.replace(/"/g, ''))); } } diff --git a/packages/angular_devkit/core/src/json/schema/registry_spec.ts b/packages/angular_devkit/core/src/json/schema/registry_spec.ts index 5790bdce095a..aa9825585e66 100644 --- a/packages/angular_devkit/core/src/json/schema/registry_spec.ts +++ b/packages/angular_devkit/core/src/json/schema/registry_spec.ts @@ -1,23 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any non-null-operator no-big-function -import { of as observableOf } from 'rxjs'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { map, mergeMap } from 'rxjs/operators'; import { SchemaFormat } from './interface'; -import { CoreSchemaRegistry } from './registry'; +import { CoreSchemaRegistry, SchemaValidationException } from './registry'; import { addUndefinedDefaults } from './transforms'; - describe('CoreSchemaRegistry', () => { - it('works asynchronously', done => { + it('works asynchronously', (done) => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults); - const data: any = {}; // tslint:disable-line:no-any + const data: any = {}; registry .compile({ @@ -31,25 +30,26 @@ describe('CoreSchemaRegistry', () => { }, }, tslint: { - $ref: 'https://json.schemastore.org/tslint#', + $ref: 'https://json.schemastore.org/npm-link-up#', }, }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(true); expect(data.obj.num).toBeUndefined(); expect(data.tslint).not.toBeUndefined(); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); - it('supports pre transforms', done => { + it('supports pre transforms', (done) => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults); - const data: any = {}; // tslint:disable-line:no-any + const data = {}; registry.addPreTransform((data, ptr) => { if (ptr == '/') { @@ -73,18 +73,19 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { const data = result.data as any; expect(result.success).toBe(true); expect(data.str).toBe('string'); expect(data.obj.num).toBeUndefined(); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); - it('supports local references', done => { + it('supports local references', (done) => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults); const data = { numbers: { one: 1 } }; @@ -102,16 +103,17 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(true); expect(data.numbers.one).not.toBeUndefined(); }), - ) - .toPromise().then(done, done.fail); + ) + .toPromise() + .then(done, done.fail); }); - it('fails on invalid additionalProperties', done => { + it('fails on invalid additionalProperties', (done) => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults); const data = { notNum: 'foo' }; @@ -122,18 +124,46 @@ describe('CoreSchemaRegistry', () => { num: { type: 'number' }, }, additionalProperties: false, - }).pipe( - mergeMap(validator => validator(data)), - map(result => { + }) + .pipe( + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(false); expect(result.errors && result.errors[0].message).toContain( - 'should NOT have additional properties'); + 'must NOT have additional properties', + ); }), - ) - .toPromise().then(done, done.fail); + ) + .toPromise() + .then(done, done.fail); }); - it('fails on invalid additionalProperties async', done => { + it('fails on invalid enum value', (done) => { + const registry = new CoreSchemaRegistry(); + registry.addPostTransform(addUndefinedDefaults); + const data = { packageManager: 'foo' }; + + registry + .compile({ + properties: { + packageManager: { type: 'string', enum: ['npm', 'yarn', 'pnpm', 'cnpm'] }, + }, + additionalProperties: false, + }) + .pipe( + mergeMap((validator) => validator(data)), + map((result) => { + expect(result.success).toBe(false); + expect(new SchemaValidationException(result.errors).message).toContain( + `Data path "/packageManager" must be equal to one of the allowed values. Allowed values are: "npm", "yarn", "pnpm", "cnpm".`, + ); + }), + ) + .toPromise() + .then(done, done.fail); + }); + + it('fails on invalid additionalProperties async', (done) => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults); const data = { notNum: 'foo' }; @@ -145,19 +175,20 @@ describe('CoreSchemaRegistry', () => { num: { type: 'number' }, }, additionalProperties: false, - }).pipe( - mergeMap(validator => validator(data)), - map(result => { + }) + .pipe( + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(false); - expect(result.errors && result.errors[0].message).toContain( - 'should NOT have additional properties'); - expect(result.errors && result.errors[0].keyword).toBe('additionalProperties'); + expect(result.errors?.[0].message).toContain('must NOT have additional properties'); + expect(result.errors?.[0].keyword).toBe('additionalProperties'); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); - it('supports sync format', done => { + it('supports sync format', (done) => { const registry = new CoreSchemaRegistry(); const data = { str: 'hotdog' }; const format = { @@ -176,15 +207,16 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(true); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); - it('supports async format', done => { + it('supports async format', (done) => { const registry = new CoreSchemaRegistry(); const data = { str: 'hotdog' }; @@ -206,15 +238,16 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(true); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); - it('shows dataPath and message on error', done => { + it('shows dataPath and message on error', async () => { const registry = new CoreSchemaRegistry(); const data = { hotdot: 'hotdog', banana: 'banana' }; const format: SchemaFormat = { @@ -227,7 +260,7 @@ describe('CoreSchemaRegistry', () => { registry.addFormat(format); - registry + await registry .compile({ properties: { hotdot: { type: 'string', format: 'is-hotdog' }, @@ -235,19 +268,19 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(false); expect(result.errors && result.errors[0]).toBeTruthy(); expect(result.errors && result.errors[0].keyword).toBe('format'); - expect(result.errors && result.errors[0].instancePath).toBe('.banana'); + expect(result.errors && result.errors[0].instancePath).toBe('/banana'); expect(result.errors && (result.errors[0].params as any).format).toBe('is-hotdog'); }), ) - .toPromise().then(done, done.fail); + .toPromise(); }); - it('supports smart defaults', done => { + it('supports smart defaults', (done) => { const registry = new CoreSchemaRegistry(); const data: any = { arr: [{}], @@ -269,7 +302,7 @@ describe('CoreSchemaRegistry', () => { return schema['blue']; }); registry.addSmartDefaultProvider('test3', () => { - return [ 1, 2, 3 ]; + return [1, 2, 3]; }); registry @@ -325,8 +358,8 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(result.success).toBe(true); expect(data.bool).toBe(true); expect(data.arr[0].test).toBe('yep'); @@ -334,13 +367,14 @@ describe('CoreSchemaRegistry', () => { expect(data.obj.deep.arr).toEqual([1, 2, 3]); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); it('works with true as a schema and post-transforms', async () => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults); - const data: any = { a: 1, b: 2 }; // tslint:disable-line:no-any + const data = { a: 1, b: 2 }; const validate = await registry.compile(true).toPromise(); const result = await validate(data).toPromise(); @@ -349,10 +383,10 @@ describe('CoreSchemaRegistry', () => { expect(result.data).toBe(data); }); - it('adds deprecated options usage', done => { + it('adds deprecated options usage', (done) => { const registry = new CoreSchemaRegistry(); const deprecatedMessages: string[] = []; - registry.useXDeprecatedProvider(m => deprecatedMessages.push(m)); + registry.useXDeprecatedProvider((m) => deprecatedMessages.push(m)); const data = { foo: true, @@ -370,14 +404,15 @@ describe('CoreSchemaRegistry', () => { }, }) .pipe( - mergeMap(validator => validator(data)), - map(result => { + mergeMap((validator) => validator(data)), + map((result) => { expect(deprecatedMessages.length).toBe(2); expect(deprecatedMessages[0]).toBe('Option "foo" is deprecated: Use bar instead.'); expect(deprecatedMessages[1]).toBe('Option "bar" is deprecated.'); expect(result.success).toBe(true, result.errors); }), ) - .toPromise().then(done, done.fail); + .toPromise() + .then(done, done.fail); }); }); diff --git a/packages/angular_devkit/core/src/json/schema/schema.ts b/packages/angular_devkit/core/src/json/schema/schema.ts index fab3ff69e978..e4e747116def 100644 --- a/packages/angular_devkit/core/src/json/schema/schema.ts +++ b/packages/angular_devkit/core/src/json/schema/schema.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { JsonObject, JsonValue, isJsonObject } from '../interface'; + +import { JsonObject, JsonValue, isJsonObject } from '../utils'; /** * A specialized interface for JsonSchema (to come). JsonSchemas are also JsonObject. @@ -14,7 +15,6 @@ import { JsonObject, JsonValue, isJsonObject } from '../interface'; */ export type JsonSchema = JsonObject | boolean; - export function isJsonSchema(value: unknown): value is JsonSchema { return isJsonObject(value as JsonValue) || value === false || value === true; } diff --git a/packages/angular_devkit/core/src/json/schema/transforms.ts b/packages/angular_devkit/core/src/json/schema/transforms.ts index 0148b3efb51c..9bad19d0b37e 100644 --- a/packages/angular_devkit/core/src/json/schema/transforms.ts +++ b/packages/angular_devkit/core/src/json/schema/transforms.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { JsonObject, JsonValue, isJsonArray, isJsonObject } from '../interface'; + +import { JsonObject, JsonValue, isJsonArray, isJsonObject } from '../utils'; import { JsonPointer } from './interface'; import { JsonSchema } from './schema'; import { getTypesOfSchema } from './utility'; @@ -19,6 +20,8 @@ export function addUndefinedDefaults( return value; } + value ??= schema.default; + const types = getTypesOfSchema(schema); if (types.size === 0) { return value; @@ -73,20 +76,22 @@ export function addUndefinedDefaults( const propertySchemas = schemaObject.oneOf || schemaObject.anyOf; const allProperties = Object.keys(value); // Locate a schema which declares all the properties that the object contains. - const adjustedSchema = isJsonArray(propertySchemas) && propertySchemas.find(s => { - if (!isJsonObject(s)) { - return false; - } + const adjustedSchema = + isJsonArray(propertySchemas) && + propertySchemas.find((s) => { + if (!isJsonObject(s)) { + return false; + } - const schemaType = getTypesOfSchema(s); - if (schemaType.size === 1 && schemaType.has('object') && isJsonObject(s.properties)) { - const properties = Object.keys(s.properties); + const schemaType = getTypesOfSchema(s); + if (schemaType.size === 1 && schemaType.has('object') && isJsonObject(s.properties)) { + const properties = Object.keys(s.properties); - return allProperties.every(key => properties.includes(key)); - } + return allProperties.every((key) => properties.includes(key)); + } - return false; - }); + return false; + }); if (adjustedSchema && isJsonObject(adjustedSchema)) { newValue[propName] = addUndefinedDefaults(value, _pointer, adjustedSchema); diff --git a/packages/angular_devkit/core/src/json/schema/transforms_spec.ts b/packages/angular_devkit/core/src/json/schema/transforms_spec.ts index 2e2e5a564a03..a4fc18a88558 100644 --- a/packages/angular_devkit/core/src/json/schema/transforms_spec.ts +++ b/packages/angular_devkit/core/src/json/schema/transforms_spec.ts @@ -1,20 +1,20 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { mergeMap } from 'rxjs/operators'; import { CoreSchemaRegistry } from './registry'; import { addUndefinedDefaults } from './transforms'; -// tslint:disable-next-line: no-big-function describe('addUndefinedDefaults', () => { it('should add defaults to undefined properties (1)', async () => { const registry = new CoreSchemaRegistry(); registry.addPreTransform(addUndefinedDefaults); - const data: any = {}; // tslint:disable-line:no-any + const data: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any const result = await registry .compile({ @@ -28,20 +28,13 @@ describe('addUndefinedDefaults', () => { }, }, objAllOk: { - allOf: [ - { type: 'object' }, - ], + allOf: [{ type: 'object' }], }, objAllBad: { - allOf: [ - { type: 'object' }, - { type: 'number' }, - ], + allOf: [{ type: 'object' }, { type: 'number' }], }, objOne: { - oneOf: [ - { type: 'object' }, - ], + oneOf: [{ type: 'object' }], }, objNotOk: { not: { not: { type: 'object' } }, @@ -52,12 +45,9 @@ describe('addUndefinedDefaults', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ) + .pipe(mergeMap((validator) => validator(data))) .toPromise(); - expect(result.success).toBeTrue(); expect(data.bool).toBeUndefined(); expect(data.str).toBe('someString'); @@ -73,7 +63,7 @@ describe('addUndefinedDefaults', () => { it('should add defaults to undefined properties (2)', async () => { const registry = new CoreSchemaRegistry(); registry.addPreTransform(addUndefinedDefaults); - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const data: any = { bool: undefined, str: undefined, @@ -94,9 +84,8 @@ describe('addUndefinedDefaults', () => { }, }, }) - .pipe( - mergeMap(validator => validator(data)), - ).toPromise(); + .pipe(mergeMap((validator) => validator(data))) + .toPromise(); expect(result.success).toBeTrue(); expect(data.bool).toBeTrue(); @@ -107,11 +96,11 @@ describe('addUndefinedDefaults', () => { it('should add defaults to undefined properties when using oneOf', async () => { const registry = new CoreSchemaRegistry(); registry.addPreTransform(addUndefinedDefaults); - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const dataNoObj: any = { bool: undefined, }; - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const dataObj: any = { bool: undefined, obj: { @@ -119,58 +108,51 @@ describe('addUndefinedDefaults', () => { }, }; - const validator = registry - .compile({ - properties: { - bool: { type: 'boolean', default: true }, - obj: { - default: true, - oneOf: [ - { - type: 'object', - properties: { - a: { type: 'boolean', default: true }, - b: { type: 'boolean', default: true }, - c: { type: 'boolean', default: false }, - }, - }, - { - type: 'boolean', + const validator = registry.compile({ + properties: { + bool: { type: 'boolean', default: true }, + obj: { + default: true, + oneOf: [ + { + type: 'object', + properties: { + a: { type: 'boolean', default: true }, + b: { type: 'boolean', default: true }, + c: { type: 'boolean', default: false }, }, - ], - }, - noDefaultOneOf: { - oneOf: [ - { - type: 'object', - properties: { - a: { type: 'boolean', default: true }, - b: { type: 'boolean', default: true }, - c: { type: 'boolean', default: false }, - }, - }, - { - type: 'boolean', + }, + { + type: 'boolean', + }, + ], + }, + noDefaultOneOf: { + oneOf: [ + { + type: 'object', + properties: { + a: { type: 'boolean', default: true }, + b: { type: 'boolean', default: true }, + c: { type: 'boolean', default: false }, }, - ], - }, + }, + { + type: 'boolean', + }, + ], }, - }); + }, + }); - const result1 = await validator - .pipe( - mergeMap(validator => validator(dataNoObj)), - ).toPromise(); + const result1 = await validator.pipe(mergeMap((validator) => validator(dataNoObj))).toPromise(); expect(result1.success).toBeTrue(); expect(dataNoObj.bool).toBeTrue(); expect(dataNoObj.obj).toBeTrue(); expect(dataNoObj.noDefaultOneOf).toBeUndefined(); - const result2 = await validator - .pipe( - mergeMap(validator => validator(dataObj)), - ).toPromise(); + const result2 = await validator.pipe(mergeMap((validator) => validator(dataObj))).toPromise(); expect(result2.success).toBeTrue(); expect(dataObj.bool).toBeTrue(); @@ -182,11 +164,11 @@ describe('addUndefinedDefaults', () => { it('should add defaults to undefined properties when using anyOf', async () => { const registry = new CoreSchemaRegistry(); registry.addPreTransform(addUndefinedDefaults); - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const dataNoObj: any = { bool: undefined, }; - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const dataObj: any = { bool: undefined, obj: { @@ -194,48 +176,41 @@ describe('addUndefinedDefaults', () => { }, }; - const validator = registry - .compile({ - properties: { - bool: { type: 'boolean', default: true }, - obj: { - default: true, - anyOf: [ - { - type: 'object', - properties: { - d: { type: 'boolean', default: false }, - }, - }, - { - type: 'object', - properties: { - a: { type: 'boolean', default: true }, - b: { type: 'boolean', default: true }, - c: { type: 'boolean', default: false }, - }, + const validator = registry.compile({ + properties: { + bool: { type: 'boolean', default: true }, + obj: { + default: true, + anyOf: [ + { + type: 'object', + properties: { + d: { type: 'boolean', default: false }, }, - { - type: 'boolean', + }, + { + type: 'object', + properties: { + a: { type: 'boolean', default: true }, + b: { type: 'boolean', default: true }, + c: { type: 'boolean', default: false }, }, - ], - }, + }, + { + type: 'boolean', + }, + ], }, - }); + }, + }); - const result1 = await validator - .pipe( - mergeMap(validator => validator(dataNoObj)), - ).toPromise(); + const result1 = await validator.pipe(mergeMap((validator) => validator(dataNoObj))).toPromise(); expect(result1.success).toBeTrue(); expect(dataNoObj.bool).toBeTrue(); expect(dataNoObj.obj).toBeTrue(); - const result2 = await validator - .pipe( - mergeMap(validator => validator(dataObj)), - ).toPromise(); + const result2 = await validator.pipe(mergeMap((validator) => validator(dataObj))).toPromise(); expect(result2.success).toBeTrue(); expect(dataObj.bool).toBeTrue(); @@ -243,4 +218,42 @@ describe('addUndefinedDefaults', () => { expect(dataObj.obj.b).toBeTrue(); expect(dataObj.obj.c).toBeFalse(); }); + + it('should add defaults to undefined properties when using $refs', async () => { + const registry = new CoreSchemaRegistry(); + registry.addPreTransform(addUndefinedDefaults); + const dataNoObj: Record = {}; + + const dataObj: Record = { + boolRef: true, + }; + + const validator = registry.compile({ + definitions: { + boolRef: { + default: false, + type: 'boolean', + }, + }, + properties: { + bool: { + default: false, + type: 'boolean', + }, + boolRef: { + $ref: '#/definitions/boolRef', + }, + }, + }); + + const result1 = await validator.pipe(mergeMap((validator) => validator(dataNoObj))).toPromise(); + expect(result1.success).toBeTrue(); + expect(dataNoObj['bool']).toBeFalse(); + expect(dataNoObj['boolRef']).toBeFalse(); + + const result2 = await validator.pipe(mergeMap((validator) => validator(dataObj))).toPromise(); + expect(result2.success).toBeTrue(); + expect(dataObj['bool']).toBeFalse(); + expect(dataObj['boolRef']).toBeTrue(); + }); }); diff --git a/packages/angular_devkit/core/src/json/schema/utility.ts b/packages/angular_devkit/core/src/json/schema/utility.ts index 00b4a8278343..7f08e7abbda2 100644 --- a/packages/angular_devkit/core/src/json/schema/utility.ts +++ b/packages/angular_devkit/core/src/json/schema/utility.ts @@ -1,13 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { JsonObject, isJsonArray, isJsonObject } from '../interface'; -import { JsonSchema } from './schema'; +import { JsonObject, isJsonArray, isJsonObject } from '../utils'; +import { JsonSchema } from './schema'; const allTypes = ['string', 'integer', 'number', 'object', 'array', 'boolean', 'null']; @@ -53,13 +53,13 @@ export function getTypesOfSchema(schema: JsonSchema): Set { if (isJsonObject(schema.not)) { const notTypes = getTypesOfSchema(schema.not); - potentials = new Set([...potentials].filter(p => !notTypes.has(p))); + potentials = new Set([...potentials].filter((p) => !notTypes.has(p))); } if (Array.isArray(schema.allOf)) { for (const sub of schema.allOf) { const types = getTypesOfSchema(sub as JsonObject); - potentials = new Set([...types].filter(t => potentials.has(t))); + potentials = new Set([...types].filter((t) => potentials.has(t))); } } @@ -69,7 +69,7 @@ export function getTypesOfSchema(schema: JsonSchema): Set { const types = getTypesOfSchema(sub as JsonObject); options = new Set([...options, ...types]); } - potentials = new Set([...options].filter(o => potentials.has(o))); + potentials = new Set([...options].filter((o) => potentials.has(o))); } if (Array.isArray(schema.anyOf)) { @@ -78,7 +78,7 @@ export function getTypesOfSchema(schema: JsonSchema): Set { const types = getTypesOfSchema(sub as JsonObject); options = new Set([...options, ...types]); } - potentials = new Set([...options].filter(o => potentials.has(o))); + potentials = new Set([...options].filter((o) => potentials.has(o))); } if (schema.properties) { diff --git a/packages/angular_devkit/core/src/json/schema/visitor.ts b/packages/angular_devkit/core/src/json/schema/visitor.ts index 194c29eef4fe..c3f7ad18df78 100644 --- a/packages/angular_devkit/core/src/json/schema/visitor.ts +++ b/packages/angular_devkit/core/src/json/schema/visitor.ts @@ -1,13 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable, concat, from, isObservable, of as observableOf } from 'rxjs'; import { concatMap, ignoreElements, mergeMap, tap } from 'rxjs/operators'; -import { JsonArray, JsonObject, JsonValue } from '../interface'; +import { JsonArray, JsonObject, JsonValue } from '../utils'; import { JsonPointer, JsonSchemaVisitor, JsonVisitor } from './interface'; import { buildJsonPointer, joinJsonPointer } from './pointer'; import { JsonSchema } from './schema'; @@ -57,9 +58,10 @@ function _visitJsonRecursive( // There's no schema definition, so just visit the JSON recursively. schema = undefined; } + // eslint-disable-next-line no-prototype-builtins if (schema && schema.hasOwnProperty('$ref') && typeof schema['$ref'] == 'string') { if (refResolver) { - const resolved = refResolver(schema['$ref'] as string, context); + const resolved = refResolver(schema['$ref'], context); schema = resolved.schema; context = resolved.context; } @@ -68,7 +70,7 @@ function _visitJsonRecursive( const value = visitor(json, ptr, schema as JsonObject, root); return (isObservable(value) ? value : observableOf(value)).pipe( - concatMap(value => { + concatMap((value) => { if (Array.isArray(value)) { return concat( from(value).pipe( @@ -81,7 +83,7 @@ function _visitJsonRecursive( refResolver, context, root || value, - ).pipe(tap(x => (value[i] = x))); + ).pipe(tap((x) => (value[i] = x))); }), ignoreElements(), ), @@ -90,7 +92,7 @@ function _visitJsonRecursive( } else if (typeof value == 'object' && value !== null) { return concat( from(Object.getOwnPropertyNames(value)).pipe( - mergeMap(key => { + mergeMap((key) => { return _visitJsonRecursive( value[key], visitor, @@ -100,7 +102,7 @@ function _visitJsonRecursive( context, root || value, ).pipe( - tap(x => { + tap((x) => { const descriptor = Object.getOwnPropertyDescriptor(value, key); if (descriptor && descriptor.writable && value[key] !== x) { value[key] = x; diff --git a/packages/angular_devkit/core/src/json/schema/visitor_spec.ts b/packages/angular_devkit/core/src/json/schema/visitor_spec.ts index e938ccd56216..68a15f5f4758 100644 --- a/packages/angular_devkit/core/src/json/schema/visitor_spec.ts +++ b/packages/angular_devkit/core/src/json/schema/visitor_spec.ts @@ -1,37 +1,36 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable, from } from 'rxjs'; import { JsonObject, JsonValue } from '..'; import { visitJson } from './visitor'; - function syncObs(obs: Observable): T { let value: T; let set = false; obs - .forEach(x => { + .forEach((x) => { if (set) { throw new Error('Multiple value.'); } value = x; set = true; }) - .catch(err => fail(err)); + .catch((err) => fail(err)); if (!set) { throw new Error('Async observable.'); } - return value !; // tslint:disable-line:no-non-null-assertion + return value!; // eslint-disable-line @typescript-eslint/no-non-null-assertion } - describe('visitJson', () => { it('works to replace the root', () => { const json = { a: 1 }; @@ -45,13 +44,15 @@ describe('visitJson', () => { const json = { a: 1 }; const newJson = { b: 'hello ' }; - const result = syncObs(visitJson(json, value => { - if (typeof value == 'object') { - return newJson; - } else { - return value + 'world'; - } - })); + const result = syncObs( + visitJson(json, (value) => { + if (typeof value == 'object') { + return newJson; + } else { + return value + 'world'; + } + }), + ); expect(result).toEqual({ b: 'hello world' }); expect(newJson).toEqual({ b: 'hello world' }); }); @@ -62,26 +63,28 @@ describe('visitJson', () => { const newJson2 = { c: [] }; const newJson3 = [1, 2, 3]; - const result = syncObs(visitJson(json, (value, ptr) => { - if (ptr.endsWith('a')) { - return newJson; - } else if (ptr.endsWith('b')) { - return newJson2; - } else if (ptr.endsWith('c')) { - return newJson3; - } else if (typeof value == 'number') { - return '_' + value; - } else if (ptr == '/') { - return value; - } else { - return 'abc'; - } - })); + const result = syncObs( + visitJson(json, (value, ptr) => { + if (ptr.endsWith('a')) { + return newJson; + } else if (ptr.endsWith('b')) { + return newJson2; + } else if (ptr.endsWith('c')) { + return newJson3; + } else if (typeof value == 'number') { + return '_' + value; + } else if (ptr == '/') { + return value; + } else { + return 'abc'; + } + }), + ); expect(result).toEqual({ a: { b: { c: ['_1', '_2', '_3'] } } }); }); - it('goes through all replacements recursively (async)', done => { + it('goes through all replacements recursively (async)', (done) => { const json = { a: 1 }; const newJson = { b: '' }; const newJson2 = { c: [] }; @@ -101,13 +104,12 @@ describe('visitJson', () => { } else { return from(Promise.resolve('abc')); } - }).toPromise().then( - result => { + }) + .toPromise() + .then((result) => { expect(result).toEqual({ a: { b: { c: ['_1', '_2', '_3'] } } }); done(); - }, - done.fail, - ); + }, done.fail); }); it('works with schema', () => { diff --git a/packages/angular_devkit/core/src/json/utils.ts b/packages/angular_devkit/core/src/json/utils.ts new file mode 100644 index 000000000000..edbcd70c2b47 --- /dev/null +++ b/packages/angular_devkit/core/src/json/utils.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface JsonArray extends Array {} + +export interface JsonObject { + [prop: string]: JsonValue; +} + +export type JsonValue = boolean | string | number | JsonArray | JsonObject | null; + +export function isJsonObject(value: JsonValue): value is JsonObject { + return value != null && typeof value === 'object' && !Array.isArray(value); +} + +export function isJsonArray(value: JsonValue): value is JsonArray { + return Array.isArray(value); +} diff --git a/packages/angular_devkit/core/src/logger/indent.ts b/packages/angular_devkit/core/src/logger/indent.ts index 7eb1958fc855..18c132de10b2 100644 --- a/packages/angular_devkit/core/src/logger/indent.ts +++ b/packages/angular_devkit/core/src/logger/indent.ts @@ -1,14 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { map } from 'rxjs/operators'; import { Logger } from './logger'; - /** * Keep an map of indentation => array of indentations based on the level. * This is to optimize calculating the prefix based on the indentation itself. Since most logs @@ -16,8 +16,7 @@ import { Logger } from './logger'; * loggers. Also, string concatenation is expensive so performing concats for every log entries * is expensive; this alleviates it. */ -const indentationMap: {[indentationType: string]: string[]} = {}; - +const indentationMap: { [indentationType: string]: string[] } = {}; export class IndentLogger extends Logger { constructor(name: string, parent: Logger | null = null, indentation = ' ') { @@ -26,19 +25,21 @@ export class IndentLogger extends Logger { indentationMap[indentation] = indentationMap[indentation] || ['']; const indentMap = indentationMap[indentation]; - this._observable = this._observable.pipe(map(entry => { - const l = entry.path.filter(x => !!x).length; - if (l >= indentMap.length) { - let current = indentMap[indentMap.length - 1]; - while (l >= indentMap.length) { - current += indentation; - indentMap.push(current); + this._observable = this._observable.pipe( + map((entry) => { + const l = entry.path.filter((x) => !!x).length; + if (l >= indentMap.length) { + let current = indentMap[indentMap.length - 1]; + while (l >= indentMap.length) { + current += indentation; + indentMap.push(current); + } } - } - entry.message = indentMap[l] + entry.message.split(/\n/).join('\n' + indentMap[l]); + entry.message = indentMap[l] + entry.message.split(/\n/).join('\n' + indentMap[l]); - return entry; - })); + return entry; + }), + ); } } diff --git a/packages/angular_devkit/core/src/logger/indent_spec.ts b/packages/angular_devkit/core/src/logger/indent_spec.ts index a35ed3307a96..3d1f922dce92 100644 --- a/packages/angular_devkit/core/src/logger/indent_spec.ts +++ b/packages/angular_devkit/core/src/logger/indent_spec.ts @@ -1,20 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { toArray } from 'rxjs/operators'; import { IndentLogger } from './indent'; import { LogEntry, Logger } from './logger'; - describe('IndentSpec', () => { it('works', (done: DoneFn) => { const logger = new IndentLogger('test'); - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: LogEntry[]) => { expect(observed).toEqual([ @@ -25,7 +26,10 @@ describe('IndentSpec', () => { jasmine.objectContaining({ message: 'test5', level: 'info', name: 'test' }) as any, ]); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); const logger2 = new Logger('test2', logger); const logger3 = new Logger('test3', logger2); const logger4 = new Logger('test4', logger); diff --git a/packages/angular_devkit/core/src/logger/index.ts b/packages/angular_devkit/core/src/logger/index.ts index 63bc0860b0c8..02fb1e32bd1c 100644 --- a/packages/angular_devkit/core/src/logger/index.ts +++ b/packages/angular_devkit/core/src/logger/index.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export * from './indent'; export * from './level'; export * from './logger'; diff --git a/packages/angular_devkit/core/src/logger/level.ts b/packages/angular_devkit/core/src/logger/level.ts index 7de3255d90c5..b43d1b8b1e5d 100644 --- a/packages/angular_devkit/core/src/logger/level.ts +++ b/packages/angular_devkit/core/src/logger/level.ts @@ -1,33 +1,34 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { JsonObject } from '../json/interface'; + +import { JsonObject } from '../json/utils'; import { LogLevel, Logger } from './logger'; export class LevelTransformLogger extends Logger { constructor( - public readonly name: string, - public readonly parent: Logger | null = null, + public override readonly name: string, + public override readonly parent: Logger | null = null, public readonly levelTransform: (level: LogLevel) => LogLevel, ) { super(name, parent); } - log(level: LogLevel, message: string, metadata: JsonObject = {}): void { + override log(level: LogLevel, message: string, metadata: JsonObject = {}): void { return super.log(this.levelTransform(level), message, metadata); } - createChild(name: string): Logger { + override createChild(name: string): Logger { return new LevelTransformLogger(name, this, this.levelTransform); } } export class LevelCapLogger extends LevelTransformLogger { - static levelMap: {[cap: string]: {[level: string]: string}} = { + static levelMap: { [cap: string]: { [level: string]: string } } = { debug: { debug: 'debug', info: 'debug', warn: 'debug', error: 'debug', fatal: 'debug' }, info: { debug: 'debug', info: 'info', warn: 'info', error: 'info', fatal: 'info' }, warn: { debug: 'debug', info: 'info', warn: 'warn', error: 'warn', fatal: 'warn' }, @@ -36,8 +37,8 @@ export class LevelCapLogger extends LevelTransformLogger { }; constructor( - public readonly name: string, - public readonly parent: Logger | null = null, + public override readonly name: string, + public override readonly parent: Logger | null = null, public readonly levelCap: LogLevel, ) { super(name, parent, (level: LogLevel) => { diff --git a/packages/angular_devkit/core/src/logger/logger.ts b/packages/angular_devkit/core/src/logger/logger.ts index f684b2427299..4eb090cdb129 100644 --- a/packages/angular_devkit/core/src/logger/logger.ts +++ b/packages/angular_devkit/core/src/logger/logger.ts @@ -1,13 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Observable, Operator, PartialObserver, Subject, Subscription, empty } from 'rxjs'; -import { JsonObject } from '../json/interface'; +import { EMPTY, Observable, Operator, PartialObserver, Subject, Subscription } from 'rxjs'; +import { JsonObject } from '../json/utils'; export interface LoggerMetadata extends JsonObject { name: string; @@ -30,35 +30,40 @@ export interface LoggerApi { export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'; - export class Logger extends Observable implements LoggerApi { protected readonly _subject: Subject = new Subject(); protected _metadata: LoggerMetadata; - private _obs: Observable = empty(); + private _obs: Observable = EMPTY; private _subscription: Subscription | null = null; - protected get _observable() { return this._obs; } + protected get _observable() { + return this._obs; + } protected set _observable(v: Observable) { if (this._subscription) { this._subscription.unsubscribe(); } this._obs = v; if (this.parent) { - this._subscription = this.subscribe((value: LogEntry) => { - if (this.parent) { - this.parent._subject.next(value); - } - }, (error: Error) => { - if (this.parent) { - this.parent._subject.error(error); - } - }, () => { - if (this._subscription) { - this._subscription.unsubscribe(); - } - this._subscription = null; - }); + this._subscription = this.subscribe( + (value: LogEntry) => { + if (this.parent) { + this.parent._subject.next(value); + } + }, + (error: Error) => { + if (this.parent) { + this.parent._subject.error(error); + } + }, + () => { + if (this._subscription) { + this._subscription.unsubscribe(); + } + this._subscription = null; + }, + ); } } @@ -103,7 +108,9 @@ export class Logger extends Observable implements LoggerApi { log(level: LogLevel, message: string, metadata: JsonObject = {}): void { const entry: LogEntry = Object.assign({}, metadata, this._metadata, { - level, message, timestamp: +Date.now(), + level, + message, + timestamp: +Date.now(), }); this._subject.next(entry); } @@ -127,27 +134,38 @@ export class Logger extends Observable implements LoggerApi { return this.log('fatal', message, metadata); } - toString() { + override toString() { return ``; } - lift(operator: Operator): Observable { + override lift(operator: Operator): Observable { return this._observable.lift(operator); } - subscribe(): Subscription; - subscribe(observer: PartialObserver): Subscription; - subscribe(next?: (value: LogEntry) => void, error?: (error: Error) => void, - complete?: () => void): Subscription; - subscribe(_observerOrNext?: PartialObserver | ((value: LogEntry) => void), - _error?: (error: Error) => void, - _complete?: () => void): Subscription { + override subscribe(): Subscription; + override subscribe(observer: PartialObserver): Subscription; + override subscribe( + next?: (value: LogEntry) => void, + error?: (error: Error) => void, + complete?: () => void, + ): Subscription; + override subscribe( + _observerOrNext?: PartialObserver | ((value: LogEntry) => void), + _error?: (error: Error) => void, + _complete?: () => void, + ): Subscription { + // eslint-disable-next-line prefer-spread return this._observable.subscribe.apply( this._observable, - (arguments as unknown) as Parameters['subscribe']>, + // eslint-disable-next-line prefer-rest-params + arguments as unknown as Parameters['subscribe']>, ); } - forEach(next: (value: LogEntry) => void, PromiseCtor?: typeof Promise): Promise { - return this._observable.forEach(next, PromiseCtor); + + override forEach( + next: (value: LogEntry) => void, + promiseCtor: PromiseConstructorLike = Promise, + ): Promise { + return this._observable.forEach(next, promiseCtor); } } diff --git a/packages/angular_devkit/core/src/logger/logger_spec.ts b/packages/angular_devkit/core/src/logger/logger_spec.ts index 50439ca9a452..d3da5b331e85 100644 --- a/packages/angular_devkit/core/src/logger/logger_spec.ts +++ b/packages/angular_devkit/core/src/logger/logger_spec.ts @@ -1,28 +1,32 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { toArray } from 'rxjs/operators'; -import { JsonValue } from '../json/interface'; +import { JsonValue } from '../json/utils'; import { Logger } from './logger'; - describe('Logger', () => { it('works', (done: DoneFn) => { const logger = new Logger('test'); - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: JsonValue[]) => { expect(observed).toEqual([ - jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }) as any, - jasmine.objectContaining({ message: 'world', level: 'info', name: 'test' }) as any, + jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }), + jasmine.objectContaining({ message: 'world', level: 'info', name: 'test' }), ]); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); logger.debug('hello'); logger.info('world'); @@ -32,7 +36,8 @@ describe('Logger', () => { it('works with children', (done: DoneFn) => { const logger = new Logger('test'); let hasCompleted = false; - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: JsonValue[]) => { expect(observed).toEqual([ @@ -41,10 +46,13 @@ describe('Logger', () => { ]); expect(hasCompleted).toBe(true); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); const childLogger = new Logger('child', logger); - childLogger.subscribe(undefined, undefined, () => hasCompleted = true); + childLogger.subscribe(undefined, undefined, () => (hasCompleted = true)); childLogger.debug('hello'); childLogger.info('world'); logger.complete(); @@ -54,14 +62,18 @@ describe('Logger', () => { const logger = new Logger('test'); logger.debug('woah'); - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: JsonValue[]) => { expect(observed).toEqual([ jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }) as any, ]); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); logger.debug('hello'); logger.complete(); diff --git a/packages/angular_devkit/core/src/logger/null-logger.ts b/packages/angular_devkit/core/src/logger/null-logger.ts index 4a687840a1c3..4ca51bb73a9a 100644 --- a/packages/angular_devkit/core/src/logger/null-logger.ts +++ b/packages/angular_devkit/core/src/logger/null-logger.ts @@ -1,21 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { EMPTY } from 'rxjs'; import { Logger, LoggerApi } from './logger'; - export class NullLogger extends Logger { constructor(parent: Logger | null = null) { super('', parent); this._observable = EMPTY; } - asApi(): LoggerApi { + override asApi(): LoggerApi { return { createChild: () => new NullLogger(this), log() {}, diff --git a/packages/angular_devkit/core/src/logger/null-logger_spec.ts b/packages/angular_devkit/core/src/logger/null-logger_spec.ts index 52f1936e48f9..f7639f6c254d 100644 --- a/packages/angular_devkit/core/src/logger/null-logger_spec.ts +++ b/packages/angular_devkit/core/src/logger/null-logger_spec.ts @@ -1,24 +1,28 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { toArray } from 'rxjs/operators'; import { LogEntry, Logger } from './logger'; import { NullLogger } from './null-logger'; - describe('NullLogger', () => { it('works', (done: DoneFn) => { const logger = new NullLogger(); - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: LogEntry[]) => { expect(observed).toEqual([]); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); logger.debug('hello'); logger.info('world'); @@ -27,12 +31,16 @@ describe('NullLogger', () => { it('nullifies children', (done: DoneFn) => { const logger = new Logger('test'); - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: LogEntry[]) => { expect(observed).toEqual([]); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); const nullLogger = new NullLogger(logger); const child = new Logger('test', nullLogger); diff --git a/packages/angular_devkit/core/src/logger/transform-logger.ts b/packages/angular_devkit/core/src/logger/transform-logger.ts index 4d024fd2ffb2..2e4fd9c4d4e8 100644 --- a/packages/angular_devkit/core/src/logger/transform-logger.ts +++ b/packages/angular_devkit/core/src/logger/transform-logger.ts @@ -1,18 +1,20 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable } from 'rxjs'; import { LogEntry, Logger } from './logger'; - export class TransformLogger extends Logger { - constructor(name: string, - transform: (stream: Observable) => Observable, - parent: Logger | null = null) { + constructor( + name: string, + transform: (stream: Observable) => Observable, + parent: Logger | null = null, + ) { super(name, parent); this._observable = transform(this._observable); } diff --git a/packages/angular_devkit/core/src/logger/transform-logger_spec.ts b/packages/angular_devkit/core/src/logger/transform-logger_spec.ts index d99e2f87269c..f7b1268ece32 100644 --- a/packages/angular_devkit/core/src/logger/transform-logger_spec.ts +++ b/packages/angular_devkit/core/src/logger/transform-logger_spec.ts @@ -1,31 +1,36 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { filter, map, toArray } from 'rxjs/operators'; import { LogEntry } from './logger'; import { TransformLogger } from './transform-logger'; - describe('TransformLogger', () => { it('works', (done: DoneFn) => { - const logger = new TransformLogger('test', stream => { + const logger = new TransformLogger('test', (stream) => { return stream.pipe( - filter(entry => entry.message != 'hello'), - map(entry => (entry.message += '1', entry))); + filter((entry) => entry.message != 'hello'), + map((entry) => ((entry.message += '1'), entry)), + ); }); - logger.pipe(toArray()) + logger + .pipe(toArray()) .toPromise() .then((observed: LogEntry[]) => { expect(observed).toEqual([ jasmine.objectContaining({ message: 'world1', level: 'info', name: 'test' }) as any, ]); }) - .then(() => done(), err => done.fail(err)); + .then( + () => done(), + (err) => done.fail(err), + ); logger.debug('hello'); logger.info('world'); diff --git a/packages/angular_devkit/core/src/utils/array.ts b/packages/angular_devkit/core/src/utils/array.ts deleted file mode 100644 index adb896d69cfb..000000000000 --- a/packages/angular_devkit/core/src/utils/array.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -/** @deprecated Since v12.0, unused by the Angular tooling */ -export function clean(array: Array): Array { - return array.filter(x => x !== undefined) as Array; -} diff --git a/packages/angular_devkit/core/src/utils/index.ts b/packages/angular_devkit/core/src/utils/index.ts index 5e6749ee9b95..f5873285e487 100644 --- a/packages/angular_devkit/core/src/utils/index.ts +++ b/packages/angular_devkit/core/src/utils/index.ts @@ -1,14 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import * as tags from './literals'; import * as strings from './strings'; -export * from './array'; export * from './object'; export * from './template'; export * from './partially-ordered-set'; @@ -16,20 +16,3 @@ export * from './priority-queue'; export * from './lang'; export { tags, strings }; - -export type DeepReadonly = - T extends (infer R)[] ? DeepReadonlyArray : - T extends Function ? T : - T extends object ? DeepReadonlyObject : - T; - -// This should be ReadonlyArray but it has implications. -export interface DeepReadonlyArray extends Array> {} - -export type DeepReadonlyObject = { - readonly [P in keyof T]: DeepReadonly; -}; - -export type Readwrite = { - -readonly [P in keyof T]: T[P]; -}; diff --git a/packages/angular_devkit/core/src/utils/lang.ts b/packages/angular_devkit/core/src/utils/lang.ts index 032010566a7a..ecefcd0acd3d 100644 --- a/packages/angular_devkit/core/src/utils/lang.ts +++ b/packages/angular_devkit/core/src/utils/lang.ts @@ -1,16 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + // Borrowed from @angular/core /** * Determine if the argument is shaped like a Promise */ -// tslint:disable-next-line:no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isPromise(obj: any): obj is Promise { // allow any Promise/A+ compliant thenable. // It's up to the caller to ensure that obj.then conforms to the spec diff --git a/packages/angular_devkit/core/src/utils/literals.ts b/packages/angular_devkit/core/src/utils/literals.ts index bfa16c201c98..8b18251967f4 100644 --- a/packages/angular_devkit/core/src/utils/literals.ts +++ b/packages/angular_devkit/core/src/utils/literals.ts @@ -1,18 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export interface TemplateTag { // Any is the only way here. - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any (template: TemplateStringsArray, ...substitutions: any[]): R; } - -// tslint:disable-next-line:no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function oneLine(strings: TemplateStringsArray, ...values: any[]) { const endResult = String.raw(strings, ...values); @@ -30,8 +30,7 @@ export function indentBy(indentations: number): TemplateTag { }; } - -// tslint:disable-next-line:no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function stripIndent(strings: TemplateStringsArray, ...values: any[]) { const endResult = String.raw(strings, ...values); @@ -43,29 +42,30 @@ export function stripIndent(strings: TemplateStringsArray, ...values: any[]) { return endResult; } - const indent = Math.min(...match.map(el => el.length)); + const indent = Math.min(...match.map((el) => el.length)); const regexp = new RegExp('^[ \\t]{' + indent + '}', 'gm'); return (indent > 0 ? endResult.replace(regexp, '') : endResult).trim(); } - -// tslint:disable-next-line:no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function stripIndents(strings: TemplateStringsArray, ...values: any[]) { return String.raw(strings, ...values) .split('\n') - .map(line => line.trim()) + .map((line) => line.trim()) .join('\n') .trim(); } -// tslint:disable-next-line:no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function trimNewlines(strings: TemplateStringsArray, ...values: any[]) { const endResult = String.raw(strings, ...values); - return endResult - // Remove the newline at the start. - .replace(/^(?:\r?\n)+/, '') - // Remove the newline at the end and following whitespace. - .replace(/(?:\r?\n(?:\s*))$/, ''); + return ( + endResult + // Remove the newline at the start. + .replace(/^(?:\r?\n)+/, '') + // Remove the newline at the end and following whitespace. + .replace(/(?:\r?\n(?:\s*))$/, '') + ); } diff --git a/packages/angular_devkit/core/src/utils/literals_spec.ts b/packages/angular_devkit/core/src/utils/literals_spec.ts index 0883eb685535..c88978434d61 100644 --- a/packages/angular_devkit/core/src/utils/literals_spec.ts +++ b/packages/angular_devkit/core/src/utils/literals_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { oneLine, stripIndent, stripIndents, trimNewlines } from './literals'; describe('literals', () => { diff --git a/packages/angular_devkit/core/src/utils/object.ts b/packages/angular_devkit/core/src/utils/object.ts index ee5523d8a4b0..83676d3fd9c9 100644 --- a/packages/angular_devkit/core/src/utils/object.ts +++ b/packages/angular_devkit/core/src/utils/object.ts @@ -1,35 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -/** @deprecated Since v12.0, unused by the Angular tooling */ -export function mapObject(obj: { [k: string]: T }, - mapper: (k: string, v: T) => V): { [k: string]: V } { - return Object.keys(obj).reduce((acc: { [k: string]: V }, k: string) => { - acc[k] = mapper(k, obj[k]); - - return acc; - }, {}); -} - - const copySymbol = Symbol(); -// tslint:disable-next-line:no-any -export function deepCopy(value: T): T { +export function deepCopy(value: T): T { if (Array.isArray(value)) { - // tslint:disable-next-line:no-any - return value.map((o: any) => deepCopy(o)) as unknown as T; + return value.map((o) => deepCopy(o)) as unknown as T; } else if (value && typeof value === 'object') { - const valueCasted = value as { - [copySymbol]?: T, - toJSON?: () => string, - // tslint:disable-next-line:no-any - [key: string]: any, + const valueCasted = value as unknown as { + [copySymbol]?: T; + toJSON?: () => string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; }; if (valueCasted[copySymbol]) { @@ -46,7 +33,7 @@ export function deepCopy(value: T): T { for (const key of Object.getOwnPropertyNames(valueCasted)) { copy[key] = deepCopy(valueCasted[key]); } - valueCasted[copySymbol] = undefined; + delete valueCasted[copySymbol]; return copy; } else { diff --git a/packages/angular_devkit/core/src/utils/object_spec.ts b/packages/angular_devkit/core/src/utils/object_spec.ts index 67756e5f31b7..ac8d4ce7bcbc 100644 --- a/packages/angular_devkit/core/src/utils/object_spec.ts +++ b/packages/angular_devkit/core/src/utils/object_spec.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { deepCopy } from './object'; describe('object', () => { diff --git a/packages/angular_devkit/core/src/utils/partially-ordered-set.ts b/packages/angular_devkit/core/src/utils/partially-ordered-set.ts index 2127f521eef2..d9bc4c4e9933 100644 --- a/packages/angular_devkit/core/src/utils/partially-ordered-set.ts +++ b/packages/angular_devkit/core/src/utils/partially-ordered-set.ts @@ -1,17 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BaseException } from '../exception'; export class DependencyNotFoundException extends BaseException { - constructor() { super('One of the dependencies is not part of the set.'); } + constructor() { + super('One of the dependencies is not part of the set.'); + } } export class CircularDependencyFoundException extends BaseException { - constructor() { super('Circular dependencies found.'); } + constructor() { + super('Circular dependencies found.'); + } } export class PartiallyOrderedSet implements Set { @@ -22,7 +27,7 @@ export class PartiallyOrderedSet implements Set { throw new CircularDependencyFoundException(); } - deps.forEach(dep => this._checkCircularDependencies(item, this._items.get(dep) || new Set())); + deps.forEach((dep) => this._checkCircularDependencies(item, this._items.get(dep) || new Set())); } clear() { @@ -36,7 +41,7 @@ export class PartiallyOrderedSet implements Set { } forEach( callbackfn: (value: T, value2: T, set: PartiallyOrderedSet) => void, - thisArg?: any, // tslint:disable-line:no-any + thisArg?: any, // eslint-disable-line @typescript-eslint/no-explicit-any ): void { for (const x of this) { callbackfn.call(thisArg, x, x, this); @@ -66,8 +71,7 @@ export class PartiallyOrderedSet implements Set { return this[Symbol.iterator](); } - - add(item: T, deps: (Set | T[]) = new Set()) { + add(item: T, deps: Set | T[] = new Set()) { if (Array.isArray(deps)) { deps = new Set(deps); } @@ -121,7 +125,7 @@ export class PartiallyOrderedSet implements Set { } // Remove it from all dependencies if force == true. - this._items.forEach(value => value.delete(item)); + this._items.forEach((value) => value.delete(item)); return this._items.delete(item); } @@ -134,7 +138,7 @@ export class PartiallyOrderedSet implements Set { } while (copy.size > 0) { - const run = []; + const run: T[] = []; // Take the first item without dependencies. for (const [item, deps] of copy.entries()) { if (deps.size == 0) { @@ -143,7 +147,7 @@ export class PartiallyOrderedSet implements Set { } for (const item of run) { - copy.forEach(s => s.delete(item)); + copy.forEach((s) => s.delete(item)); copy.delete(item); yield item; } diff --git a/packages/angular_devkit/core/src/utils/partially-ordered-set_spec.ts b/packages/angular_devkit/core/src/utils/partially-ordered-set_spec.ts index 31aa667cb54e..6fdaf470466d 100644 --- a/packages/angular_devkit/core/src/utils/partially-ordered-set_spec.ts +++ b/packages/angular_devkit/core/src/utils/partially-ordered-set_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { PartiallyOrderedSet } from './partially-ordered-set'; describe('PartiallyOrderedSet', () => { diff --git a/packages/angular_devkit/core/src/utils/priority-queue.ts b/packages/angular_devkit/core/src/utils/priority-queue.ts index 9d5008487d47..58d2f355a62d 100644 --- a/packages/angular_devkit/core/src/utils/priority-queue.ts +++ b/packages/angular_devkit/core/src/utils/priority-queue.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -17,7 +17,7 @@ export class PriorityQueue { } push(item: T) { - const index = this._items.findIndex(existing => this._comparator(item, existing) <= 0); + const index = this._items.findIndex((existing) => this._comparator(item, existing) <= 0); if (index === -1) { this._items.push(item); diff --git a/packages/angular_devkit/core/src/utils/priority-queue_spec.ts b/packages/angular_devkit/core/src/utils/priority-queue_spec.ts index ef850273de4e..b6b6c16582df 100644 --- a/packages/angular_devkit/core/src/utils/priority-queue_spec.ts +++ b/packages/angular_devkit/core/src/utils/priority-queue_spec.ts @@ -1,12 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { PriorityQueue } from './priority-queue'; +import { PriorityQueue } from './priority-queue'; describe('PriorityQueue', () => { it('adds an item', () => { diff --git a/packages/angular_devkit/core/src/utils/strings.ts b/packages/angular_devkit/core/src/utils/strings.ts index ff3c0ce7cec3..40c420da2b26 100644 --- a/packages/angular_devkit/core/src/utils/strings.ts +++ b/packages/angular_devkit/core/src/utils/strings.ts @@ -1,15 +1,16 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -const STRING_DASHERIZE_REGEXP = (/[ _]/g); -const STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); -const STRING_CAMELIZE_REGEXP = (/(-|_|\.|\s)+(.)?/g); -const STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); -const STRING_UNDERSCORE_REGEXP_2 = (/-|\s+/g); + +const STRING_DASHERIZE_REGEXP = /[ _]/g; +const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g; +const STRING_CAMELIZE_REGEXP = /(-|_|\.|\s)+(.)?/g; +const STRING_UNDERSCORE_REGEXP_1 = /([a-z\d])([A-Z]+)/g; +const STRING_UNDERSCORE_REGEXP_2 = /-|\s+/g; /** * Converts a camelized string into all lower case separated by underscores. @@ -73,23 +74,27 @@ export function camelize(str: string): string { /** Returns the UpperCamelCase form of a string. + @example ```javascript 'innerHTML'.classify(); // 'InnerHTML' 'action_name'.classify(); // 'ActionName' 'css-class-name'.classify(); // 'CssClassName' 'my favorite items'.classify(); // 'MyFavoriteItems' + 'app.component'.classify(); // 'AppComponent' ``` - @method classify @param {String} str the string to classify @return {String} the classified string */ export function classify(str: string): string { - return str.split('.').map(part => capitalize(camelize(part))).join('.'); + return str + .split('.') + .map((part) => capitalize(camelize(part))) + .join(''); } /** - More general than decamelize. Returns the lower\_case\_and\_underscored + More general than decamelize. Returns the lower_case_and_underscored form of a string. ```javascript @@ -125,7 +130,7 @@ export function underscore(str: string): string { @return {String} The capitalized string. */ export function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.substr(1); + return str.charAt(0).toUpperCase() + str.slice(1); } /** @@ -147,7 +152,7 @@ export function levenshtein(a: string, b: string): number { return a.length; } - const matrix = []; + const matrix: number[][] = []; // increment along the first column of each row for (let i = 0; i <= b.length; i++) { diff --git a/packages/angular_devkit/core/src/utils/template.ts b/packages/angular_devkit/core/src/utils/template.ts index 1ed86bd81098..015f426973c6 100644 --- a/packages/angular_devkit/core/src/utils/template.ts +++ b/packages/angular_devkit/core/src/utils/template.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Position, SourceNode } from 'source-map'; // Matches <%= expr %>. This does not support structural JavaScript (for/if/...). @@ -19,7 +20,7 @@ const kEscapeRe = /<%-([\s\S]+?)%>/g; const kEvaluateRe = /<%([\s\S]+?)%>/g; /** Used to map characters to HTML entities. */ -const kHtmlEscapes: {[char: string]: string} = { +const kHtmlEscapes: { [char: string]: string } = { '&': '&', '<': '<', '>': '>', @@ -40,7 +41,6 @@ export interface TemplateOptions { fileName?: string; } - function _positionFor(content: string, offset: number): Position { let line = 1; let column = 0; @@ -117,21 +117,22 @@ export interface TemplateAstInterpolate extends TemplateAstBase { expression: string; } -export type TemplateAstNode = TemplateAstContent - | TemplateAstEvaluate - | TemplateAstComment - | TemplateAstEscape - | TemplateAstInterpolate; +export type TemplateAstNode = + | TemplateAstContent + | TemplateAstEvaluate + | TemplateAstComment + | TemplateAstEscape + | TemplateAstInterpolate; /** * Given a source text (and a fileName), returns a TemplateAst. */ export function templateParser(sourceText: string, fileName: string): TemplateAst { - const children = []; + const children: TemplateAstNode[] = []; // Compile the regexp to match each delimiter. const reExpressions = [kEscapeRe, kCommentRe, kInterpolateRe, kEvaluateRe]; - const reDelimiters = RegExp(reExpressions.map(x => x.source).join('|') + '|$', 'g'); + const reDelimiters = RegExp(reExpressions.map((x) => x.source).join('|') + '|$', 'g'); const parsed = sourceText.split(reDelimiters); let offset = 0; @@ -193,7 +194,7 @@ export function templateParser(sourceText: string, fileName: string): TemplateAs */ function templateFast(ast: TemplateAst, options?: TemplateOptions): string { const module = options && options.module ? 'module.exports.default =' : ''; - const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, '\\\\\\\''); + const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, "\\\\\\'"); return ` return ${module} function(obj) { @@ -207,7 +208,8 @@ function templateFast(ast: TemplateAst, options?: TemplateOptions): string { return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : ''; }; with (obj) { - ${ast.children.map(node => { + ${ast.children + .map((node) => { switch (node.kind) { case 'content': return `__p += ${JSON.stringify(node.content)};`; @@ -218,8 +220,8 @@ function templateFast(ast: TemplateAst, options?: TemplateOptions): string { case 'evaluate': return node.expression; } - }).join('\n') - } + }) + .join('\n')} } return __p; @@ -233,10 +235,10 @@ function templateFast(ast: TemplateAst, options?: TemplateOptions): string { function templateWithSourceMap(ast: TemplateAst, options?: TemplateOptions): string { const sourceUrl = ast.fileName; const module = options && options.module ? 'module.exports.default =' : ''; - const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, '\\\\\\\''); + const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, "\\\\\\'"); - const preamble = (new SourceNode(1, 0, sourceUrl, '')) - .add(new SourceNode(1, 0, sourceUrl, [ + const preamble = new SourceNode(1, 0, sourceUrl, '').add( + new SourceNode(1, 0, sourceUrl, [ `return ${module} function(obj) {\n`, ' obj || (obj = {});\n', ' let __t;\n', @@ -248,96 +250,95 @@ function templateWithSourceMap(ast: TemplateAst, options?: TemplateOptions): str ` return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';`, ` };\n`, ` with (obj) {\n`, - ])); + ]), + ); const end = ast.children.length ? ast.children[ast.children.length - 1].end : { line: 0, column: 0 }; - const nodes = ast.children.reduce((chunk, node) => { - let code: string | SourceNode | (SourceNode | string)[] = ''; - switch (node.kind) { - case 'content': - code = [ - new SourceNode(node.start.line, node.start.column, sourceUrl, '__p = __p'), - ...node.content.split('\n').map((line, i, arr) => { - return new SourceNode( - node.start.line + i, - i == 0 ? node.start.column : 0, - sourceUrl, - '\n + ' - + JSON.stringify(line + (i == arr.length - 1 ? '' : '\n')), - ); - }), - new SourceNode(node.end.line, node.end.column, sourceUrl, ';\n'), - ]; - break; - case 'interpolate': - code = [ - new SourceNode(node.start.line, node.start.column, sourceUrl, '__p += ((__t = '), - ...node.expression.split('\n').map((line, i, arr) => { - return new SourceNode( - node.start.line + i, - i == 0 ? node.start.column : 0, - sourceUrl, - line + ((i == arr.length - 1) ? '' : '\n'), - ); - }), - new SourceNode(node.end.line, node.end.column, sourceUrl, ') == null ? "" : __t);\n'), - ]; - break; - case 'escape': - code = [ - new SourceNode(node.start.line, node.start.column, sourceUrl, '__p += __e('), - ...node.expression.split('\n').map((line, i, arr) => { - return new SourceNode( - node.start.line + i, - i == 0 ? node.start.column : 0, - sourceUrl, - line + ((i == arr.length - 1) ? '' : '\n'), - ); - }), - new SourceNode(node.end.line, node.end.column, sourceUrl, ');\n'), - ]; - break; - case 'evaluate': - code = [ - ...node.expression.split('\n').map((line, i, arr) => { - return new SourceNode( - node.start.line + i, - i == 0 ? node.start.column : 0, - sourceUrl, - line + ((i == arr.length - 1) ? '' : '\n'), - ); - }), - new SourceNode(node.end.line, node.end.column, sourceUrl, '\n'), - ]; - break; - } + const nodes = ast.children + .reduce((chunk, node) => { + let code: string | SourceNode | (SourceNode | string)[] = ''; + switch (node.kind) { + case 'content': + code = [ + new SourceNode(node.start.line, node.start.column, sourceUrl, '__p = __p'), + ...node.content.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + '\n + ' + JSON.stringify(line + (i == arr.length - 1 ? '' : '\n')), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, ';\n'), + ]; + break; + case 'interpolate': + code = [ + new SourceNode(node.start.line, node.start.column, sourceUrl, '__p += ((__t = '), + ...node.expression.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + line + (i == arr.length - 1 ? '' : '\n'), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, ') == null ? "" : __t);\n'), + ]; + break; + case 'escape': + code = [ + new SourceNode(node.start.line, node.start.column, sourceUrl, '__p += __e('), + ...node.expression.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + line + (i == arr.length - 1 ? '' : '\n'), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, ');\n'), + ]; + break; + case 'evaluate': + code = [ + ...node.expression.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + line + (i == arr.length - 1 ? '' : '\n'), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, '\n'), + ]; + break; + } - return chunk.add(new SourceNode(node.start.line, node.start.column, sourceUrl, code)); - }, preamble) - .add(new SourceNode(end.line, end.column, sourceUrl, [ - ' };\n', - '\n', - ' return __p;\n', - '}\n', - ])); + return chunk.add(new SourceNode(node.start.line, node.start.column, sourceUrl, code)); + }, preamble) + .add( + new SourceNode(end.line, end.column, sourceUrl, [' };\n', '\n', ' return __p;\n', '}\n']), + ); const code = nodes.toStringWithSourceMap({ file: sourceUrl, - sourceRoot: options && options.sourceRoot || '.', + sourceRoot: (options && options.sourceRoot) || '.', }); // Set the source content in the source map, otherwise the sourceUrl is not enough // to find the content. code.map.setSourceContent(sourceUrl, ast.content); - return code.code - + '\n//# sourceMappingURL=data:application/json;base64,' - + Buffer.from(code.map.toString()).toString('base64'); + return ( + code.code + + '\n//# sourceMappingURL=data:application/json;base64,' + + Buffer.from(code.map.toString()).toString('base64') + ); } - /** * An equivalent of EJS templates, which is based on John Resig's `tmpl` implementation * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js @@ -354,7 +355,7 @@ function templateWithSourceMap(ast: TemplateAst, options?: TemplateOptions): str * of the template with the input applied. */ export function template(content: string, options?: TemplateOptions): (input: T) => string { - const sourceUrl = options && options.sourceURL || 'ejs'; + const sourceUrl = (options && options.sourceURL) || 'ejs'; const ast = templateParser(content, sourceUrl); let source: string; @@ -369,9 +370,8 @@ export function template(content: string, options?: TemplateOptions): (input: // need to only use the source, not the function itself. Otherwise expect a module object to be // passed, and we use that one. const fn = Function('module', source); - const module = options && options.module - ? (options.module === true ? { exports: {} } : options.module) - : null; + const module = + options && options.module ? (options.module === true ? { exports: {} } : options.module) : null; const result = fn(module); // Provide the compiled function's source by its `toString` method or diff --git a/packages/angular_devkit/core/src/virtual-fs/host/alias.ts b/packages/angular_devkit/core/src/virtual-fs/host/alias.ts index 4df9c6b1fbe2..f51de0a3ff71 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/alias.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/alias.ts @@ -1,14 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { NormalizedRoot, Path, PathFragment, join, split } from '../path'; import { ResolverHost } from './resolver'; - /** * A Virtual Host that allow to alias some paths to other paths. * @@ -74,11 +74,13 @@ export class AliasHost extends ResolverHost maybeAlias = join(maybeAlias, ...remaining); } // Allow non-null-operator because we know sp.length > 0 (condition on while). - remaining.unshift(sp.pop() !); // tslint:disable-line:no-non-null-assertion + remaining.unshift(sp.pop()!); // eslint-disable-line @typescript-eslint/no-non-null-assertion } return maybeAlias || path; } - get aliases(): Map { return this._aliases; } + get aliases(): Map { + return this._aliases; + } } diff --git a/packages/angular_devkit/core/src/virtual-fs/host/alias_spec.ts b/packages/angular_devkit/core/src/virtual-fs/host/alias_spec.ts index ff852af9910e..6f6f92c05964 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/alias_spec.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/alias_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { normalize } from '..'; import { AliasHost } from './alias'; import { stringToFileBuffer } from './buffer'; @@ -18,16 +19,14 @@ describe('AliasHost', () => { host.write(normalize('/some/file'), content).subscribe(); const aHost = new AliasHost(host); - aHost.read(normalize('/some/file')) - .subscribe(x => expect(x).toBe(content)); + aHost.read(normalize('/some/file')).subscribe((x) => expect(x).toBe(content)); aHost.aliases.set(normalize('/some/file'), normalize('/other/path')); // This file will not exist because /other/path does not exist. try { - aHost.read(normalize('/some/file')) - .subscribe(undefined, err => { - expect(err.message).toMatch(/does not exist/); - }); + aHost.read(normalize('/some/file')).subscribe(undefined, (err) => { + expect(err.message).toMatch(/does not exist/); + }); } catch { // Ignore it. RxJS <6 still throw errors when they happen synchronously. } @@ -41,19 +40,18 @@ describe('AliasHost', () => { host.write(normalize('/some/folder/file'), content).subscribe(); const aHost = new AliasHost(host); - aHost.read(normalize('/some/folder/file')) - .subscribe(x => expect(x).toBe(content)); + aHost.read(normalize('/some/folder/file')).subscribe((x) => expect(x).toBe(content)); aHost.aliases.set(normalize('/some'), normalize('/other')); // This file will not exist because /other/path does not exist. try { - aHost.read(normalize('/some/folder/file')) - .subscribe(undefined, err => expect(err.message).toMatch(/does not exist/)); + aHost + .read(normalize('/some/folder/file')) + .subscribe(undefined, (err) => expect(err.message).toMatch(/does not exist/)); } catch {} // Create the file with new content and verify that this has the new content. aHost.write(normalize('/other/folder/file'), content2).subscribe(); - aHost.read(normalize('/some/folder/file')) - .subscribe(x => expect(x).toBe(content2)); + aHost.read(normalize('/some/folder/file')).subscribe((x) => expect(x).toBe(content2)); }); }); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/buffer.ts b/packages/angular_devkit/core/src/virtual-fs/host/buffer.ts index 08a988d25306..515e89926533 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/buffer.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/buffer.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { TemplateTag } from '../../utils/literals'; import { FileBuffer } from './interface'; @@ -15,7 +16,7 @@ declare const TextEncoder: { }; declare const TextDecoder: { - new(encoding: string): { + new (encoding: string): { decode(bytes: Uint8Array): string; }; }; @@ -33,7 +34,7 @@ export function stringToFileBuffer(str: string): FileBuffer { return ab; } else if (typeof TextEncoder !== 'undefined') { // Modern browsers implement TextEncode. - return new TextEncoder('utf-8').encode(str).buffer as ArrayBuffer; + return new TextEncoder('utf-8').encode(str).buffer; } else { // Slowest method but sure to be compatible with every platform. const buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char @@ -46,12 +47,10 @@ export function stringToFileBuffer(str: string): FileBuffer { } } - export const fileBuffer: TemplateTag = (strings, ...values) => { return stringToFileBuffer(String.raw(strings, ...values)); }; - export function fileBufferToString(fileBuffer: FileBuffer): string { if (fileBuffer.toString.length == 1) { return (fileBuffer.toString as (enc: string) => string)('utf-8'); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/create.ts b/packages/angular_devkit/core/src/virtual-fs/host/create.ts index 8002f1dea48b..5e8e3cd38148 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/create.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/create.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable } from 'rxjs'; import { Path, PathFragment } from '../path'; import { FileBuffer, FileBufferLike, Host, HostCapabilities, Stats } from './interface'; diff --git a/packages/angular_devkit/core/src/virtual-fs/host/empty.ts b/packages/angular_devkit/core/src/virtual-fs/host/empty.ts index 791b27c6e7cf..3a9a20a2ef11 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/empty.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/empty.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable, of, throwError } from 'rxjs'; import { FileDoesNotExistException } from '../../exception'; import { Path, PathFragment } from '../path'; diff --git a/packages/angular_devkit/core/src/virtual-fs/host/index.ts b/packages/angular_devkit/core/src/virtual-fs/host/index.ts index a795f4209fc6..affdcc3b2fa6 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/index.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license diff --git a/packages/angular_devkit/core/src/virtual-fs/host/interface.ts b/packages/angular_devkit/core/src/virtual-fs/host/interface.ts index 9c48e58f5d0d..1682584ff677 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/interface.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/interface.ts @@ -1,14 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable } from 'rxjs'; import { Path, PathFragment } from '../path'; - export type FileBuffer = ArrayBuffer; export type FileBufferLike = ArrayBufferLike; @@ -17,12 +17,11 @@ export interface HostWatchOptions { readonly recursive?: boolean; } - export const enum HostWatchEventType { Changed = 0, Created = 1, Deleted = 2, - Renamed = 3, // Applied to the original file path. + Renamed = 3, // Applied to the original file path. } export type Stats = T & { diff --git a/packages/angular_devkit/core/src/virtual-fs/host/memory.ts b/packages/angular_devkit/core/src/virtual-fs/host/memory.ts index ca80bc7ea916..d1d08825ddff 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/memory.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/memory.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable, Subject } from 'rxjs'; import { FileAlreadyExistException, @@ -33,7 +34,6 @@ import { Stats, } from './interface'; - export interface SimpleMemoryHostStats { readonly content: FileBuffer | null; } @@ -44,10 +44,16 @@ export class SimpleMemoryHost implements Host<{}> { protected _newDirStats() { return { - inspect() { return ''; }, - - isFile() { return false; }, - isDirectory() { return true; }, + inspect() { + return ''; + }, + + isFile() { + return false; + }, + isDirectory() { + return true; + }, size: 0, atime: new Date(), @@ -60,10 +66,16 @@ export class SimpleMemoryHost implements Host<{}> { } protected _newFileStats(content: FileBuffer, oldStats?: Stats) { return { - inspect() { return ``; }, - - isFile() { return true; }, - isDirectory() { return false; }, + inspect() { + return ``; + }, + + isFile() { + return true; + }, + isDirectory() { + return false; + }, size: content.byteLength, atime: oldStats ? oldStats.atime : new Date(), @@ -95,7 +107,7 @@ export class SimpleMemoryHost implements Host<{}> { const maybeWatcher = this._watchers.get(currentPath); if (maybeWatcher) { - maybeWatcher.forEach(watcher => { + maybeWatcher.forEach((watcher) => { const [options, subject] = watcher; subject.next({ path, time, type }); @@ -112,7 +124,7 @@ export class SimpleMemoryHost implements Host<{}> { const maybeWatcher = this._watchers.get(currentPath); if (maybeWatcher) { - maybeWatcher.forEach(watcher => { + maybeWatcher.forEach((watcher) => { const [options, subject] = watcher; if (!options.recursive) { return; @@ -212,7 +224,7 @@ export class SimpleMemoryHost implements Host<{}> { const content = this._cache.get(from); if (content) { const fragments = split(to); - const newDirectories = []; + const newDirectories: Path[] = []; let curr: Path = normalize('/'); for (const fr of fragments) { curr = join(curr, fr); @@ -301,7 +313,7 @@ export class SimpleMemoryHost implements Host<{}> { } write(path: Path, content: FileBuffer): Observable { - return new Observable(obs => { + return new Observable((obs) => { this._write(path, content); obs.next(); obs.complete(); @@ -309,7 +321,7 @@ export class SimpleMemoryHost implements Host<{}> { } read(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { const content = this._read(path); obs.next(content); obs.complete(); @@ -317,7 +329,7 @@ export class SimpleMemoryHost implements Host<{}> { } delete(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { this._delete(path); obs.next(); obs.complete(); @@ -325,7 +337,7 @@ export class SimpleMemoryHost implements Host<{}> { } rename(from: Path, to: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { this._rename(from, to); obs.next(); obs.complete(); @@ -333,28 +345,28 @@ export class SimpleMemoryHost implements Host<{}> { } list(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { obs.next(this._list(path)); obs.complete(); }); } exists(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { obs.next(this._exists(path)); obs.complete(); }); } isDirectory(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { obs.next(this._isDirectory(path)); obs.complete(); }); } isFile(path: Path): Observable { - return new Observable(obs => { + return new Observable((obs) => { obs.next(this._isFile(path)); obs.complete(); }); @@ -362,7 +374,7 @@ export class SimpleMemoryHost implements Host<{}> { // Some hosts may not support stat. stat(path: Path): Observable | null> | null { - return new Observable | null>(obs => { + return new Observable | null>((obs) => { obs.next(this._stat(path)); obs.complete(); }); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/memory_spec.ts b/packages/angular_devkit/core/src/virtual-fs/host/memory_spec.ts index bf9df7a106c9..08cd4cf1db80 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/memory_spec.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/memory_spec.ts @@ -1,19 +1,18 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-implicit-dependencies -// tslint:disable:no-non-null-assertion -import { fragment, normalize } from '@angular-devkit/core'; + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { fragment, normalize } from '../path'; import { stringToFileBuffer } from './buffer'; import { SimpleMemoryHost } from './memory'; import { SyncDelegateHost } from './sync'; describe('SimpleMemoryHost', () => { - it('can watch', () => { const host = new SyncDelegateHost(new SimpleMemoryHost()); @@ -24,10 +23,10 @@ describe('SimpleMemoryHost', () => { let noRecursiveFileCalled = 0; let diffFile = 0; - host.watch(normalize('/sub'), { recursive: true }) !.subscribe(() => recursiveCalled++); - host.watch(normalize('/sub')) !.subscribe(() => noRecursiveCalled++); - host.watch(normalize('/sub/file2')) !.subscribe(() => noRecursiveFileCalled++); - host.watch(normalize('/sub/file3')) !.subscribe(() => diffFile++); + host.watch(normalize('/sub'), { recursive: true })!.subscribe(() => recursiveCalled++); + host.watch(normalize('/sub'))!.subscribe(() => noRecursiveCalled++); + host.watch(normalize('/sub/file2'))!.subscribe(() => noRecursiveFileCalled++); + host.watch(normalize('/sub/file3'))!.subscribe(() => diffFile++); host.write(normalize('/sub/file2'), stringToFileBuffer('')); @@ -105,10 +104,12 @@ describe('SimpleMemoryHost', () => { host.write(normalize('/sub/sub1/file3'), buffer); host.write(normalize('/file4'), buffer); - expect(host.list(normalize('/sub'))) - .toEqual([fragment('file1'), fragment('file2'), fragment('sub1')]); - expect(host.list(normalize('/'))) - .toEqual([fragment('sub'), fragment('file4')]); + expect(host.list(normalize('/sub'))).toEqual([ + fragment('file1'), + fragment('file2'), + fragment('sub1'), + ]); + expect(host.list(normalize('/'))).toEqual([fragment('sub'), fragment('file4')]); expect(host.list(normalize('/inexistent'))).toEqual([]); }); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/pattern.ts b/packages/angular_devkit/core/src/virtual-fs/host/pattern.ts index 9880c1e67903..bcf74f1c87f9 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/pattern.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/pattern.ts @@ -1,17 +1,16 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Path } from '../path'; import { ResolverHost } from './resolver'; - export type ReplacementFunction = (path: Path) => Path; - /** */ export class PatternMatchingHost extends ResolverHost { @@ -19,17 +18,25 @@ export class PatternMatchingHost extends ResolverHos addPattern(pattern: string | string[], replacementFn: ReplacementFunction) { // Simple GLOB pattern replacement. - const reString = '^(' - + (Array.isArray(pattern) ? pattern : [pattern]) - .map(ex => '(' - + ex.split(/[\/\\]/g).map(f => f - .replace(/[\-\[\]{}()+?.^$|]/g, '\\$&') - .replace(/^\*\*/g, '(.+?)?') - .replace(/\*/g, '[^/\\\\]*')) - .join('[\/\\\\]') - + ')') - .join('|') - + ')($|/|\\\\)'; + const reString = + '^(' + + (Array.isArray(pattern) ? pattern : [pattern]) + .map( + (ex) => + '(' + + ex + .split(/[/\\]/g) + .map((f) => + f + .replace(/[-[\]{}()+?.^$|]/g, '\\$&') + .replace(/^\*\*/g, '(.+?)?') + .replace(/\*/g, '[^/\\\\]*'), + ) + .join('[/\\\\]') + + ')', + ) + .join('|') + + ')($|/|\\\\)'; this._patterns.set(new RegExp(reString), replacementFn); } diff --git a/packages/angular_devkit/core/src/virtual-fs/host/pattern_spec.ts b/packages/angular_devkit/core/src/virtual-fs/host/pattern_spec.ts index eb10ea21c1e0..55b779d06c49 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/pattern_spec.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/pattern_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { normalize } from '..'; import { stringToFileBuffer } from './buffer'; import { SimpleMemoryHost } from './memory'; @@ -19,26 +20,23 @@ describe('PatternMatchingHost', () => { host.write(normalize('/some/file.tns.ts'), content).subscribe(); const pHost = new PatternMatchingHost(host); - pHost.read(normalize('/some/file.tns.ts')) - .subscribe(x => expect(x).toBe(content)); + pHost.read(normalize('/some/file.tns.ts')).subscribe((x) => expect(x).toBe(content)); - pHost.addPattern('**/*.tns.ts', path => { + pHost.addPattern('**/*.tns.ts', (path) => { return normalize(path.replace(/\.tns\.ts$/, '.ts')); }); // This file will not exist because /some/file.ts does not exist. try { - pHost.read(normalize('/some/file.tns.ts')) - .subscribe(undefined, err => { - expect(err.message).toMatch(/does not exist/); - }); + pHost.read(normalize('/some/file.tns.ts')).subscribe(undefined, (err) => { + expect(err.message).toMatch(/does not exist/); + }); } catch { // Ignore it. RxJS <6 still throw errors when they happen synchronously. } // Create the file, it should exist now. pHost.write(normalize('/some/file.ts'), content2).subscribe(); - pHost.read(normalize('/some/file.tns.ts')) - .subscribe(x => expect(x).toBe(content2)); + pHost.read(normalize('/some/file.tns.ts')).subscribe((x) => expect(x).toBe(content2)); }); }); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/record.ts b/packages/angular_devkit/core/src/virtual-fs/host/record.ts index 899c445d735e..037789f4bdd9 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/record.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/record.ts @@ -1,18 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - EMPTY, - Observable, - concat, - from as observableFrom, - of, - throwError, -} from 'rxjs'; + +import { EMPTY, Observable, concat, from as observableFrom, of, throwError } from 'rxjs'; import { concatMap, map, reduce, switchMap, toArray } from 'rxjs/operators'; import { FileAlreadyExistException, @@ -31,7 +25,6 @@ import { } from './interface'; import { SimpleMemoryHost } from './memory'; - export interface CordHostCreate { kind: 'create'; path: Path; @@ -51,11 +44,7 @@ export interface CordHostDelete { kind: 'delete'; path: Path; } -export type CordHostRecord = CordHostCreate - | CordHostOverwrite - | CordHostRename - | CordHostDelete; - +export type CordHostRecord = CordHostCreate | CordHostOverwrite | CordHostRename | CordHostDelete; /** * A Host that records changes to the underlying Host, while keeping a record of Create, Overwrite, @@ -74,10 +63,14 @@ export class CordHost extends SimpleMemoryHost { protected _filesToDelete = new Set(); protected _filesToOverwrite = new Set(); - constructor(protected _back: ReadonlyHost) { super(); } + constructor(protected _back: ReadonlyHost) { + super(); + } - get backend(): ReadonlyHost { return this._back; } - get capabilities(): HostCapabilities { + get backend(): ReadonlyHost { + return this._back; + } + override get capabilities(): HostCapabilities { // Our own host is always Synchronous, but the backend might not be. return { synchronous: this._back.capabilities.synchronous, @@ -113,13 +106,15 @@ export class CordHost extends SimpleMemoryHost { commit(host: Host, force = false): Observable { // Really commit everything to the actual host. return observableFrom(this.records()).pipe( - concatMap(record => { + concatMap((record) => { switch (record.kind) { - case 'delete': return host.delete(record.path); - case 'rename': return host.rename(record.from, record.to); + case 'delete': + return host.delete(record.path); + case 'rename': + return host.rename(record.from, record.to); case 'create': return host.exists(record.path).pipe( - switchMap(exists => { + switchMap((exists) => { if (exists && !force) { return throwError(new FileAlreadyExistException(record.path)); } else { @@ -129,7 +124,7 @@ export class CordHost extends SimpleMemoryHost { ); case 'overwrite': return host.exists(record.path).pipe( - switchMap(exists => { + switchMap((exists) => { if (!exists && !force) { return throwError(new FileDoesNotExistException(record.path)); } else { @@ -145,18 +140,37 @@ export class CordHost extends SimpleMemoryHost { records(): CordHostRecord[] { return [ - ...[...this._filesToDelete.values()].map(path => ({ - kind: 'delete', path, - }) as CordHostRecord), - ...[...this._filesToRename.entries()].map(([from, to]) => ({ - kind: 'rename', from, to, - }) as CordHostRecord), - ...[...this._filesToCreate.values()].map(path => ({ - kind: 'create', path, content: this._read(path), - }) as CordHostRecord), - ...[...this._filesToOverwrite.values()].map(path => ({ - kind: 'overwrite', path, content: this._read(path), - }) as CordHostRecord), + ...[...this._filesToDelete.values()].map( + (path) => + ({ + kind: 'delete', + path, + } as CordHostRecord), + ), + ...[...this._filesToRename.entries()].map( + ([from, to]) => + ({ + kind: 'rename', + from, + to, + } as CordHostRecord), + ), + ...[...this._filesToCreate.values()].map( + (path) => + ({ + kind: 'create', + path, + content: this._read(path), + } as CordHostRecord), + ), + ...[...this._filesToOverwrite.values()].map( + (path) => + ({ + kind: 'overwrite', + path, + content: this._read(path), + } as CordHostRecord), + ), ]; } @@ -184,14 +198,14 @@ export class CordHost extends SimpleMemoryHost { overwrite(path: Path, content: FileBuffer): Observable { return this.isDirectory(path).pipe( - switchMap(isDir => { + switchMap((isDir) => { if (isDir) { return throwError(new PathIsDirectoryException(path)); } return this.exists(path); }), - switchMap(exists => { + switchMap((exists) => { if (!exists) { return throwError(new FileDoesNotExistException(path)); } @@ -205,9 +219,9 @@ export class CordHost extends SimpleMemoryHost { ); } - write(path: Path, content: FileBuffer): Observable { + override write(path: Path, content: FileBuffer): Observable { return this.exists(path).pipe( - switchMap(exists => { + switchMap((exists) => { if (exists) { // It exists, but might be being renamed or deleted. In that case we want to create it. if (this.willRename(path) || this.willDelete(path)) { @@ -222,7 +236,7 @@ export class CordHost extends SimpleMemoryHost { ); } - read(path: Path): Observable { + override read(path: Path): Observable { if (this._exists(path)) { return super.read(path); } @@ -230,7 +244,7 @@ export class CordHost extends SimpleMemoryHost { return this._back.read(path); } - delete(path: Path): Observable { + override delete(path: Path): Observable { if (this._exists(path)) { if (this._filesToCreate.has(path)) { this._filesToCreate.delete(path); @@ -253,7 +267,7 @@ export class CordHost extends SimpleMemoryHost { return super.delete(path); } else { return this._back.exists(path).pipe( - switchMap(exists => { + switchMap((exists) => { if (exists) { this._filesToDelete.add(path); @@ -266,11 +280,8 @@ export class CordHost extends SimpleMemoryHost { } } - rename(from: Path, to: Path): Observable { - return concat( - this.exists(to), - this.exists(from), - ).pipe( + override rename(from: Path, to: Path): Observable { + return concat(this.exists(to), this.exists(from)).pipe( toArray(), switchMap(([existTo, existFrom]) => { if (!existFrom) { @@ -298,7 +309,7 @@ export class CordHost extends SimpleMemoryHost { // if will be by-passed because we just deleted the `from` path from files to overwrite. return concat( this.rename(from, to), - new Observable(x => { + new Observable((x) => { this._filesToOverwrite.add(to); x.complete(); }), @@ -310,9 +321,7 @@ export class CordHost extends SimpleMemoryHost { this._filesToOverwrite.add(to); // We need to delete the original and write the new one. - return this.read(from).pipe( - map(content => this._write(to, content)), - ); + return this.read(from).pipe(map((content) => this._write(to, content))); } const maybeTo1 = this._filesToRenameRevert.get(from); @@ -332,49 +341,50 @@ export class CordHost extends SimpleMemoryHost { return super.rename(from, to); } else { // Create a file with the same content. - return this._back.read(from).pipe( - switchMap(content => super.write(to, content)), - ); + return this._back.read(from).pipe(switchMap((content) => super.write(to, content))); } }), ); } - list(path: Path): Observable { - return concat( - super.list(path), - this._back.list(path), - ).pipe( + override list(path: Path): Observable { + return concat(super.list(path), this._back.list(path)).pipe( reduce((list: Set, curr: PathFragment[]) => { - curr.forEach(elem => list.add(elem)); + curr.forEach((elem) => list.add(elem)); return list; }, new Set()), - map(set => [...set]), + map((set) => [...set]), ); } - exists(path: Path): Observable { + override exists(path: Path): Observable { return this._exists(path) ? of(true) - : ((this.willDelete(path) || this.willRename(path)) ? of(false) : this._back.exists(path)); + : this.willDelete(path) || this.willRename(path) + ? of(false) + : this._back.exists(path); } - isDirectory(path: Path): Observable { + override isDirectory(path: Path): Observable { return this._exists(path) ? super.isDirectory(path) : this._back.isDirectory(path); } - isFile(path: Path): Observable { + override isFile(path: Path): Observable { return this._exists(path) ? super.isFile(path) - : ((this.willDelete(path) || this.willRename(path)) ? of(false) : this._back.isFile(path)); + : this.willDelete(path) || this.willRename(path) + ? of(false) + : this._back.isFile(path); } - stat(path: Path): Observable | null { + override stat(path: Path): Observable | null { return this._exists(path) ? super.stat(path) - : ((this.willDelete(path) || this.willRename(path)) ? of(null) : this._back.stat(path)); + : this.willDelete(path) || this.willRename(path) + ? of(null) + : this._back.stat(path); } - watch(path: Path, options?: HostWatchOptions) { + override watch(path: Path, options?: HostWatchOptions) { // Watching not supported. return null; } diff --git a/packages/angular_devkit/core/src/virtual-fs/host/record_spec.ts b/packages/angular_devkit/core/src/virtual-fs/host/record_spec.ts index 2500cc66bb5d..72cb494ee159 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/record_spec.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/record_spec.ts @@ -1,22 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-any no-big-function + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { path } from '../path'; import { fileBuffer } from './buffer'; import { CordHost } from './record'; import { test } from './test'; - describe('CordHost', () => { const TestHost = test.TestHost; const mutatingTestRecord = ['write', 'delete', 'rename']; - it('works (create)', done => { + it('works (create)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -27,7 +27,7 @@ describe('CordHost', () => { const target = new TestHost(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/blue` }, ]); @@ -36,7 +36,7 @@ describe('CordHost', () => { done(); }); - it('works (create -> create)', done => { + it('works (create -> create)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -48,7 +48,7 @@ describe('CordHost', () => { const target = new TestHost(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/blue` }, ]); @@ -58,7 +58,7 @@ describe('CordHost', () => { done(); }); - it('works (create -> delete)', done => { + it('works (create -> delete)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -70,15 +70,14 @@ describe('CordHost', () => { const target = new TestHost(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ - ]); + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([]); expect(target.$exists('/hello')).toBe(false); expect(target.$exists('/blue')).toBe(false); done(); }); - it('works (create -> rename)', done => { + it('works (create -> rename)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -91,7 +90,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/red` }, ]); @@ -102,7 +101,7 @@ describe('CordHost', () => { done(); }); - it('works (create -> rename (identity))', done => { + it('works (create -> rename (identity))', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -115,7 +114,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/blue` }, ]); @@ -125,7 +124,7 @@ describe('CordHost', () => { done(); }); - it('works (create -> rename -> rename)', done => { + it('works (create -> rename -> rename)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -139,7 +138,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/yellow` }, ]); @@ -151,7 +150,7 @@ describe('CordHost', () => { done(); }); - it('works (rename)', done => { + it('works (rename)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -163,7 +162,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'rename', from: path`/hello`, to: path`/blue` }, ]); @@ -173,7 +172,7 @@ describe('CordHost', () => { done(); }); - it('works (rename -> rename)', done => { + it('works (rename -> rename)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -186,7 +185,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'rename', from: path`/hello`, to: path`/red` }, ]); @@ -197,7 +196,7 @@ describe('CordHost', () => { done(); }); - it('works (rename -> create)', done => { + it('works (rename -> create)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -210,7 +209,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'rename', from: path`/hello`, to: path`/blue` }, { kind: 'write', path: path`/hello` }, ]); @@ -221,7 +220,7 @@ describe('CordHost', () => { done(); }); - it('works (overwrite)', done => { + it('works (overwrite)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -233,7 +232,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/hello` }, ]); @@ -243,7 +242,7 @@ describe('CordHost', () => { done(); }); - it('works (overwrite -> overwrite)', done => { + it('works (overwrite -> overwrite)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -256,7 +255,7 @@ describe('CordHost', () => { host.commit(target).subscribe(undefined, done.fail); // Check that there's only 1 write done. - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/hello` }, ]); @@ -266,7 +265,7 @@ describe('CordHost', () => { done(); }); - it('works (overwrite -> rename)', done => { + it('works (overwrite -> rename)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -278,7 +277,7 @@ describe('CordHost', () => { const target = base.clone(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'rename', from: path`/hello`, to: path`/blue` }, { kind: 'write', path: path`/blue` }, ]); @@ -290,7 +289,7 @@ describe('CordHost', () => { done(); }); - it('works (overwrite -> delete)', done => { + it('works (overwrite -> delete)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -302,7 +301,7 @@ describe('CordHost', () => { const target = base.clone(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'delete', path: path`/hello` }, ]); @@ -310,7 +309,7 @@ describe('CordHost', () => { done(); }); - it('works (rename -> overwrite)', done => { + it('works (rename -> overwrite)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -322,7 +321,7 @@ describe('CordHost', () => { const target = base.clone(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'rename', from: path`/hello`, to: path`/blue` }, { kind: 'write', path: path`/blue` }, ]); @@ -334,7 +333,7 @@ describe('CordHost', () => { done(); }); - it('works (delete)', done => { + it('works (delete)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -345,7 +344,7 @@ describe('CordHost', () => { const target = new TestHost(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'delete', path: path`/hello` }, ]); @@ -353,7 +352,7 @@ describe('CordHost', () => { done(); }); - it('works (delete -> create)', done => { + it('works (delete -> create)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -365,7 +364,7 @@ describe('CordHost', () => { const target = base.clone(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'write', path: path`/hello` }, ]); @@ -374,7 +373,7 @@ describe('CordHost', () => { done(); }); - it('works (rename -> delete)', done => { + it('works (rename -> delete)', (done) => { const base = new TestHost({ '/hello': 'world', }); @@ -386,7 +385,7 @@ describe('CordHost', () => { const target = base.clone(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'delete', path: path`/hello` }, ]); @@ -394,7 +393,7 @@ describe('CordHost', () => { done(); }); - it('works (delete -> rename)', done => { + it('works (delete -> rename)', (done) => { const base = new TestHost({ '/hello': 'world', '/blue': 'foo', @@ -406,7 +405,7 @@ describe('CordHost', () => { const target = base.clone(); host.commit(target).subscribe(undefined, done.fail); - expect(target.records.filter(x => mutatingTestRecord.includes(x.kind))).toEqual([ + expect(target.records.filter((x) => mutatingTestRecord.includes(x.kind))).toEqual([ { kind: 'delete', path: path`/hello` }, { kind: 'write', path: path`/blue` }, ]); @@ -429,7 +428,11 @@ describe('CordHost', () => { }); let error = false; - host.commit(target).subscribe(undefined, () => error = true, () => error = false); + host.commit(target).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); @@ -444,7 +447,11 @@ describe('CordHost', () => { const target = new TestHost({}); let error = false; - host.commit(target).subscribe(undefined, () => error = true, () => error = false); + host.commit(target).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); @@ -459,7 +466,11 @@ describe('CordHost', () => { const target = new TestHost({}); let error = false; - host.commit(target).subscribe(undefined, () => error = true, () => error = false); + host.commit(target).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); @@ -476,7 +487,11 @@ describe('CordHost', () => { }); let error = false; - host.commit(target).subscribe(undefined, () => error = true, () => error = false); + host.commit(target).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); @@ -487,20 +502,25 @@ describe('CordHost', () => { const host = new CordHost(base); let error = false; - host.write(path`/dir`, fileBuffer`beautiful world`) - .subscribe(undefined, () => error = true, () => error = false); + host.write(path`/dir`, fileBuffer`beautiful world`).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); it('errors (delete: not exist)', () => { - const base = new TestHost({ - }); + const base = new TestHost({}); const host = new CordHost(base); let error = false; - host.delete(path`/hello`) - .subscribe(undefined, () => error = true, () => error = false); + host.delete(path`/hello`).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); @@ -513,20 +533,25 @@ describe('CordHost', () => { const host = new CordHost(base); let error = false; - host.rename(path`/hello`, path`/blue`) - .subscribe(undefined, () => error = true, () => error = false); + host.rename(path`/hello`, path`/blue`).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); it('errors (rename: not exist)', () => { - const base = new TestHost({ - }); + const base = new TestHost({}); const host = new CordHost(base); let error = false; - host.rename(path`/hello`, path`/blue`) - .subscribe(undefined, () => error = true, () => error = false); + host.rename(path`/hello`, path`/blue`).subscribe( + undefined, + () => (error = true), + () => (error = false), + ); expect(error).toBe(true); }); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/resolver.ts b/packages/angular_devkit/core/src/virtual-fs/host/resolver.ts index 69c53d642e2f..054f678e3349 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/resolver.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/resolver.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable } from 'rxjs'; import { Path, PathFragment } from '../path'; import { @@ -25,7 +26,9 @@ export abstract class ResolverHost implements Host { constructor(protected _delegate: Host) {} - get capabilities(): HostCapabilities { return this._delegate.capabilities; } + get capabilities(): HostCapabilities { + return this._delegate.capabilities; + } write(path: Path, content: FileBuffer): Observable { return this._delegate.write(this._resolve(path), content); diff --git a/packages/angular_devkit/core/src/virtual-fs/host/safe.ts b/packages/angular_devkit/core/src/virtual-fs/host/safe.ts index fd4ef719261c..c39362033ab2 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/safe.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/safe.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { Path, PathFragment } from '../path'; @@ -26,31 +27,23 @@ export class SafeReadonlyHost implements ReadonlyHos } list(path: Path): Observable { - return this._delegate.list(path).pipe( - catchError(() => of([])), - ); + return this._delegate.list(path).pipe(catchError(() => of([]))); } exists(path: Path): Observable { return this._delegate.exists(path); } isDirectory(path: Path): Observable { - return this._delegate.isDirectory(path).pipe( - catchError(() => of(false)), - ); + return this._delegate.isDirectory(path).pipe(catchError(() => of(false))); } isFile(path: Path): Observable { - return this._delegate.isFile(path).pipe( - catchError(() => of(false)), - ); + return this._delegate.isFile(path).pipe(catchError(() => of(false))); } // Some hosts may not support stats. stat(path: Path): Observable | null> | null { const maybeStat = this._delegate.stat(path); - return maybeStat && maybeStat.pipe( - catchError(() => of(null)), - ); + return maybeStat && maybeStat.pipe(catchError(() => of(null))); } } diff --git a/packages/angular_devkit/core/src/virtual-fs/host/scoped.ts b/packages/angular_devkit/core/src/virtual-fs/host/scoped.ts index da6e884bc266..0dfe6a26ad51 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/scoped.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/scoped.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { NormalizedRoot, Path, join } from '../path'; import { Host } from './interface'; import { ResolverHost } from './resolver'; diff --git a/packages/angular_devkit/core/src/virtual-fs/host/sync.ts b/packages/angular_devkit/core/src/virtual-fs/host/sync.ts index cbc8629081cc..e3548b707034 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/sync.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/sync.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable } from 'rxjs'; import { BaseException } from '../../exception'; import { Path, PathFragment } from '../path'; @@ -18,9 +19,10 @@ import { Stats, } from './interface'; - export class SynchronousDelegateExpectedException extends BaseException { - constructor() { super(`Expected a synchronous delegate but got an asynchronous one.`); } + constructor() { + super(`Expected a synchronous delegate but got an asynchronous one.`); + } } /** @@ -40,9 +42,9 @@ export class SyncDelegateHost { // Perf note: this is not using an observer object to avoid a performance penalty in RxJS. // See https://github.com/ReactiveX/rxjs/pull/5646 for details. observable.subscribe( - (x: ResultT) => result = x, - (err: Error) => errorResult = err, - () => completed = true, + (x: ResultT) => (result = x), + (err: Error) => (errorResult = err), + () => (completed = true), ); if (errorResult !== undefined) { @@ -55,8 +57,8 @@ export class SyncDelegateHost { // The non-null operation is to work around `void` type. We don't allow to return undefined // but ResultT could be void, which is undefined in JavaScript, so this doesn't change the // behaviour. - // tslint:disable-next-line:no-non-null-assertion - return result !; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return result!; } get capabilities(): HostCapabilities { diff --git a/packages/angular_devkit/core/src/virtual-fs/host/test.ts b/packages/angular_devkit/core/src/virtual-fs/host/test.ts index 4e90c409e3c1..b8c407b79fd4 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/test.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/test.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable } from 'rxjs'; import { Path, PathFragment, join, normalize } from '../path'; import { fileBufferToString, stringToFileBuffer } from './buffer'; @@ -12,22 +13,31 @@ import { FileBuffer, HostWatchEvent, HostWatchOptions, Stats } from './interface import { SimpleMemoryHost, SimpleMemoryHostStats } from './memory'; import { SyncDelegateHost } from './sync'; +// eslint-disable-next-line @typescript-eslint/no-namespace export namespace test { - - export type TestLogRecord = { - kind: 'write' | 'read' | 'delete' | 'list' | 'exists' | 'isDirectory' | 'isFile' | 'stat' - | 'watch'; - path: Path; - } | { - kind: 'rename'; - from: Path; - to: Path; - }; - + export type TestLogRecord = + | { + kind: + | 'write' + | 'read' + | 'delete' + | 'list' + | 'exists' + | 'isDirectory' + | 'isFile' + | 'stat' + | 'watch'; + path: Path; + } + | { + kind: 'rename'; + from: Path; + to: Path; + }; export class TestHost extends SimpleMemoryHost { protected _records: TestLogRecord[] = []; - protected _sync: SyncDelegateHost<{}>|null = null; + protected _sync: SyncDelegateHost<{}> | null = null; constructor(map: { [path: string]: string } = {}) { super(); @@ -47,8 +57,9 @@ export namespace test { get files(): Path[] { const sync = this.sync; function _visit(p: Path): Path[] { - return sync.list(p) - .map(fragment => join(p, fragment)) + return sync + .list(p) + .map((fragment) => join(p, fragment)) .reduce((files, path) => { if (sync.isDirectory(path)) { return files.concat(_visit(path)); @@ -77,52 +88,52 @@ export namespace test { } // Override parents functions to keep a record of all operators that were done. - protected _write(path: Path, content: FileBuffer) { + protected override _write(path: Path, content: FileBuffer) { this._records.push({ kind: 'write', path }); return super._write(path, content); } - protected _read(path: Path) { + protected override _read(path: Path) { this._records.push({ kind: 'read', path }); return super._read(path); } - protected _delete(path: Path) { + protected override _delete(path: Path) { this._records.push({ kind: 'delete', path }); return super._delete(path); } - protected _rename(from: Path, to: Path) { + protected override _rename(from: Path, to: Path) { this._records.push({ kind: 'rename', from, to }); return super._rename(from, to); } - protected _list(path: Path): PathFragment[] { + protected override _list(path: Path): PathFragment[] { this._records.push({ kind: 'list', path }); return super._list(path); } - protected _exists(path: Path) { + protected override _exists(path: Path) { this._records.push({ kind: 'exists', path }); return super._exists(path); } - protected _isDirectory(path: Path) { + protected override _isDirectory(path: Path) { this._records.push({ kind: 'isDirectory', path }); return super._isDirectory(path); } - protected _isFile(path: Path) { + protected override _isFile(path: Path) { this._records.push({ kind: 'isFile', path }); return super._isFile(path); } - protected _stat(path: Path): Stats | null { + protected override _stat(path: Path): Stats | null { this._records.push({ kind: 'stat', path }); return super._stat(path); } - protected _watch(path: Path, options?: HostWatchOptions): Observable { + protected override _watch(path: Path, options?: HostWatchOptions): Observable { this._records.push({ kind: 'watch', path }); return super._watch(path, options); @@ -152,5 +163,4 @@ export namespace test { return super._isFile(normalize(path)); } } - } diff --git a/packages/angular_devkit/core/src/virtual-fs/host/test_spec.ts b/packages/angular_devkit/core/src/virtual-fs/host/test_spec.ts index 4e0cb296a155..c1930ce0d6a2 100644 --- a/packages/angular_devkit/core/src/virtual-fs/host/test_spec.ts +++ b/packages/angular_devkit/core/src/virtual-fs/host/test_spec.ts @@ -1,16 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { test } from './test'; +import { test } from './test'; // Yes, we realize the irony of testing a test host. describe('TestHost', () => { - it('can list files', () => { const files = { '/x/y/z': '', @@ -22,5 +21,4 @@ describe('TestHost', () => { const host = new test.TestHost(files); expect(host.files.sort() as string[]).toEqual(Object.keys(files).sort()); }); - }); diff --git a/packages/angular_devkit/core/src/virtual-fs/index.ts b/packages/angular_devkit/core/src/virtual-fs/index.ts index 988c3b40d449..2eb9af9490e3 100644 --- a/packages/angular_devkit/core/src/virtual-fs/index.ts +++ b/packages/angular_devkit/core/src/virtual-fs/index.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -export * from './path'; import * as virtualFs from './host/index'; + +export * from './path'; export { virtualFs }; diff --git a/packages/angular_devkit/core/src/virtual-fs/path.ts b/packages/angular_devkit/core/src/virtual-fs/path.ts index d5edd6da300a..563b8f5cbf0c 100644 --- a/packages/angular_devkit/core/src/virtual-fs/path.ts +++ b/packages/angular_devkit/core/src/virtual-fs/path.ts @@ -1,25 +1,30 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BaseException } from '../exception'; import { TemplateTag } from '../utils/literals'; - export class InvalidPathException extends BaseException { - constructor(path: string) { super(`Path ${JSON.stringify(path)} is invalid.`); } + constructor(path: string) { + super(`Path ${JSON.stringify(path)} is invalid.`); + } } export class PathMustBeAbsoluteException extends BaseException { - constructor(path: string) { super(`Path ${JSON.stringify(path)} must be absolute.`); } + constructor(path: string) { + super(`Path ${JSON.stringify(path)} must be absolute.`); + } } export class PathCannotBeFragmentException extends BaseException { - constructor(path: string) { super(`Path ${JSON.stringify(path)} cannot be made a fragment.`); } + constructor(path: string) { + super(`Path ${JSON.stringify(path)} cannot be made a fragment.`); + } } - /** * A Path recognized by most methods in the DevKit. */ @@ -34,20 +39,17 @@ export type PathFragment = Path & { __PRIVATE_DEVKIT_PATH_FRAGMENT: void; }; - /** * The Separator for normalized path. * @type {Path} */ export const NormalizedSep = '/' as Path; - /** * The root of a normalized path. * @type {Path} */ -export const NormalizedRoot = NormalizedSep as Path; - +export const NormalizedRoot = NormalizedSep; /** * Split a path into multiple path fragments. Each fragments except the last one will end with @@ -56,7 +58,7 @@ export const NormalizedRoot = NormalizedSep as Path; * @returns {Path[]} An array of path fragments. */ export function split(path: Path): PathFragment[] { - const fragments = path.split(NormalizedSep).map(x => fragment(x)); + const fragments = path.split(NormalizedSep).map((x) => fragment(x)); if (fragments[fragments.length - 1].length === 0) { fragments.pop(); } @@ -73,11 +75,10 @@ export function extname(path: Path): string { if (i < 1) { return ''; } else { - return base.substr(i); + return base.slice(i); } } - /** * Return the basename of the path, as a Path. See path.basename */ @@ -86,11 +87,10 @@ export function basename(path: Path): PathFragment { if (i == -1) { return fragment(path); } else { - return fragment(path.substr(path.lastIndexOf(NormalizedSep) + 1)); + return fragment(path.slice(path.lastIndexOf(NormalizedSep) + 1)); } } - /** * Return the dirname of the path, as a Path. See path.dirname */ @@ -102,10 +102,9 @@ export function dirname(path: Path): Path { const endIndex = index === 0 ? 1 : index; // case of file under root: '/file' - return normalize(path.substr(0, endIndex)); + return normalize(path.slice(0, endIndex)); } - /** * Join multiple paths together, and normalize the result. Accepts strings that will be * normalized as well (but the original must be a path). @@ -118,7 +117,6 @@ export function join(p1: Path, ...others: string[]): Path { } } - /** * Returns true if a path is absolute. */ @@ -126,7 +124,6 @@ export function isAbsolute(p: Path) { return p.startsWith(NormalizedSep); } - /** * Returns a path such that `join(from, relative(from, to)) == to`. * Both paths must be absolute, otherwise it does not make much sense. @@ -155,14 +152,16 @@ export function relative(from: Path, to: Path): Path { if (splitFrom.length == 0) { p = splitTo.join(NormalizedSep); } else { - p = splitFrom.map(_ => '..').concat(splitTo).join(NormalizedSep); + p = splitFrom + .map(() => '..') + .concat(splitTo) + .join(NormalizedSep); } } return normalize(p); } - /** * Returns a Path that is the resolution of p2, from p1. If p2 is absolute, it will return p2, * otherwise will join both p1 and p2. @@ -175,7 +174,6 @@ export function resolve(p1: Path, p2: Path) { } } - export function fragment(path: string): PathFragment { if (path.indexOf(NormalizedSep) != -1) { throw new PathCannotBeFragmentException(path); @@ -184,14 +182,12 @@ export function fragment(path: string): PathFragment { return path as PathFragment; } - /** * normalize() cache to reduce computation. For now this grows and we never flush it, but in the * future we might want to add a few cache flush to prevent this from growing too large. */ let normalizedCache = new Map(); - /** * Reset the cache. This is only useful for testing. * @private @@ -200,7 +196,6 @@ export function resetNormalizeCache() { normalizedCache = new Map(); } - /** * Normalize a string into a Path. This is the only mean to get a Path type from a string that * represents a system path. This method cache the results as real world paths tend to be @@ -225,7 +220,6 @@ export function normalize(path: string): Path { return maybePath; } - /** * The no cache version of the normalize() function. Used for benchmarking and testing. */ @@ -238,12 +232,12 @@ export function noCacheNormalize(path: string): Path { // Match absolute windows path. const original = path; - if (path.match(/^[A-Z]:[\/\\]/i)) { - path = '\\' + path[0] + '\\' + path.substr(3); + if (path.match(/^[A-Z]:[/\\]/i)) { + path = '\\' + path[0] + '\\' + path.slice(3); } // We convert Windows paths as well here. - const p = path.split(/[\/\\]/g); + const p = path.split(/[/\\]/g); let relative = false; let i = 1; @@ -273,7 +267,7 @@ export function noCacheNormalize(path: string): Path { } if (p.length == 1) { - return p[0] == '' ? NormalizedSep : '' as Path; + return p[0] == '' ? NormalizedSep : ('' as Path); } else { if (p[0] == '.') { p.shift(); @@ -283,12 +277,10 @@ export function noCacheNormalize(path: string): Path { } } - export const path: TemplateTag = (strings, ...values) => { return normalize(String.raw(strings, ...values)); }; - // Platform-specific paths. export type WindowsPath = string & { __PRIVATE_DEVKIT_WINDOWS_PATH: void; diff --git a/packages/angular_devkit/core/src/virtual-fs/path_spec.ts b/packages/angular_devkit/core/src/virtual-fs/path_spec.ts index 3e66387cd38e..034733b2ac05 100644 --- a/packages/angular_devkit/core/src/virtual-fs/path_spec.ts +++ b/packages/angular_devkit/core/src/virtual-fs/path_spec.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { InvalidPathException, Path, @@ -18,7 +19,6 @@ import { split, } from './path'; - describe('path', () => { it('normalize', () => { expect(normalize('////')).toBe('/'); @@ -58,12 +58,13 @@ describe('path', () => { expect(normalize('./a/../../a/b/c')).toBe('../a/b/c'); // Invalid use cases. - expect(() => normalize('/./././../././/')) - .toThrow(new InvalidPathException('/./././../././/')); - expect(() => normalize('/./././../././/../')) - .toThrow(new InvalidPathException('/./././../././/../')); - expect(() => normalize('/./././../././a/.')) - .toThrow(new InvalidPathException('/./././../././a/.')); + expect(() => normalize('/./././../././/')).toThrow(new InvalidPathException('/./././../././/')); + expect(() => normalize('/./././../././/../')).toThrow( + new InvalidPathException('/./././../././/../'), + ); + expect(() => normalize('/./././../././a/.')).toThrow( + new InvalidPathException('/./././../././a/.'), + ); expect(() => normalize('/c/../../')).toThrow(new InvalidPathException('/c/../../')); @@ -74,8 +75,7 @@ describe('path', () => { expect(normalize('C:\\a\\b\\c')).toBe('/C/a/b/c'); expect(normalize('c:\\a\\b\\c')).toBe('/c/a/b/c'); expect(normalize('A:\\a\\b\\c')).toBe('/A/a/b/c'); - expect(() => normalize('A:\\..\\..')) - .toThrow(new InvalidPathException('A:\\..\\..')); + expect(() => normalize('A:\\..\\..')).toThrow(new InvalidPathException('A:\\..\\..')); expect(normalize('\\.\\a\\b\\c')).toBe('/a/b/c'); expect(normalize('\\.\\a\\b\\.\\c')).toBe('/a/b/c'); expect(normalize('\\.\\a\\b\\d\\..\\c')).toBe('/a/b/c'); @@ -112,7 +112,7 @@ describe('path', () => { ]; for (const [input, result] of tests) { - const args = input.map(x => normalize(x)) as [Path, ...Path[]]; + const args = input.map((x) => normalize(x)) as [Path, ...Path[]]; it(`(${JSON.stringify(args)}) == "${result}"`, () => { expect(join(...args)).toBe(result); @@ -128,10 +128,7 @@ describe('path', () => { ['/a/b/c', '/a/b', '..'], ['/a/b/c', '/a/b/d', '../d'], ['/a/b/c/d/e', '/a/f/g', '../../../../f/g'], - [ - '/src/app/sub1/test1', '/src/app/sub2/test2', - '../../sub2/test2', - ], + ['/src/app/sub1/test1', '/src/app/sub2/test2', '../../sub2/test2'], ['/', '/a/b/c', 'a/b/c'], ['/a/b/c', '/d', '../../../d'], ]; @@ -168,5 +165,4 @@ describe('path', () => { expect(asWindowsPath(normalize('c:/b/'))).toBe('c:\\b'); expect(asWindowsPath(normalize('c:/b/c'))).toBe('c:\\b\\c'); }); - }); diff --git a/packages/angular_devkit/core/src/workspace/core.ts b/packages/angular_devkit/core/src/workspace/core.ts index ad5ae8be981f..44549806215e 100644 --- a/packages/angular_devkit/core/src/workspace/core.ts +++ b/packages/angular_devkit/core/src/workspace/core.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { basename, getSystemPath, join, normalize } from '../virtual-fs'; import { WorkspaceDefinition } from './definitions'; import { WorkspaceHost } from './host'; @@ -80,7 +81,10 @@ export async function readWorkspace( } } if (!found) { - throw new Error('Unable to locate a workspace file for workspace path.'); + throw new Error( + 'Unable to locate a workspace file for workspace path. Are you missing an `angular.json`' + + ' or `.angular.json` file?', + ); } } else if (format === undefined) { const filename = basename(normalize(path)); diff --git a/packages/angular_devkit/core/src/workspace/core_spec.ts b/packages/angular_devkit/core/src/workspace/core_spec.ts index f92170be2586..fc10c52ca71c 100644 --- a/packages/angular_devkit/core/src/workspace/core_spec.ts +++ b/packages/angular_devkit/core/src/workspace/core_spec.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-big-function + +/* eslint-disable @typescript-eslint/no-empty-function */ import { getSystemPath, join, normalize } from '../virtual-fs'; import { WorkspaceFormat, @@ -17,9 +18,8 @@ import { import { WorkspaceDefinition } from './definitions'; import { WorkspaceHost } from './host'; - describe('readWorkspace', () => { - it('attempts to read from specified file path [angular.json]', async (done) => { + it('attempts to read from specified file path [angular.json]', async () => { const requestedPath = '/path/to/workspace/angular.json'; const host: WorkspaceHost = { @@ -42,11 +42,9 @@ describe('readWorkspace', () => { }; await readWorkspace(requestedPath, host); - - done(); }); - it('attempts to read from specified file path [.angular.json]', async (done) => { + it('attempts to read from specified file path [.angular.json]', async () => { const requestedPath = '/path/to/workspace/.angular.json'; const host: WorkspaceHost = { @@ -69,11 +67,9 @@ describe('readWorkspace', () => { }; await readWorkspace(requestedPath, host); - - done(); }); - it('attempts to read from specified non-standard file path with format', async (done) => { + it('attempts to read from specified non-standard file path with format', async () => { const requestedPath = '/path/to/workspace/abc.json'; const host: WorkspaceHost = { @@ -96,11 +92,9 @@ describe('readWorkspace', () => { }; await readWorkspace(requestedPath, host, WorkspaceFormat.JSON); - - done(); }); - it('errors when reading from specified non-standard file path without format', async (done) => { + it('errors when reading from specified non-standard file path without format', async () => { const requestedPath = '/path/to/workspace/abc.json'; const host: WorkspaceHost = { @@ -122,17 +116,12 @@ describe('readWorkspace', () => { }, }; - try { - await readWorkspace(requestedPath, host); - fail(); - } catch (e) { - expect(e.message).toContain('Unable to determine format for workspace path'); - } - - done(); + await expectAsync(readWorkspace(requestedPath, host)).toBeRejectedWithError( + /Unable to determine format for workspace path/, + ); }); - it('errors when reading from specified file path with invalid specified format', async (done) => { + it('errors when reading from specified file path with invalid specified format', async () => { const requestedPath = '/path/to/workspace/angular.json'; const host: WorkspaceHost = { @@ -154,17 +143,12 @@ describe('readWorkspace', () => { }, }; - try { - await readWorkspace(requestedPath, host, 12 as WorkspaceFormat); - fail(); - } catch (e) { - expect(e.message).toContain('Unsupported workspace format'); - } - - done(); + await expectAsync( + readWorkspace(requestedPath, host, 12 as WorkspaceFormat), + ).toBeRejectedWithError(/Unsupported workspace format/); }); - it('attempts to find/read from directory path', async (done) => { + it('attempts to find/read from directory path', async () => { const requestedPath = getSystemPath(normalize('/path/to/workspace')); const expectedFile = getSystemPath(join(normalize(requestedPath), '.angular.json')); @@ -195,15 +179,15 @@ describe('readWorkspace', () => { await readWorkspace(requestedPath, host); isFileChecks.sort(); - expect(isFileChecks).toEqual([ - getSystemPath(join(normalize(requestedPath), 'angular.json')), - getSystemPath(join(normalize(requestedPath), '.angular.json')), - ].sort()); - - done(); + expect(isFileChecks).toEqual( + [ + getSystemPath(join(normalize(requestedPath), 'angular.json')), + getSystemPath(join(normalize(requestedPath), '.angular.json')), + ].sort(), + ); }); - it('attempts to find/read only files for specified format from directory path', async (done) => { + it('attempts to find/read only files for specified format from directory path', async () => { const requestedPath = '/path/to/workspace'; const isFileChecks: string[] = []; @@ -240,19 +224,13 @@ describe('readWorkspace', () => { } isFileChecks.sort(); - expect(isFileChecks).toEqual([ - getSystemPath(join(normalize(requestedPath), 'angular.json')), - ]); + expect(isFileChecks).toEqual([getSystemPath(join(normalize(requestedPath), 'angular.json'))]); readFileChecks.sort(); - expect(readFileChecks).toEqual([ - getSystemPath(join(normalize(requestedPath), 'angular.json')), - ]); - - done(); + expect(readFileChecks).toEqual([getSystemPath(join(normalize(requestedPath), 'angular.json'))]); }); - it('errors when no file found from specified directory path', async (done) => { + it('errors when no file found from specified directory path', async () => { const requestedPath = '/path/to/workspace'; const host: WorkspaceHost = { @@ -276,20 +254,14 @@ describe('readWorkspace', () => { }, }; - try { - await readWorkspace(requestedPath, host); - fail(); - } catch (e) { - expect(e.message).toContain('Unable to locate a workspace file'); - } - - done(); + await expectAsync(readWorkspace(requestedPath, host)).toBeRejectedWithError( + /Unable to locate a workspace file/, + ); }); - }); describe('writeWorkspace', () => { - it('attempts to write to specified file path', async (done) => { + it('attempts to write to specified file path', async () => { const requestedPath = '/path/to/workspace/angular.json'; let writtenPath: string | undefined; @@ -317,11 +289,9 @@ describe('writeWorkspace', () => { await writeWorkspace({} as WorkspaceDefinition, host, requestedPath, WorkspaceFormat.JSON); expect(writtenPath).toBe(requestedPath); - - done(); }); - it('errors when writing to specified file path with invalid specified format', async (done) => { + it('errors when writing to specified file path with invalid specified format', async () => { const requestedPath = '/path/to/workspace/angular.json'; const host: WorkspaceHost = { @@ -330,30 +300,27 @@ describe('writeWorkspace', () => { return ''; }, - async writeFile() { fail(); }, + async writeFile() { + fail(); + }, async isFile() { fail(); return false; }, async isDirectory() { - fail(); + fail(); - return false; + return false; }, }; - try { - await writeWorkspace({} as WorkspaceDefinition, host, requestedPath, 12 as WorkspaceFormat); - fail(); - } catch (e) { - expect(e.message).toContain('Unsupported workspace format'); - } - - done(); + await expectAsync( + writeWorkspace({} as WorkspaceDefinition, host, requestedPath, 12 as WorkspaceFormat), + ).toBeRejectedWithError(/Unsupported workspace format/); }); - it('errors when writing custom workspace without specified format', async (done) => { + it('errors when writing custom workspace without specified format', async () => { const requestedPath = '/path/to/workspace/angular.json'; const host: WorkspaceHost = { @@ -362,27 +329,23 @@ describe('writeWorkspace', () => { return ''; }, - async writeFile() { fail(); }, + async writeFile() { + fail(); + }, async isFile() { fail(); return false; }, async isDirectory() { - fail(); + fail(); - return false; + return false; }, }; - try { - await writeWorkspace({} as WorkspaceDefinition, host, requestedPath); - fail(); - } catch (e) { - expect(e.message).toContain('A format is required'); - } - - done(); + await expectAsync( + writeWorkspace({} as WorkspaceDefinition, host, requestedPath), + ).toBeRejectedWithError(/A format is required/); }); - }); diff --git a/packages/angular_devkit/core/src/workspace/definitions.ts b/packages/angular_devkit/core/src/workspace/definitions.ts index f27b1b5af1af..2df0d4d05ab5 100644 --- a/packages/angular_devkit/core/src/workspace/definitions.ts +++ b/packages/angular_devkit/core/src/workspace/definitions.ts @@ -1,15 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { JsonValue } from '../json'; export interface WorkspaceDefinition { readonly extensions: Record; - readonly projects: ProjectDefinitionCollection; } @@ -31,38 +31,33 @@ export interface TargetDefinition { export type DefinitionCollectionListener = ( name: string, - action: 'add' | 'remove' | 'replace', newValue: V | undefined, - oldValue: V | undefined, collection: DefinitionCollection, ) => void; class DefinitionCollection implements ReadonlyMap { private _map: Map; - constructor( - initial?: Record, - private _listener?: DefinitionCollectionListener, - ) { + constructor(initial?: Record, private _listener?: DefinitionCollectionListener) { this._map = new Map(initial && Object.entries(initial)); } delete(key: string): boolean { - const value = this._map.get(key); const result = this._map.delete(key); - if (result && value !== undefined && this._listener) { - this._listener(key, 'remove', undefined, value, this); + + if (result) { + this._listener?.(key, undefined, this); } return result; } set(key: string, value: V): this { - const existing = this.get(key); - this._map.set(key, value); + const updatedValue = value !== this.get(key); - if (this._listener) { - this._listener(key, existing !== undefined ? 'replace' : 'add', value, existing, this); + if (updatedValue) { + this._map.set(key, value); + this._listener?.(key, value, this); } return this; @@ -133,7 +128,6 @@ function isJsonValue(value: unknown): value is JsonValue { } export class ProjectDefinitionCollection extends DefinitionCollection { - constructor( initial?: Record, listener?: DefinitionCollectionListener, @@ -141,16 +135,14 @@ export class ProjectDefinitionCollection extends DefinitionCollection, - [key: string]: unknown, - }, - ): ProjectDefinition { + add(definition: { + name: string; + root: string; + sourceRoot?: string; + prefix?: string; + targets?: Record; + [key: string]: unknown; + }): ProjectDefinition { if (this.has(definition.name)) { throw new Error('Project name already exists.'); } @@ -195,7 +187,7 @@ export class ProjectDefinitionCollection extends DefinitionCollection { - constructor( initial?: Record, listener?: DefinitionCollectionListener, @@ -222,7 +212,7 @@ export class TargetDefinitionCollection extends DefinitionCollection { - it('can be created without initial values or a listener', () => { const collection = new ProjectDefinitionCollection(); @@ -54,7 +53,9 @@ describe('ProjectDefinitionCollection', () => { }); it('can be created with a listener', () => { - const listener = () => { fail('listener should not execute on initialization'); }; + const listener = () => { + fail('listener should not execute on initialization'); + }; const collection = new ProjectDefinitionCollection(undefined, listener); @@ -72,7 +73,9 @@ describe('ProjectDefinitionCollection', () => { builder: 'build-builder', }); - const listener = () => { fail('listener should not execute on initialization'); }; + const listener = () => { + fail('listener should not execute on initialization'); + }; const collection = new ProjectDefinitionCollection(initial, listener); @@ -96,27 +99,24 @@ describe('ProjectDefinitionCollection', () => { }); it('listens to an addition via set', () => { - const listener = (name: string, action: string) => { + const listener = (name: string, value?: ProjectDefinition) => { expect(name).toBe('my-app'); - expect(action).toBe('add'); + expect(value?.root).toBe('src/my-app'); }; const collection = new ProjectDefinitionCollection(undefined, listener); - collection.set( - 'my-app', - { root: 'src/my-app', extensions: {}, targets: new TargetDefinitionCollection() }, - ); + collection.set('my-app', { + root: 'src/my-app', + extensions: {}, + targets: new TargetDefinitionCollection(), + }); }); it('listens to an addition via add', () => { - const listener = (name: string, action: string, value?: ProjectDefinition) => { + const listener = (name: string, value?: ProjectDefinition) => { expect(name).toBe('my-app'); - expect(action).toBe('add'); - expect(value).not.toBeUndefined(); - if (value) { - expect(value.root).toBe('src/my-app'); - } + expect(value?.root).toBe('src/my-app'); }; const collection = new ProjectDefinitionCollection(undefined, listener); @@ -132,9 +132,9 @@ describe('ProjectDefinitionCollection', () => { 'my-app': { root: 'src/my-app', extensions: {}, targets: new TargetDefinitionCollection() }, }; - const listener = (name: string, action: string) => { + const listener = (name: string, value?: ProjectDefinition) => { expect(name).toBe('my-app'); - expect(action).toBe('remove'); + expect(value).toBeUndefined(); }; const collection = new ProjectDefinitionCollection(initial, listener); @@ -147,36 +147,22 @@ describe('ProjectDefinitionCollection', () => { 'my-app': { root: 'src/my-app', extensions: {}, targets: new TargetDefinitionCollection() }, }; - const listener = ( - name: string, - action: string, - newValue?: ProjectDefinition, - oldValue?: ProjectDefinition, - ) => { + const listener = (name: string, value?: ProjectDefinition) => { expect(name).toBe('my-app'); - expect(action).toBe('replace'); - expect(newValue).not.toBeUndefined(); - if (newValue) { - expect(newValue.root).toBe('src/my-app2'); - } - expect(oldValue).not.toBeUndefined(); - if (oldValue) { - expect(oldValue.root).toBe('src/my-app'); - } + expect(value?.root).toBe('src/my-app2'); }; const collection = new ProjectDefinitionCollection(initial, listener); - collection.set( - 'my-app', - { root: 'src/my-app2', extensions: {}, targets: new TargetDefinitionCollection() }, - ); + collection.set('my-app', { + root: 'src/my-app2', + extensions: {}, + targets: new TargetDefinitionCollection(), + }); }); - }); describe('TargetDefinitionCollection', () => { - it('can be created without initial values or a listener', () => { const collection = new TargetDefinitionCollection(); @@ -195,18 +181,16 @@ describe('TargetDefinitionCollection', () => { const build = collection.get('build'); expect(build).not.toBeUndefined(); - if (build) { - expect(build.builder).toBe('builder:build'); - } + expect(build?.builder).toBe('builder:build'); const test = collection.get('test'); expect(test).not.toBeUndefined(); - if (test) { - expect(test.builder).toBe('builder:test'); - } + expect(test?.builder).toBe('builder:test'); }); it('can be created with a listener', () => { - const listener = () => { fail('listener should not execute on initialization'); }; + const listener = () => { + fail('listener should not execute on initialization'); + }; const collection = new TargetDefinitionCollection(undefined, listener); @@ -219,46 +203,35 @@ describe('TargetDefinitionCollection', () => { 'test': { builder: 'builder:test' }, }; - const listener = () => { fail('listener should not execute on initialization'); }; + const listener = () => { + fail('listener should not execute on initialization'); + }; const collection = new TargetDefinitionCollection(initial, listener); expect(collection.size).toBe(2); const build = collection.get('build'); - expect(build).not.toBeUndefined(); - if (build) { - expect(build.builder).toBe('builder:build'); - } + expect(build?.builder).toBe('builder:build'); const test = collection.get('test'); - expect(test).not.toBeUndefined(); - if (test) { - expect(test.builder).toBe('builder:test'); - } + expect(test?.builder).toBe('builder:test'); }); it('listens to an addition via set', () => { - const listener = (name: string, action: string) => { + const listener = (name: string, value?: TargetDefinition) => { expect(name).toBe('build'); - expect(action).toBe('add'); + expect(value?.builder).toBe('builder:build'); }; const collection = new TargetDefinitionCollection(undefined, listener); - collection.set( - 'build', - { builder: 'builder:build' }, - ); + collection.set('build', { builder: 'builder:build' }); }); it('listens to an addition via add', () => { - const listener = (name: string, action: string, value?: TargetDefinition) => { + const listener = (name: string, value?: TargetDefinition) => { expect(name).toBe('build'); - expect(action).toBe('add'); - expect(value).not.toBeUndefined(); - if (value) { - expect(value.builder).toBe('builder:build'); - } + expect(value?.builder).toBe('builder:build'); }; const collection = new TargetDefinitionCollection(undefined, listener); @@ -274,9 +247,8 @@ describe('TargetDefinitionCollection', () => { 'build': { builder: 'builder:build' }, }; - const listener = (name: string, action: string) => { + const listener = (name: string, value?: TargetDefinition) => { expect(name).toBe('build'); - expect(action).toBe('remove'); }; const collection = new TargetDefinitionCollection(initial, listener); @@ -289,30 +261,13 @@ describe('TargetDefinitionCollection', () => { 'build': { builder: 'builder:build' }, }; - const listener = ( - name: string, - action: string, - newValue?: TargetDefinition, - oldValue?: TargetDefinition, - ) => { + const listener = (name: string, value?: TargetDefinition) => { expect(name).toBe('build'); - expect(action).toBe('replace'); - expect(newValue).not.toBeUndefined(); - if (newValue) { - expect(newValue.builder).toBe('builder:test'); - } - expect(oldValue).not.toBeUndefined(); - if (oldValue) { - expect(oldValue.builder).toBe('builder:build'); - } + expect(value?.builder).toBe('builder:test'); }; const collection = new TargetDefinitionCollection(initial, listener); - collection.set( - 'build', - { builder: 'builder:test' }, - ); + collection.set('build', { builder: 'builder:test' }); }); - }); diff --git a/packages/angular_devkit/core/src/workspace/host.ts b/packages/angular_devkit/core/src/workspace/host.ts index d9d75c0cf171..e0c84b789a76 100644 --- a/packages/angular_devkit/core/src/workspace/host.ts +++ b/packages/angular_devkit/core/src/workspace/host.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { normalize, virtualFs } from '../virtual-fs'; export interface WorkspaceHost { diff --git a/packages/angular_devkit/core/src/workspace/host_spec.ts b/packages/angular_devkit/core/src/workspace/host_spec.ts index 079885b60d25..2985e85bf360 100644 --- a/packages/angular_devkit/core/src/workspace/host_spec.ts +++ b/packages/angular_devkit/core/src/workspace/host_spec.ts @@ -1,14 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { test } from '../virtual-fs/host'; import { WorkspaceHost, createWorkspaceHost } from './host'; - describe('createWorkspaceHost', () => { let testHost: test.TestHost; let workspaceHost: WorkspaceHost; @@ -21,18 +21,16 @@ describe('createWorkspaceHost', () => { workspaceHost = createWorkspaceHost(testHost); }); - it('supports isFile', async (done) => { + it('supports isFile', async () => { expect(await workspaceHost.isFile('abc.txt')).toBeTruthy(); expect(await workspaceHost.isFile('foo/bar.json')).toBeTruthy(); expect(await workspaceHost.isFile('foo\\bar.json')).toBeTruthy(); expect(await workspaceHost.isFile('foo')).toBeFalsy(); expect(await workspaceHost.isFile('not.there')).toBeFalsy(); - - done(); }); - it('supports isDirectory', async (done) => { + it('supports isDirectory', async () => { expect(await workspaceHost.isDirectory('foo')).toBeTruthy(); expect(await workspaceHost.isDirectory('foo/')).toBeTruthy(); expect(await workspaceHost.isDirectory('foo\\')).toBeTruthy(); @@ -40,27 +38,16 @@ describe('createWorkspaceHost', () => { expect(await workspaceHost.isDirectory('abc.txt')).toBeFalsy(); expect(await workspaceHost.isDirectory('foo/bar.json')).toBeFalsy(); expect(await workspaceHost.isDirectory('not.there')).toBeFalsy(); - - done(); }); - it('supports readFile', async (done) => { + it('supports readFile', async () => { expect(await workspaceHost.readFile('abc.txt')).toBe('abcdefg'); - - done(); }); - it('supports writeFile', async (done) => { + it('supports writeFile', async () => { await workspaceHost.writeFile('newfile', 'baz'); - expect(testHost.files.sort() as string[]).toEqual([ - '/abc.txt', - '/foo/bar.json', - '/newfile', - ]); + expect(testHost.files.sort() as string[]).toEqual(['/abc.txt', '/foo/bar.json', '/newfile']); expect(testHost.$read('newfile')).toBe('baz'); - - done(); }); - }); diff --git a/packages/angular_devkit/core/src/workspace/index.ts b/packages/angular_devkit/core/src/workspace/index.ts index 88c1071ae2f7..395a411dbba3 100644 --- a/packages/angular_devkit/core/src/workspace/index.ts +++ b/packages/angular_devkit/core/src/workspace/index.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export * from './definitions'; export { WorkspaceHost, createWorkspaceHost } from './host'; export { WorkspaceFormat, readWorkspace, writeWorkspace } from './core'; diff --git a/packages/angular_devkit/core/src/workspace/json/metadata.ts b/packages/angular_devkit/core/src/workspace/json/metadata.ts index bc7d2c58bd5d..67fa6b11d64e 100644 --- a/packages/angular_devkit/core/src/workspace/json/metadata.ts +++ b/packages/angular_devkit/core/src/workspace/json/metadata.ts @@ -1,11 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { JsonAstArray, JsonAstKeyValue, JsonAstNode, JsonAstObject, JsonValue } from '../../json'; + +import { JSONPath, Node, findNodeAtLocation, getNodeValue } from 'jsonc-parser'; +import { JsonValue } from '../../json'; import { ProjectDefinition, TargetDefinition, WorkspaceDefinition } from '../definitions'; export const JsonWorkspaceSymbol = Symbol.for('@angular/core:workspace-json'); @@ -22,56 +24,63 @@ interface ChangeValues { targetcollection: Iterable<[string, TargetDefinition]>; } -export interface JsonChange { - // core collections can only be added as they are managed directly by _Collection_ objects - op: T extends 'json' | 'project' | 'target' ? 'add' | 'remove' | 'replace' : 'add'; - path: string; - node: JsonAstNode | JsonAstKeyValue; - value?: ChangeValues[T]; - type: T; +export interface JsonChange { + value?: unknown; + type?: keyof ChangeValues; + jsonPath: string[]; +} + +function escapeKey(key: string): string | number { + return key.replace('~', '~0').replace('/', '~1'); } export class JsonWorkspaceMetadata { - readonly changes: JsonChange[] = []; + readonly changes = new Map(); - constructor(readonly filePath: string, readonly ast: JsonAstObject, readonly raw: string) { } + hasLegacyTargetsName = true; + + constructor(readonly filePath: string, private readonly ast: Node, readonly raw: string) {} get hasChanges(): boolean { - return this.changes.length > 0; + return this.changes.size > 0; } get changeCount(): number { - return this.changes.length; + return this.changes.size; + } + + getNodeValueFromAst(path: JSONPath): unknown { + const node = findNodeAtLocation(this.ast, path); + + return node && getNodeValue(node); } - findChangesForPath(path: string): JsonChange[] { - return this.changes.filter(c => c.path === path); + findChangesForPath(path: string): JsonChange | undefined { + return this.changes.get(path); } addChange( - op: 'add' | 'remove' | 'replace', - path: string, - node: JsonAstArray | JsonAstObject | JsonAstKeyValue, - value?: ChangeValues[T], + jsonPath: string[], + value: ChangeValues[T] | undefined, type?: T, ): void { - // Remove redundant operations - if (op === 'remove' || op === 'replace') { - for (let i = this.changes.length - 1; i >= 0; --i) { - const currentPath = this.changes[i].path; - if (currentPath === path || currentPath.startsWith(path + '/')) { - if (op === 'replace' && currentPath === path && this.changes[i].op === 'add') { - op = 'add'; - } - this.changes.splice(i, 1); - } + let currentPath = ''; + for (let index = 0; index < jsonPath.length - 1; index++) { + currentPath = currentPath + '/' + escapeKey(jsonPath[index]); + if (this.changes.has(currentPath)) { + // Ignore changes on children as parent is updated. + return; } } - this.changes.push({ op, path, node, value, type: op === 'remove' || !type ? 'json' : type }); - } + const pathKey = '/' + jsonPath.map((k) => escapeKey(k)).join('/'); + for (const key of this.changes.keys()) { + if (key.startsWith(pathKey + '/')) { + // changes on the same or child paths are redundant. + this.changes.delete(key); + } + } - reset(): void { - this.changes.length = 0; + this.changes.set(pathKey, { jsonPath, type, value }); } } diff --git a/packages/angular_devkit/core/src/workspace/json/reader.ts b/packages/angular_devkit/core/src/workspace/json/reader.ts index 42b410f8e8dd..1b60ff10231a 100644 --- a/packages/angular_devkit/core/src/workspace/json/reader.ts +++ b/packages/angular_devkit/core/src/workspace/json/reader.ts @@ -1,18 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - JsonAstKeyValue, - JsonAstNode, - JsonAstObject, - JsonParseMode, - JsonValue, - parseJsonAst, -} from '../../json'; + +import { Node, findNodeAtLocation, getNodeValue, parseTree } from 'jsonc-parser'; +import { JsonValue, isJsonObject } from '../../json/utils'; import { DefinitionCollectionListener, ProjectDefinition, @@ -23,51 +18,76 @@ import { } from '../definitions'; import { WorkspaceHost } from '../host'; import { JsonWorkspaceMetadata, JsonWorkspaceSymbol } from './metadata'; -import { createVirtualAstObject, escapeKey } from './utilities'; +import { createVirtualAstObject } from './utilities'; + +const ANGULAR_WORKSPACE_EXTENSIONS = Object.freeze([ + 'cli', + 'defaultProject', + 'newProjectRoot', + 'schematics', +]); +const ANGULAR_PROJECT_EXTENSIONS = Object.freeze(['cli', 'schematics', 'projectType', 'i18n']); interface ParserContext { readonly host: WorkspaceHost; readonly metadata: JsonWorkspaceMetadata; readonly trackChanges: boolean; - error(message: string, node: JsonAstNode | JsonAstKeyValue): void; - warn(message: string, node: JsonAstNode | JsonAstKeyValue): void; + readonly unprefixedWorkspaceExtensions: ReadonlySet; + readonly unprefixedProjectExtensions: ReadonlySet; + error(message: string, node: JsonValue): void; + warn(message: string, node: JsonValue): void; +} + +export interface JsonWorkspaceOptions { + allowedProjectExtensions?: string[]; + allowedWorkspaceExtensions?: string[]; } export async function readJsonWorkspace( path: string, host: WorkspaceHost, + options: JsonWorkspaceOptions = {}, ): Promise { const raw = await host.readFile(path); - if (raw === undefined) { throw new Error('Unable to read workspace file.'); } - const ast = parseJsonAst(raw, JsonParseMode.Loose); - if (ast.kind !== 'object') { + const ast = parseTree(raw, undefined, { allowTrailingComma: true, disallowComments: false }); + if (ast?.type !== 'object' || !ast.children) { throw new Error('Invalid workspace file - expected JSON object.'); } // Version check - const versionNode = ast.properties.find(pair => pair.key.value === 'version'); + const versionNode = findNodeAtLocation(ast, ['version']); if (!versionNode) { throw new Error('Unknown format - version specifier not found.'); } - const formatVersion = versionNode.value.value; - if (formatVersion !== 1) { - throw new Error(`Invalid format version detected - Expected:[ 1 ] Found: [ ${formatVersion} ]`); + const version = versionNode.value; + if (version !== 1) { + throw new Error(`Invalid format version detected - Expected:[ 1 ] Found: [ ${version} ]`); } const context: ParserContext = { host, metadata: new JsonWorkspaceMetadata(path, ast, raw), trackChanges: true, + unprefixedWorkspaceExtensions: new Set([ + ...ANGULAR_WORKSPACE_EXTENSIONS, + ...(options.allowedWorkspaceExtensions ?? []), + ]), + unprefixedProjectExtensions: new Set([ + ...ANGULAR_PROJECT_EXTENSIONS, + ...(options.allowedProjectExtensions ?? []), + ]), error(message, _node) { // TODO: Diagnostic reporting support throw new Error(message); }, - warn(_message, _node) { + warn(message, _node) { // TODO: Diagnostic reporting support + // eslint-disable-next-line no-console + console.warn(message); }, }; @@ -76,53 +96,42 @@ export async function readJsonWorkspace( return workspace; } -const specialWorkspaceExtensions = ['cli', 'defaultProject', 'newProjectRoot', 'schematics']; - -const specialProjectExtensions = ['cli', 'schematics', 'projectType']; - -function parseWorkspace(workspaceNode: JsonAstObject, context: ParserContext): WorkspaceDefinition { +function parseWorkspace(workspaceNode: Node, context: ParserContext): WorkspaceDefinition { const jsonMetadata = context.metadata; let projects; - let projectsNode: JsonAstObject | undefined; let extensions: Record | undefined; if (!context.trackChanges) { extensions = Object.create(null); } - for (const { key, value } of workspaceNode.properties) { - const name = key.value; - + // TODO: `getNodeValue` - looks potentially expensive since it walks the whole tree and instantiates the full object structure each time. + // Might be something to look at moving forward to optimize. + const workspaceNodeValue = getNodeValue(workspaceNode); + for (const [name, value] of Object.entries(workspaceNodeValue)) { if (name === '$schema' || name === 'version') { // skip } else if (name === 'projects') { - if (value.kind !== 'object') { + const nodes = findNodeAtLocation(workspaceNode, ['projects']); + if (!isJsonObject(value) || !nodes) { context.error('Invalid "projects" field found; expected an object.', value); continue; } - projectsNode = value; - projects = parseProjectsObject(value, context); + projects = parseProjectsObject(nodes, context); } else { - if (!specialWorkspaceExtensions.includes(name) && !/^[a-z]{1,3}-.*/.test(name)) { - context.warn(`Project extension with invalid name found.`, key); + if (!context.unprefixedWorkspaceExtensions.has(name) && !/^[a-z]{1,3}-.*/.test(name)) { + context.warn(`Workspace extension with invalid name (${name}) found.`, name); } if (extensions) { - extensions[name] = value.value; + extensions[name] = value; } } } let collectionListener: DefinitionCollectionListener | undefined; - if (context.trackChanges && projectsNode) { - const parentNode = projectsNode; - collectionListener = (name, action, newValue) => { - jsonMetadata.addChange( - action, - `/projects/${escapeKey(name)}`, - parentNode, - newValue, - 'project', - ); + if (context.trackChanges) { + collectionListener = (name, newValue) => { + jsonMetadata.addChange(['projects', name], newValue, 'project'); }; } @@ -134,30 +143,30 @@ function parseWorkspace(workspaceNode: JsonAstObject, context: ParserContext): W // If not tracking changes the `extensions` variable will contain the parsed // values. Otherwise the extensions are tracked via a virtual AST object. extensions: - extensions || - createVirtualAstObject(workspaceNode, { + extensions ?? + createVirtualAstObject(workspaceNodeValue, { exclude: ['$schema', 'version', 'projects'], - listener(op, path, node, value) { - jsonMetadata.addChange(op, path, node, value); + listener(path, value) { + jsonMetadata.addChange(path, value); }, }), } as WorkspaceDefinition; } function parseProjectsObject( - projectsNode: JsonAstObject, + projectsNode: Node, context: ParserContext, ): Record { const projects: Record = Object.create(null); - for (const { key, value } of projectsNode.properties) { - if (value.kind !== 'object') { + for (const [name, value] of Object.entries(getNodeValue(projectsNode))) { + const nodes = findNodeAtLocation(projectsNode, [name]); + if (!isJsonObject(value) || !nodes) { context.warn('Skipping invalid project value; expected an object.', value); continue; } - const name = key.value; - projects[name] = parseProject(name, value, context); + projects[name] = parseProject(name, nodes, context); } return projects; @@ -165,12 +174,12 @@ function parseProjectsObject( function parseProject( projectName: string, - projectNode: JsonAstObject, + projectNode: Node, context: ParserContext, ): ProjectDefinition { const jsonMetadata = context.metadata; let targets; - let targetsNode: JsonAstObject | undefined; + let hasTargets = false; let extensions: Record | undefined; let properties: Record<'root' | 'sourceRoot' | 'prefix', string> | undefined; if (!context.trackChanges) { @@ -179,34 +188,43 @@ function parseProject( properties = Object.create(null); } - for (const { key, value } of projectNode.properties) { - const name = key.value; + const projectNodeValue = getNodeValue(projectNode); + if (!('root' in projectNodeValue)) { + throw new Error(`Project "${projectName}" is missing a required property "root".`); + } + + for (const [name, value] of Object.entries(projectNodeValue)) { switch (name) { case 'targets': case 'architect': - if (value.kind !== 'object') { + const nodes = findNodeAtLocation(projectNode, [name]); + if (!isJsonObject(value) || !nodes) { context.error(`Invalid "${name}" field found; expected an object.`, value); break; } - targetsNode = value; - targets = parseTargetsObject(projectName, value, context); + hasTargets = true; + targets = parseTargetsObject(projectName, nodes, context); + jsonMetadata.hasLegacyTargetsName = name === 'architect'; break; case 'prefix': case 'root': case 'sourceRoot': - if (value.kind !== 'string') { + if (typeof value !== 'string') { context.warn(`Project property "${name}" should be a string.`, value); } if (properties) { - properties[name] = value.value as string; + properties[name] = value as string; } break; default: - if (!specialProjectExtensions.includes(name) && !/^[a-z]{1,3}-.*/.test(name)) { - context.warn(`Project extension with invalid name found.`, key); + if (!context.unprefixedProjectExtensions.has(name) && !/^[a-z]{1,3}-.*/.test(name)) { + context.warn( + `Project '${projectName}' contains extension with invalid name (${name}).`, + name, + ); } if (extensions) { - extensions[name] = value.value; + extensions[name] = value; } break; } @@ -214,34 +232,17 @@ function parseProject( let collectionListener: DefinitionCollectionListener | undefined; if (context.trackChanges) { - if (targetsNode) { - const parentNode = targetsNode; - collectionListener = (name, action, newValue) => { + collectionListener = (name, newValue, collection) => { + if (hasTargets) { + jsonMetadata.addChange(['projects', projectName, 'targets', name], newValue, 'target'); + } else { jsonMetadata.addChange( - action, - `/projects/${projectName}/targets/${escapeKey(name)}`, - parentNode, - newValue, - 'target', - ); - }; - } else { - let added = false; - collectionListener = (_name, action, _new, _old, collection) => { - if (added || action !== 'add') { - return; - } - - jsonMetadata.addChange( - 'add', - `/projects/${projectName}/targets`, - projectNode, + ['projects', projectName, 'targets'], collection, 'targetcollection', ); - added = true; - }; - } + } + }; } const base = { @@ -249,63 +250,53 @@ function parseProject( // If not tracking changes the `extensions` variable will contain the parsed // values. Otherwise the extensions are tracked via a virtual AST object. extensions: - extensions || - createVirtualAstObject(projectNode, { + extensions ?? + createVirtualAstObject(projectNodeValue, { exclude: ['architect', 'prefix', 'root', 'sourceRoot', 'targets'], - listener(op, path, node, value) { - jsonMetadata.addChange(op, `/projects/${projectName}${path}`, node, value); + listener(path, value) { + jsonMetadata.addChange(['projects', projectName, ...path], value); }, }), }; - let project: ProjectDefinition; - if (context.trackChanges) { - project = createVirtualAstObject(projectNode, { - base, - include: ['prefix', 'root', 'sourceRoot'], - listener(op, path, node, value) { - jsonMetadata.addChange(op, `/projects/${projectName}${path}`, node, value); + const baseKeys = new Set(Object.keys(base)); + const project = + properties ?? + createVirtualAstObject(projectNodeValue, { + include: ['prefix', 'root', 'sourceRoot', ...baseKeys], + listener(path, value) { + if (!baseKeys.has(path[0])) { + jsonMetadata.addChange(['projects', projectName, ...path], value); + } }, }); - } else { - project = { - ...base, - ...properties, - } as ProjectDefinition; - } - return project; + return Object.assign(project, base) as ProjectDefinition; } function parseTargetsObject( projectName: string, - targetsNode: JsonAstObject, + targetsNode: Node, context: ParserContext, ): Record { const jsonMetadata = context.metadata; const targets: Record = Object.create(null); - for (const { key, value } of targetsNode.properties) { - if (value.kind !== 'object') { + for (const [name, value] of Object.entries(getNodeValue(targetsNode))) { + if (!isJsonObject(value)) { context.warn('Skipping invalid target value; expected an object.', value); continue; } - const name = key.value; if (context.trackChanges) { targets[name] = createVirtualAstObject(value, { - include: [ 'builder', 'options', 'configurations', 'defaultConfiguration' ], - listener(op, path, node, value) { - jsonMetadata.addChange( - op, - `/projects/${projectName}/targets/${name}${path}`, - node, - value, - ); + include: ['builder', 'options', 'configurations', 'defaultConfiguration'], + listener(path, value) { + jsonMetadata.addChange(['projects', projectName, 'targets', name, ...path], value); }, }); } else { - targets[name] = value.value as unknown as TargetDefinition; + targets[name] = value as unknown as TargetDefinition; } } diff --git a/packages/angular_devkit/core/src/workspace/json/reader_spec.ts b/packages/angular_devkit/core/src/workspace/json/reader_spec.ts index b6ac75b95971..5b80cc50d92b 100644 --- a/packages/angular_devkit/core/src/workspace/json/reader_spec.ts +++ b/packages/angular_devkit/core/src/workspace/json/reader_spec.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-non-null-assertion no-big-function + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { readFileSync } from 'fs'; import { JsonObject } from '../../json'; import { stripIndent } from '../../utils/literals'; @@ -51,7 +52,7 @@ function createTestHost(content: string, onWrite?: (path: string, data: string) function getMetadata(workspace: WorkspaceDefinition): JsonWorkspaceMetadata { const metadata = (workspace as JsonWorkspaceDefinition)[JsonWorkspaceSymbol]; - expect(metadata).toBeDefined('JSON metadata not found. Invalid workspace definition returned.'); + expect(metadata).toBeDefined(); return metadata; } @@ -89,7 +90,8 @@ describe('readJsonWorkpace Parsing', () => { it(`doesn't remove falsy values when using the spread operator`, async () => { const host = createTestHost(representativeFile); const workspace = await readJsonWorkspace('', host); - const prodConfig = workspace.projects.get('my-app')!.targets.get('build')!.configurations!.production!; + const prodConfig = workspace.projects.get('my-app')!.targets.get('build')!.configurations! + .production!; expect({ ...prodConfig }).toEqual(prodConfig); }); @@ -99,13 +101,13 @@ describe('readJsonWorkpace Parsing', () => { const workspace = await readJsonWorkspace('', host); expect(workspace.extensions['newProjectRoot']).toBe('projects'); - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((workspace as any)['newProjectRoot']).toBeUndefined(); expect(workspace.projects.get('my-app')!.extensions['schematics']).toEqual({ '@schematics/angular:component': { styleext: 'scss' }, }); - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((workspace.projects.get('my-app') as any)['schematics']).toBeUndefined(); }); @@ -118,12 +120,9 @@ describe('readJsonWorkpace Parsing', () => { } `); - try { - await readJsonWorkspace('', host); - fail(); - } catch (e) { - expect(e.message).toContain('Invalid format version detected'); - } + await expectAsync(readJsonWorkspace('', host)).toBeRejectedWithError( + /Invalid format version detected/, + ); }); it('errors on missing version', async () => { @@ -134,12 +133,24 @@ describe('readJsonWorkpace Parsing', () => { } `); - try { - await readJsonWorkspace('', host); - fail(); - } catch (e) { - expect(e.message).toContain('version specifier not found'); - } + await expectAsync(readJsonWorkspace('', host)).toBeRejectedWithError( + /version specifier not found/, + ); + }); + + it('warns on missing root property in a project', async () => { + const host = createTestHost(stripIndent` + { + "version": 1, + "projects": { + "foo": {} + } + } + `); + + await expectAsync(readJsonWorkspace('', host)).toBeRejectedWithError( + /Project "foo" is missing a required property "root"/, + ); }); }); @@ -157,10 +168,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/x-baz')[0]; + const change = metadata.findChangesForPath('/x-baz'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toBe(101); } }); @@ -182,10 +192,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/x-baz')[0]; + const change = metadata.findChangesForPath('/x-baz'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ a: 1, b: 3, c: { d: 'abc' } }); } }); @@ -200,18 +209,23 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(workspace.extensions['x-baz']).toEqual({ a: 1, b: 2, c: { d: 'abc' } }); Object.assign(value, { x: 9, y: 8, z: 7 }); - expect(workspace.extensions['x-baz']) - .toEqual({ a: 1, b: 2, c: { d: 'abc' }, x: 9, y: 8, z: 7 }); + expect(workspace.extensions['x-baz']).toEqual({ + a: 1, + b: 2, + c: { d: 'abc' }, + x: 9, + y: 8, + z: 7, + }); const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/x-baz')[0]; + const change = metadata.findChangesForPath('/x-baz'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ a: 1, b: 2, c: { d: 'abc' }, x: 9, y: 8, z: 7 }); } }); @@ -226,18 +240,23 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(workspace.extensions['x-baz']).toEqual({ a: 1, b: 2, c: { d: 'abc' } }); workspace.extensions['x-baz'] = Object.assign(value, { x: 9, y: 8, z: 7 }); - expect(workspace.extensions['x-baz']) - .toEqual({ a: 1, b: 2, c: { d: 'abc' }, x: 9, y: 8, z: 7 }); + expect(workspace.extensions['x-baz']).toEqual({ + a: 1, + b: 2, + c: { d: 'abc' }, + x: 9, + y: 8, + z: 7, + }); const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/x-baz')[0]; + const change = metadata.findChangesForPath('/x-baz'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ a: 1, b: 2, c: { d: 'abc' }, x: 9, y: 8, z: 7 }); } }); @@ -252,18 +271,23 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(workspace.extensions['x-baz']).toEqual({ a: 1, b: 2, c: { d: 'abc' } }); workspace.extensions['x-baz'] = { ...value, ...{ x: 9, y: 8 }, z: 7 }; - expect(workspace.extensions['x-baz']) - .toEqual({ a: 1, b: 2, c: { d: 'abc' }, x: 9, y: 8, z: 7 }); + expect(workspace.extensions['x-baz']).toEqual({ + a: 1, + b: 2, + c: { d: 'abc' }, + x: 9, + y: 8, + z: 7, + }); const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/x-baz')[0]; + const change = metadata.findChangesForPath('/x-baz'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ a: 1, b: 2, c: { d: 'abc' }, x: 9, y: 8, z: 7 }); } }); @@ -274,20 +298,25 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { const workspace = await readJsonWorkspace('', host); workspace.extensions['x-foo'] = { - ...workspace.extensions['x-foo'] as JsonObject, - ...{ x: 9, y: 8 }, z: 7 }; - expect(workspace.extensions['x-foo']) - .toEqual({ is: ['good', 'great', 'awesome'], x: 9, y: 8, z: 7 }); + ...(workspace.extensions['x-foo'] as JsonObject), + ...{ x: 9, y: 8 }, + z: 7, + }; + expect(workspace.extensions['x-foo']).toEqual({ + is: ['good', 'great', 'awesome'], + x: 9, + y: 8, + z: 7, + }); const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/x-foo')[0]; + const change = metadata.findChangesForPath('/x-foo'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('replace'); expect(change.value).toEqual({ is: ['good', 'great', 'awesome'], x: 9, y: 8, z: 7 }); } }); @@ -297,37 +326,34 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { const workspace = await readJsonWorkspace('', host); - Object.assign( - workspace.extensions['x-foo'], - { x: 9, y: 8 }, - { z: 7 }, - ); - expect(workspace.extensions['x-foo']) - .toEqual({ is: ['good', 'great', 'awesome'], x: 9, y: 8, z: 7 }); + Object.assign(workspace.extensions['x-foo']!, { x: 9, y: 8 }, { z: 7 }); + expect(workspace.extensions['x-foo']).toEqual({ + is: ['good', 'great', 'awesome'], + x: 9, + y: 8, + z: 7, + }); const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(3); - let change = metadata.findChangesForPath('/x-foo/x')[0]; + let change = metadata.findChangesForPath('/x-foo/x'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(9); } - change = metadata.findChangesForPath('/x-foo/y')[0]; + change = metadata.findChangesForPath('/x-foo/y'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(8); } - change = metadata.findChangesForPath('/x-foo/z')[0]; + change = metadata.findChangesForPath('/x-foo/z'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(7); } }); @@ -338,36 +364,37 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { const workspace = await readJsonWorkspace('', host); workspace.extensions['x-foo'] = Object.assign( - workspace.extensions['x-foo'], + workspace.extensions['x-foo']!, { x: 9, y: 8 }, { z: 7 }, ); - expect(workspace.extensions['x-foo']) - .toEqual({ is: ['good', 'great', 'awesome'], x: 9, y: 8, z: 7 }); + expect(workspace.extensions['x-foo']).toEqual({ + is: ['good', 'great', 'awesome'], + x: 9, + y: 8, + z: 7, + }); const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(3); - let change = metadata.findChangesForPath('/x-foo/x')[0]; + let change = metadata.findChangesForPath('/x-foo/x'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(9); } - change = metadata.findChangesForPath('/x-foo/y')[0]; + change = metadata.findChangesForPath('/x-foo/y'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(8); } - change = metadata.findChangesForPath('/x-foo/z')[0]; + change = metadata.findChangesForPath('/x-foo/z'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(7); } }); @@ -391,10 +418,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - let change = metadata.findChangesForPath('/schematics2')[0]; + let change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'abc' } }); } @@ -403,11 +429,8 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(2); - change = metadata.findChangesForPath('/schematics')[0]; + change = metadata.findChangesForPath('/schematics'); expect(change).not.toBeUndefined(); - if (change) { - expect(change.op).toBe('remove'); - } }); it('tracks moving and modifying an existing extension object', async () => { @@ -429,10 +452,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - let change = metadata.findChangesForPath('/schematics2')[0]; + let change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'abc' } }); } @@ -444,16 +466,12 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(2); - change = metadata.findChangesForPath('/schematics')[0]; + change = metadata.findChangesForPath('/schematics'); expect(change).not.toBeUndefined(); - if (change) { - expect(change.op).toBe('remove'); - } - change = metadata.findChangesForPath('/schematics2')[0]; + change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'xyz' } }); } }); @@ -477,10 +495,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - let change = metadata.findChangesForPath('/schematics2')[0]; + let change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'abc' } }); } @@ -491,17 +508,15 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(2); - change = metadata.findChangesForPath('/schematics/@angular~1schematics:component')[0]; + change = metadata.findChangesForPath('/schematics/@angular~1schematics:component'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('replace'); expect(change.value).toEqual({ prefix: 'xyz' }); } - change = metadata.findChangesForPath('/schematics2')[0]; + change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'xyz' } }); } }); @@ -525,10 +540,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - let change = metadata.findChangesForPath('/schematics2')[0]; + let change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'abc' } }); } @@ -539,17 +553,15 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(2); - change = metadata.findChangesForPath('/schematics/@angular~1schematics:component')[0]; + change = metadata.findChangesForPath('/schematics/@angular~1schematics:component'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('replace'); expect(change.value).toEqual({ prefix: 'xyz' }); } - change = metadata.findChangesForPath('/schematics2')[0]; + change = metadata.findChangesForPath('/schematics2'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual({ '@angular/schematics:component': { prefix: 'xyz' } }); } @@ -558,18 +570,12 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(2); - change = metadata.findChangesForPath('/schematics')[0]; + change = metadata.findChangesForPath('/schematics'); expect(change).not.toBeUndefined(); - if (change) { - expect(change.op).toBe('remove'); - } const orderedChanges = Array.from(metadata.changes.values()); change = orderedChanges[1]; expect(change).not.toBeUndefined(); - if (change) { - expect(change.op).toBe('remove'); - } }); it('tracks project additions with set', async () => { @@ -595,10 +601,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/projects/new-app')[0]; + const change = metadata.findChangesForPath('/projects/new-app'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(jasmine.objectContaining({ root: 'src' })); } }); @@ -625,10 +630,9 @@ describe('JSON WorkspaceDefinition Tracks Workspace Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/projects/new-app')[0]; + const change = metadata.findChangesForPath('/projects/new-app'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toEqual(jasmine.objectContaining({ root: 'src' })); } }); @@ -648,16 +652,14 @@ describe('JSON ProjectDefinition Tracks Project Changes', () => { project.prefix = 'bar'; expect(project.prefix).toBe('bar'); - const metadata = getMetadata(workspace); expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/projects/my-app/prefix')[0]; + const change = metadata.findChangesForPath('/projects/my-app/prefix'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('replace'); expect(change.value).toBe('bar'); } }); @@ -682,10 +684,9 @@ describe('JSON ProjectDefinition Tracks Project Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/projects/my-app/sourceRoot')[0]; + const change = metadata.findChangesForPath('/projects/my-app/sourceRoot'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toBe('xyz'); } }); @@ -710,10 +711,9 @@ describe('JSON ProjectDefinition Tracks Project Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/projects/my-app/abc-option')[0]; + const change = metadata.findChangesForPath('/projects/my-app/abc-option'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.value).toBe('valueA'); } }); @@ -759,10 +759,9 @@ describe('JSON ProjectDefinition Tracks Project Changes', () => { expect(metadata.hasChanges).toBeTruthy(); expect(metadata.changeCount).toBe(1); - const change = metadata.findChangesForPath('/projects/p1/targets')[0]; + const change = metadata.findChangesForPath('/projects/p1/targets'); expect(change).not.toBeUndefined(); if (change) { - expect(change.op).toBe('add'); expect(change.type).toBe('targetcollection'); } }); diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayEmpty.json b/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayEmpty.json index 83cfeda3dda7..986639153289 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayEmpty.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayEmpty.json @@ -11,5 +11,5 @@ "is": ["good", "great", "awesome"] }, "x-bar": 5, - "x-array": [] + "x-array": [], } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayPush.json b/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayPush.json index 84b9aeca6359..12a192f8a858 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayPush.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/AddArrayPush.json @@ -13,5 +13,5 @@ "x-bar": 5, "x-array": [ "value" - ] + ], } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject1.json b/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject1.json index ee1c8db01857..7acd961c8c50 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject1.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject1.json @@ -6,14 +6,14 @@ "prefix": "abc" } }, - "x-baz": 10, + "x-baz": 1, "x-foo": { "is": ["good", "great", "awesome"] }, "x-bar": 5, "projects": { "new": { - "root": "src" + "root": "src7" } - } + }, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/AddProjectWithTargets.json b/packages/angular_devkit/core/src/workspace/json/test/cases/AddProjectWithTargets.json index 26eadbabf914..d001bfb5d6c8 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/AddProjectWithTargets.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/AddProjectWithTargets.json @@ -6,7 +6,7 @@ "prefix": "abc" } }, - "x-baz": 10, + "x-baz": 1, "x-foo": { "is": ["good", "great", "awesome"] }, @@ -14,7 +14,7 @@ "projects": { "new": { "root": "src", - "targets": { + "architect": { "build": { "builder": "build-builder", "options": { @@ -29,5 +29,5 @@ } } } - } + }, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInner.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInner.json index 111e8e538097..96d7743dde4a 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInner.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInner.json @@ -8,7 +8,10 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "awesome"] + "is": [ + "good", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInnerAdd.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInnerAdd.json index c6c4ada21046..41f9979607d0 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInnerAdd.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteInnerAdd.json @@ -8,7 +8,11 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "new", "awesome"] + "is": [ + "good", + "new", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLast.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLast.json index a96c3b8338d4..521fa740d0bd 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLast.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLast.json @@ -8,7 +8,10 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great"] + "is": [ + "good", + "great" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLastAdd.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLastAdd.json index 6ccb0498b16a..9c27359090c8 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLastAdd.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteLastAdd.json @@ -8,7 +8,11 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great", "new"] + "is": [ + "good", + "great", + "new" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteZero.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteZero.json index 2c2603005c6e..fc0fd058d98f 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteZero.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayDeleteZero.json @@ -8,7 +8,10 @@ }, "x-baz": 1, "x-foo": { - "is": ["great", "awesome"] + "is": [ + "great", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexInner.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexInner.json index 94fecec93fd8..ac769c3f9444 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexInner.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexInner.json @@ -8,7 +8,11 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "value", "awesome"] + "is": [ + "good", + "value", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexLast.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexLast.json index b54fe1e5b1c6..083e92bd9853 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexLast.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexLast.json @@ -8,7 +8,11 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great", "value"] + "is": [ + "good", + "great", + "value" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexZero.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexZero.json index 99ef79d5d0d5..e384556d2fac 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexZero.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayIndexZero.json @@ -8,7 +8,11 @@ }, "x-baz": 1, "x-foo": { - "is": ["value", "great", "awesome"] + "is": [ + "value", + "great", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPop.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPop.json index a96c3b8338d4..521fa740d0bd 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPop.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPop.json @@ -8,7 +8,10 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great"] + "is": [ + "good", + "great" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPush.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPush.json index c7a8affc6343..8d9105f5c1d2 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPush.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayPush.json @@ -8,7 +8,12 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great", "awesome", "value"] + "is": [ + "good", + "great", + "awesome", + "value" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayShift.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayShift.json index 2c2603005c6e..fc0fd058d98f 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayShift.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayShift.json @@ -8,7 +8,10 @@ }, "x-baz": 1, "x-foo": { - "is": ["great", "awesome"] + "is": [ + "great", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySort.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySort.json index 655912a26e3d..3a98d0c49080 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySort.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySort.json @@ -8,7 +8,11 @@ }, "x-baz": 1, "x-foo": { - "is": ["awesome", "good", "great"] + "is": [ + "awesome", + "good", + "great" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice1.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice1.json index a96c3b8338d4..521fa740d0bd 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice1.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice1.json @@ -8,7 +8,10 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great"] + "is": [ + "good", + "great" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice2.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice2.json index 8e5fa76c48a3..9f8f9a38f2e0 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice2.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArraySplice2.json @@ -8,7 +8,13 @@ }, "x-baz": 1, "x-foo": { - "is": ["good", "great", "value1", "value2", "awesome"] + "is": [ + "good", + "great", + "value1", + "value2", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayUnshift.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayUnshift.json index 8a8b3d923d96..cfffb7e5355d 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayUnshift.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayUnshift.json @@ -8,7 +8,12 @@ }, "x-baz": 1, "x-foo": { - "is": ["value", "good", "great", "awesome"] + "is": [ + "value", + "good", + "great", + "awesome" + ] }, "x-bar": 5, } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayValues.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayValues.json index 79e0d012d399..97b281a873d4 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayValues.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ArrayValues.json @@ -18,5 +18,5 @@ null, true, 9.9 - ] + ], } \ No newline at end of file diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/ObjectRemoveMultiple.json b/packages/angular_devkit/core/src/workspace/json/test/cases/ObjectRemoveMultiple.json index 93ebbbc99f48..cf3ae23dcf98 100644 --- a/packages/angular_devkit/core/src/workspace/json/test/cases/ObjectRemoveMultiple.json +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/ObjectRemoveMultiple.json @@ -1,6 +1,5 @@ { "version": 1, - // Comment "x-foo": { "is": ["good", "great", "awesome"] }, diff --git a/packages/angular_devkit/core/src/workspace/json/utilities.ts b/packages/angular_devkit/core/src/workspace/json/utilities.ts index df25d3bc064b..b24a09d5e94f 100644 --- a/packages/angular_devkit/core/src/workspace/json/utilities.ts +++ b/packages/angular_devkit/core/src/workspace/json/utilities.ts @@ -1,284 +1,137 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { - JsonAstArray, - JsonAstKeyValue, - JsonAstNode, - JsonAstObject, - JsonObject, - JsonValue, -} from '../../json'; -import stableStringify = require('fast-json-stable-stringify'); +import { JsonArray, JsonObject, JsonValue, isJsonObject } from '../../json'; -interface CacheEntry { - value?: JsonValue; - node?: JsonAstNode; - parent: JsonAstArray | JsonAstKeyValue | JsonAstObject; -} - -export type ChangeListener = ( - op: 'add' | 'remove' | 'replace', - path: string, - node: JsonAstArray | JsonAstObject | JsonAstKeyValue, - value?: JsonValue, -) => void; +export type ChangeListener = (path: string[], newValue: JsonValue | undefined) => void; type ChangeReporter = ( - path: string, - parent: JsonAstArray | JsonAstKeyValue | JsonAstObject, - node?: JsonAstNode, - old?: JsonValue, - current?: JsonValue, + path: string[], + target: JsonObject | JsonArray, + oldValue: JsonValue | undefined, + newValue: JsonValue | undefined, ) => void; // lib.es5 PropertyKey is string | number | symbol which doesn't overlap ProxyHandler PropertyKey which is string | symbol. // See https://github.com/microsoft/TypeScript/issues/42894 type ProxyPropertyKey = string | symbol; -function findNode( - parent: JsonAstArray | JsonAstObject, - p: ProxyPropertyKey, -): { node?: JsonAstNode; parent: JsonAstArray | JsonAstKeyValue | JsonAstObject } { - if (parent.kind === 'object') { - const entry = parent.properties.find(entry => entry.key.value === p); - if (entry) { - return { node: entry.value, parent: entry }; - } - } else { - const index = Number(p); - if (!isNaN(index)) { - return { node: parent.elements[index], parent }; - } - } - - return { parent }; -} - -function createPropertyDescriptor(value: JsonValue | undefined): PropertyDescriptor { - return { - configurable: true, - enumerable: true, - writable: true, - value, - }; -} - -export function escapeKey(key: string | number): string | number { - if (typeof key === 'number') { - return key; - } - - return key.replace('~', '~0').replace('/', '~1'); -} - -export function unescapeKey(key: string | number): string | number { - if (typeof key === 'number') { - return key; - } - - return key.replace('~1', '/').replace('~0', '~'); -} - export function createVirtualAstObject( - root: JsonAstObject, + root: JsonObject | JsonArray, options: { exclude?: string[]; include?: string[]; listener?: ChangeListener; - base?: object; } = {}, ): T { - const reporter: ChangeReporter = (path, parent, node, old, current) => { - if (options.listener) { - if (old === current || stableStringify(old) === stableStringify(current)) { - return; - } + const reporter: ChangeReporter = (path, target, oldValue, newValue) => { + if (!options.listener) { + return; + } - const op = old === undefined ? 'add' : current === undefined ? 'remove' : 'replace'; - options.listener( - op, - path, - parent, - current, - ); + if (oldValue === newValue || JSON.stringify(oldValue) === JSON.stringify(newValue)) { + // same value + return; + } + + if (Array.isArray(target)) { + // For arrays we remove the index and update the entire value as keeping + // track of changes by indices can be rather complex. + options.listener(path.slice(0, -1), target); + } else { + options.listener(path, newValue); } }; return create( - root, - '', + Array.isArray(root) ? [...root] : { ...root }, + [], reporter, new Set(options.exclude), - options.include && options.include.length > 0 ? new Set(options.include) : undefined, - options.base, + options.include?.length ? new Set(options.include) : undefined, ) as T; } function create( - ast: JsonAstObject | JsonAstArray, - path: string, + obj: JsonObject | JsonArray, + path: string[], reporter: ChangeReporter, excluded = new Set(), included?: Set, - base?: object, ) { - const cache = new Map(); - const alteredNodes = new Set(); - - if (!base) { - if (ast.kind === 'object') { - base = Object.create(null) as object; - } else { - base = []; - (base as Array).length = ast.elements.length; - } - } - - return new Proxy(base, { + return new Proxy(obj, { getOwnPropertyDescriptor(target: {}, p: ProxyPropertyKey): PropertyDescriptor | undefined { - const descriptor = Reflect.getOwnPropertyDescriptor(target, p); - if (descriptor || typeof p === 'symbol') { - return descriptor; - } else if (excluded.has(p) || (included && !included.has(p))) { + if (excluded.has(p) || (included && !included.has(p))) { return undefined; } - const propertyPath = path + '/' + escapeKey(p); - const cacheEntry = cache.get(propertyPath); - if (cacheEntry) { - if (cacheEntry.value !== undefined) { - return createPropertyDescriptor(cacheEntry.value); - } - - return undefined; - } - - const { node } = findNode(ast, p); - if (node) { - return createPropertyDescriptor(node.value); - } - - return undefined; + return Reflect.getOwnPropertyDescriptor(target, p); }, has(target: {}, p: ProxyPropertyKey): boolean { - if (Reflect.has(target, p)) { - return true; - } else if (typeof p === 'symbol' || excluded.has(p)) { + if (typeof p === 'symbol' || excluded.has(p)) { return false; } - return cache.has(path + '/' + escapeKey(p)) || findNode(ast, p) !== undefined; + return Reflect.has(target, p); }, get(target: {}, p: ProxyPropertyKey): unknown { - if (typeof p === 'symbol' || Reflect.has(target, p)) { - return Reflect.get(target, p); - } else if (excluded.has(p) || (included && !included.has(p))) { + if (excluded.has(p) || (included && !included.has(p))) { return undefined; } - const propertyPath = path + '/' + escapeKey(p); - const cacheEntry = cache.get(propertyPath); - if (cacheEntry) { - return cacheEntry.value; + const value = Reflect.get(target, p); + if (typeof p === 'symbol') { + return value; } - const { node, parent } = findNode(ast, p); - let value; - if (node) { - if (node.kind === 'object' || node.kind === 'array') { - value = create( - node, - propertyPath, - (path, parent, vnode, old, current) => { - if (!alteredNodes.has(node)) { - reporter(path, parent, vnode, old, current); - } - }, - ); - } else { - value = node.value; - } - - cache.set(propertyPath, { node, parent, value }); + if ((isJsonObject(value) && !(value instanceof Map)) || Array.isArray(value)) { + return create(value, [...path, p], reporter); + } else { + return value; } - - return value; }, set(target: {}, p: ProxyPropertyKey, value: unknown): boolean { + if (excluded.has(p) || (included && !included.has(p))) { + return false; + } + if (value === undefined) { - // setting to undefined is equivalent to a delete - // tslint:disable-next-line: no-non-null-assertion - return this.deleteProperty!(target, p); + // setting to undefined is equivalent to a delete. + return this.deleteProperty?.(target, p) ?? false; } - if (typeof p === 'symbol' || Reflect.has(target, p)) { + if (typeof p === 'symbol') { return Reflect.set(target, p, value); - } else if (excluded.has(p) || (included && !included.has(p))) { - return false; } - // TODO: Check if is JSON value - const jsonValue = value as JsonValue; + const existingValue = getCurrentValue(target, p); + if (Reflect.set(target, p, value)) { + reporter([...path, p], target, existingValue, value as JsonValue); - const propertyPath = path + '/' + escapeKey(p); - const cacheEntry = cache.get(propertyPath); - if (cacheEntry) { - const oldValue = cacheEntry.value; - cacheEntry.value = value as JsonValue; - if (cacheEntry.node && oldValue !== value) { - alteredNodes.add(cacheEntry.node); - } - reporter(propertyPath, cacheEntry.parent, cacheEntry.node, oldValue, jsonValue); - } else { - const { node, parent } = findNode(ast, p); - cache.set(propertyPath, { node, parent, value: value as JsonValue }); - if (node && node.value !== value) { - alteredNodes.add(node); - } - reporter(propertyPath, parent, node, node && node.value, value as JsonValue); + return true; } - return true; + return false; }, deleteProperty(target: {}, p: ProxyPropertyKey): boolean { - if (typeof p === 'symbol' || Reflect.has(target, p)) { - return Reflect.deleteProperty(target, p); - } else if (excluded.has(p) || (included && !included.has(p))) { + if (excluded.has(p)) { return false; } - const propertyPath = path + '/' + escapeKey(p); - const cacheEntry = cache.get(propertyPath); - if (cacheEntry) { - const oldValue = cacheEntry.value; - cacheEntry.value = undefined; - if (cacheEntry.node) { - alteredNodes.add(cacheEntry.node); - } - if (cacheEntry.parent.kind === 'keyvalue') { - // Remove the entire key/value pair from this JSON object - reporter(propertyPath, ast, cacheEntry.node, oldValue, undefined); - } else { - reporter(propertyPath, cacheEntry.parent, cacheEntry.node, oldValue, undefined); - } - } else { - const { node, parent } = findNode(ast, p); - if (node) { - cache.set(propertyPath, { node, parent, value: undefined }); - alteredNodes.add(node); - if (parent.kind === 'keyvalue') { - // Remove the entire key/value pair from this JSON object - reporter(propertyPath, ast, node, node && node.value, undefined); - } else { - reporter(propertyPath, parent, node, node && node.value, undefined); - } - } + if (typeof p === 'symbol') { + return Reflect.deleteProperty(target, p); + } + + const existingValue = getCurrentValue(target, p); + if (Reflect.deleteProperty(target, p)) { + reporter([...path, p], target, existingValue, undefined); + + return true; } return true; @@ -291,23 +144,21 @@ function create( return false; }, ownKeys(target: {}): ProxyPropertyKey[] { - let keys: ProxyPropertyKey[]; - if (ast.kind === 'object') { - keys = ast.properties - .map(entry => entry.key.value) - .filter(p => !excluded.has(p) && (!included || included.has(p))); - } else { - keys = []; - } - - for (const key of cache.keys()) { - const relativeKey = key.substr(path.length + 1); - if (relativeKey.length > 0 && !relativeKey.includes('/')) { - keys.push(`${unescapeKey(relativeKey)}`); - } - } - - return [...new Set([...keys, ...Reflect.ownKeys(target)])]; + return Reflect.ownKeys(target).filter( + (p) => !excluded.has(p) && (!included || included.has(p)), + ); }, }); } + +function getCurrentValue(target: object, property: string): JsonValue | undefined { + if (Array.isArray(target) && isFinite(+property)) { + return target[+property]; + } + + if (target && property in target) { + return (target as JsonObject)[property]; + } + + return undefined; +} diff --git a/packages/angular_devkit/core/src/workspace/json/writer.ts b/packages/angular_devkit/core/src/workspace/json/writer.ts index 0b253f917a53..7d5d5df05c7f 100644 --- a/packages/angular_devkit/core/src/workspace/json/writer.ts +++ b/packages/angular_devkit/core/src/workspace/json/writer.ts @@ -1,12 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import MagicString from 'magic-string'; -import { JsonAstKeyValue, JsonAstNode, JsonObject, JsonValue } from '../../json'; + +import { applyEdits, modify } from 'jsonc-parser'; +import { JsonObject, JsonValue } from '../../json'; import { ProjectDefinition, TargetDefinition, WorkspaceDefinition } from '../definitions'; import { WorkspaceHost } from '../host'; import { @@ -15,7 +16,6 @@ import { JsonWorkspaceMetadata, JsonWorkspaceSymbol, } from './metadata'; -import { unescapeKey } from './utilities'; export async function writeJsonWorkspace( workspace: WorkspaceDefinition, @@ -29,14 +29,12 @@ export async function writeJsonWorkspace( if (metadata) { if (!metadata.hasChanges) { - // nothing to do return; } - // update existing JSON workspace const data = updateJsonWorkspace(metadata); - return host.writeFile(path || metadata.filePath, data); + return host.writeFile(path ?? metadata.filePath, data); } else { // serialize directly if (!path) { @@ -55,7 +53,9 @@ function convertJsonWorkspace(workspace: WorkspaceDefinition, schema?: string): $schema: schema || './node_modules/@angular/cli/lib/config/schema.json', version: 1, ...workspace.extensions, - projects: workspace.projects ? convertJsonProjectCollection(workspace.projects) : {}, + ...(isEmpty(workspace.projects) + ? {} + : { projects: convertJsonProjectCollection(workspace.projects) }), }; return obj; @@ -93,7 +93,7 @@ function convertJsonProject(project: ProjectDefinition): JsonObject { return obj; } -function isEmpty(obj?: object): boolean { +function isEmpty(obj: object | undefined): boolean { return obj === undefined || Object.keys(obj).length === 0; } @@ -110,9 +110,7 @@ function convertJsonTarget(target: TargetDefinition): JsonObject { }; } -function convertJsonTargetCollection( - collection: Iterable<[string, TargetDefinition]>, -): JsonObject { +function convertJsonTargetCollection(collection: Iterable<[string, TargetDefinition]>): JsonObject { const targets = Object.create(null) as JsonObject; for (const [projectName, target] of collection) { @@ -122,247 +120,56 @@ function convertJsonTargetCollection( return targets; } -function findFullStart(node: JsonAstNode | JsonAstKeyValue, raw: string): number { - let i = node.start.offset; - while (i > 0 && /\s/.test(raw[i - 1])) { - --i; - } - - return i; -} - -function findFullEnd(node: JsonAstNode | JsonAstKeyValue, raw: string): number { - let i = node.end.offset; - if (i >= raw.length) { - return raw.length; - } else if (raw[i] === ',') { - return i + 1; - } - - while (i > node.start.offset && /\s/.test(raw[i - 1])) { - --i; - } - - return i; -} - -function findPrecedingComma(node: JsonAstNode | JsonAstKeyValue, raw: string): number { - let i = node.start.offset; - if (node.comments && node.comments.length > 0) { - i = node.comments[0].start.offset; - } - while (i > 0 && /\s/.test(raw[i - 1])) { - --i; - } - - if (raw[i - 1] === ',') { - return i - 1; - } - - return -1; -} - -function stringify( - value: JsonValue | undefined, - multiline: boolean, - depth: number, - indent: string, -): string { - if (value === undefined) { - return ''; - } - - if (multiline) { - const content = JSON.stringify(value, null, indent); - const spacing = '\n' + indent.repeat(depth); - - return content.replace(/\n/g, spacing); - } else { - return JSON.stringify(value); - } -} - function normalizeValue( value: JsonChange['value'] | undefined, type: JsonChange['type'], ): JsonValue | undefined { + if (value === undefined) { + return undefined; + } + switch (type) { case 'project': return convertJsonProject(value as ProjectDefinition); case 'projectcollection': const projects = convertJsonProjectCollection(value as Iterable<[string, ProjectDefinition]>); - return Object.keys(projects).length === 0 ? undefined : projects; + return isEmpty(projects) ? undefined : projects; case 'target': return convertJsonTarget(value as TargetDefinition); case 'targetcollection': const targets = convertJsonTargetCollection(value as Iterable<[string, TargetDefinition]>); - return Object.keys(targets).length === 0 ? undefined : targets; + return isEmpty(targets) ? undefined : targets; default: return value as JsonValue; } } function updateJsonWorkspace(metadata: JsonWorkspaceMetadata): string { - const data = new MagicString(metadata.raw); - const indent = data.getIndentString(); - const removedCommas = new Set(); - const nodeChanges = - new Map(); + let { raw: content } = metadata; + const { changes, hasLegacyTargetsName } = metadata; - for (const { op, path, node, value, type } of metadata.changes) { - // targets/projects are typically large objects so always use multiline - const multiline = node.start.line !== node.end.line || type !== 'json'; - const pathSegments = path.split('/'); - const depth = pathSegments.length - 1; // TODO: more complete analysis - const propertyOrIndex = unescapeKey(pathSegments[depth]); - const jsonValue = normalizeValue(value, type); - if (op === 'add' && jsonValue === undefined) { - continue; - } - - // Track changes to the order/size of any modified objects/arrays - let elements = nodeChanges.get(node); - if (!elements) { - if (node.kind === 'array') { - elements = node.elements.slice(); - nodeChanges.set(node, elements); - } else if (node.kind === 'object') { - elements = node.properties.slice(); - nodeChanges.set(node, elements); - } else { - // keyvalue - elements = []; - } - } - - switch (op) { - case 'add': - let contentPrefix = ''; - if (node.kind === 'object') { - contentPrefix = `"${propertyOrIndex}": `; - } - - const spacing = multiline ? '\n' + indent.repeat(depth) : ' '; - const content = spacing + contentPrefix + stringify(jsonValue, multiline, depth, indent); - - // Additions are handled after analyzing all operations - // This is mainly to support array operations which can occur at arbitrary indices - if (node.kind === 'object') { - // Object property additions are always added at the end for simplicity - elements.push(content); - } else { - // Add place holders if adding an index past the length - // An empty string is an impossible real value - for (let i = elements.length; i < +propertyOrIndex; ++i) { - elements[i] = ''; - } - if (elements[+propertyOrIndex] === '') { - elements[+propertyOrIndex] = content; - } else { - elements.splice(+propertyOrIndex, 0, content); - } - } - break; - case 'remove': - let removalIndex = -1; - if (node.kind === 'object') { - removalIndex = elements.findIndex(e => { - return typeof e != 'string' && e.kind === 'keyvalue' && e.key.value === propertyOrIndex; - }); - } else if (node.kind === 'array') { - removalIndex = +propertyOrIndex; - } - if (removalIndex === -1) { - continue; - } - - const nodeToRemove = elements[removalIndex]; - if (typeof nodeToRemove === 'string') { - // synthetic - elements.splice(removalIndex, 1); - continue; - } - - if (elements.length - 1 === removalIndex) { - // If the element is a terminal element remove the otherwise trailing comma - const commaIndex = findPrecedingComma(nodeToRemove, data.original); - if (commaIndex !== -1) { - data.remove(commaIndex, commaIndex + 1); - removedCommas.add(commaIndex); - } - } - data.remove( - findFullStart(nodeToRemove, data.original), - findFullEnd(nodeToRemove, data.original), - ); - elements.splice(removalIndex, 1); - break; - case 'replace': - let nodeToReplace; - if (node.kind === 'keyvalue') { - nodeToReplace = node.value; - } else if (node.kind === 'array') { - nodeToReplace = elements[+propertyOrIndex]; - if (typeof nodeToReplace === 'string') { - // Was already modified. This is already handled. - continue; - } - } else { - continue; - } - - nodeChanges.delete(nodeToReplace); - - data.overwrite( - nodeToReplace.start.offset, - nodeToReplace.end.offset, - stringify(jsonValue, multiline, depth, indent), - ); - break; - } - } - - for (const [node, elements] of nodeChanges.entries()) { - let parentPoint = 1 + data.original.indexOf( - node.kind === 'array' ? '[' : '{', - node.start.offset, - ); - - // Short-circuit for simple case - if (elements.length === 1 && typeof elements[0] === 'string') { - data.appendRight(parentPoint, elements[0] as string); - continue; - } - - // Combine adjecent element additions to minimize/simplify insertions - const optimizedElements: typeof elements = []; - for (let i = 0; i < elements.length; ++i) { - const element = elements[i]; - if (typeof element === 'string' && i > 0 && typeof elements[i - 1] === 'string') { - optimizedElements[optimizedElements.length - 1] += ',' + element; - } else { - optimizedElements.push(element); - } - } - - let prefixComma = false; - for (const element of optimizedElements) { - if (typeof element === 'string') { - data.appendRight( - parentPoint, - (prefixComma ? ',' : '') + element, - ); - } else { - parentPoint = findFullEnd(element, data.original); - prefixComma = data.original[parentPoint - 1] !== ',' || removedCommas.has(parentPoint - 1); - } + for (const { jsonPath, value, type } of changes.values()) { + // Determine which key to use if (architect or targets) + if (hasLegacyTargetsName && jsonPath[2] === 'targets') { + jsonPath[2] = 'architect'; } + // modify + const newJsonPath = jsonPath.map((v) => (isFinite(+v) ? +v : v)); + // TODO: `modify` re-parses the content every time. + // See: https://github.com/microsoft/node-jsonc-parser/blob/35d94cd71bd48f9784453b2439262c938e21d49b/src/impl/edit.ts#L18 + // Ideally this should accept a string or an AST to avoid the potentially expensive repeat parsing operation. + const edits = modify(content, newJsonPath, normalizeValue(value, type), { + formattingOptions: { + insertSpaces: true, + tabSize: 2, + }, + }); + + content = applyEdits(content, edits); } - const result = data.toString(); - - return result; + return content; } diff --git a/packages/angular_devkit/core/src/workspace/json/writer_spec.ts b/packages/angular_devkit/core/src/workspace/json/writer_spec.ts index 23af2d1c525f..d03d8eb3635b 100644 --- a/packages/angular_devkit/core/src/workspace/json/writer_spec.ts +++ b/packages/angular_devkit/core/src/workspace/json/writer_spec.ts @@ -1,11 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-big-function + import { readFileSync } from 'fs'; import { join } from 'path'; import { JsonArray, JsonObject } from '../../json'; @@ -40,10 +40,12 @@ function createTestCaseHost(inputData = '') { async writeFile(path: string, data: string) { try { const testCase = readFileSync( - require.resolve(join(__dirname, 'test', 'cases', path) + '.json'), 'utf8'); + require.resolve(join(__dirname, 'test', 'cases', path) + '.json'), + 'utf8', + ); expect(data).toEqual(testCase); } catch (e) { - fail(`Unable to load test case '${path}': ${e.message || e}`); + fail(`Unable to load test case '${path}': ${e instanceof Error ? e.message : e}`); } }, async isFile() { @@ -60,7 +62,7 @@ function createTestCaseHost(inputData = '') { describe('writeJsonWorkpaceFile', () => { it('does not modify a file without changes', async () => { const host = { - async readFile(path: string) { + async readFile() { return representativeFile; }, async writeFile() { @@ -274,7 +276,7 @@ describe('writeJsonWorkpaceFile', () => { await writeJsonWorkspace(workspace, host, 'AddProjectWithTargets'); }); - it('modifies a project\'s properties', async () => { + it("modifies a project's properties", async () => { const host = createTestCaseHost(representativeFile); const workspace = await readJsonWorkspace('', host); @@ -291,7 +293,7 @@ describe('writeJsonWorkpaceFile', () => { await writeJsonWorkspace(workspace, host, 'ProjectModifyProperties'); }); - it('sets a project\'s properties', async () => { + it("sets a project's properties", async () => { const host = createTestCaseHost(representativeFile); const workspace = await readJsonWorkspace('', host); @@ -578,7 +580,7 @@ describe('writeJsonWorkpaceFile', () => { const workspace = await readJsonWorkspace('', host); - workspace.extensions['x-foo'] = { }; + workspace.extensions['x-foo'] = {}; await writeJsonWorkspace(workspace, host, 'ObjectReplace2'); }); diff --git a/packages/angular_devkit/schematics/BUILD.bazel b/packages/angular_devkit/schematics/BUILD.bazel index 6715950a2b93..9f56a7377905 100644 --- a/packages/angular_devkit/schematics/BUILD.bazel +++ b/packages/angular_devkit/schematics/BUILD.bazel @@ -1,10 +1,7 @@ +load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test") -load("//tools:defaults.bzl", "ts_library") - -# @external_begin -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -# @external_end +load("//tools:defaults.bzl", "pkg_npm", "ts_library") +load("//tools:toolchain_info.bzl", "TOOLCHAINS_NAMES", "TOOLCHAINS_VERSIONS") # Copyright Google Inc. All Rights Reserved. # @@ -18,6 +15,7 @@ licenses(["notice"]) ts_library( name = "schematics", + package_name = "@angular-devkit/schematics", srcs = glob( include = ["src/**/*.ts"], exclude = [ @@ -25,8 +23,6 @@ ts_library( "src/**/*_benchmark.ts", ], ), - # The attribute below is needed in g3 to turn off strict typechecking - # strict_checks = False, data = glob( include = ["**/*.json"], exclude = [ @@ -40,10 +36,14 @@ ts_library( "//packages/angular_devkit/core", "//packages/angular_devkit/core/node", # TODO: get rid of this for 6.0 "@npm//@types/node", + "@npm//jsonc-parser", + "@npm//magic-string", "@npm//rxjs", ], ) +# @external_begin + ts_library( name = "schematics_test_lib", testonly = True, @@ -57,31 +57,56 @@ ts_library( ], ) -jasmine_node_test( - name = "schematics_test", - srcs = [":schematics_test_lib"], - deps = [ - "@npm//jasmine", - "@npm//source-map", - ], +[ + jasmine_node_test( + name = "schematics_test_" + toolchain_name, + srcs = [":schematics_test_lib"], + tags = [toolchain_name], + toolchain = toolchain, + deps = [ + "@npm//jasmine", + "@npm//source-map", + ], + ) + for toolchain_name, toolchain in zip( + TOOLCHAINS_NAMES, + TOOLCHAINS_VERSIONS, + ) +] + +genrule( + name = "license", + srcs = ["//:LICENSE"], + outs = ["LICENSE"], + cmd = "cp $(execpath //:LICENSE) $@", ) -# @external_begin pkg_npm( name = "npm_package", + pkg_deps = [ + "//packages/angular_devkit/core:package.json", + ], deps = [ + ":README.md", + ":collection-schema.json", + ":license", ":schematics", "//packages/angular_devkit/schematics/tasks", + "//packages/angular_devkit/schematics/tasks:package.json", "//packages/angular_devkit/schematics/testing", - "//packages/angular_devkit/schematics/tools", + "//packages/angular_devkit/schematics/testing:package.json", + "//packages/angular_devkit/schematics/tools:package.json", ], ) -pkg_tar( - name = "npm_package_archive", - srcs = [":npm_package"], - extension = "tar.gz", - strip_prefix = "./npm_package", - tags = ["manual"], +api_golden_test_npm_package( + name = "schematics_api", + data = [ + ":npm_package", + "//goldens:public-api", + ], + golden_dir = "angular_cli/goldens/public-api/angular_devkit/schematics", + npm_package = "angular_cli/packages/angular_devkit/schematics/npm_package", + types = ["@npm//@types/node"], ) # @external_end diff --git a/packages/angular_devkit/schematics/README.md b/packages/angular_devkit/schematics/README.md index 76e9fa6e8f78..b4f47a101e7f 100644 --- a/packages/angular_devkit/schematics/README.md +++ b/packages/angular_devkit/schematics/README.md @@ -1,33 +1,36 @@ # Schematics + > A scaffolding library for the modern web. ## Description + Schematics are generators that transform an existing filesystem. They can create files, refactor existing files, or move files around. What distinguishes Schematics from other generators, such as Yeoman or Yarn Create, is that schematics are purely descriptive; no changes are applied to the actual filesystem until everything is ready to be committed. There is no side effect, by design, in Schematics. # Glossary -| Term | Description | -|------|-------------| -| **Schematics** | A generator that executes descriptive code without side effects on an existing file system. | -| **Collection** | A list of schematics metadata. Schematics can be referred by name inside a collection. | -| **Tool** | The code using the Schematics library. | -| **Tree** | A staging area for changes, containing the original file system, and a list of changes to apply to it. | -| **Rule** | A function that applies actions to a `Tree`. It returns a new `Tree` that will contain all transformations to be applied. | -| **Source** | A function that creates an entirely new `Tree` from an empty filesystem. For example, a file source could read files from disk and create a Create Action for each of those. -| **Action** | An atomic operation to be validated and committed to a filesystem or a `Tree`. Actions are created by schematics. | -| **Sink** | The final destination of all `Action`s. | +| Term | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Schematics** | A generator that executes descriptive code without side effects on an existing file system. | +| **Collection** | A list of schematics metadata. Schematics can be referred by name inside a collection. | +| **Tool** | The code using the Schematics library. | +| **Tree** | A staging area for changes, containing the original file system, and a list of changes to apply to it. | +| **Rule** | A function that applies actions to a `Tree`. It returns a new `Tree` that will contain all transformations to be applied. | +| **Source** | A function that creates an entirely new `Tree` from an empty filesystem. For example, a file source could read files from disk and create a Create Action for each of those. | +| **Action** | An atomic operation to be validated and committed to a filesystem or a `Tree`. Actions are created by schematics. | +| **Sink** | The final destination of all `Action`s. | # Tooling -Schematics is a library, and does not work by itself. A [reference CLI](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics_cli/bin/schematics.ts) is available on this repository, and is published on NPM at [@angular-devkit/schematics-cli](https://www.npmjs.com/package/@angular-devkit/schematics-cli). This document explains the library usage and the tooling API, but does not go into the tool implementation itself. + +Schematics is a library, and does not work by itself. A [reference CLI](https://github.com/angular/angular-cli/blob/main/packages/angular_devkit/schematics_cli/bin/schematics.ts) is available on this repository, and is published on NPM at [@angular-devkit/schematics-cli](https://www.npmjs.com/package/@angular-devkit/schematics-cli). This document explains the library usage and the tooling API, but does not go into the tool implementation itself. The tooling is responsible for the following tasks: 1. Create the Schematic Engine, and pass in a Collection and Schematic loader. 1. Understand and respect the Schematics metadata and dependencies between collections. Schematics can refer to dependencies, and it's the responsibility of the tool to honor those dependencies. The reference CLI uses NPM packages for its collections. 1. Create the Options object. Options can be anything, but the schematics can specify a JSON Schema that should be respected. The reference CLI, for example, parses the arguments as a JSON object and validates it with the Schema specified by the collection. - 1. Schematics provides some JSON Schema formats for validation that tooling should add. These validate paths, html selectors and app names. Please check the reference CLI for how these can be added. +1. Schematics provides some JSON Schema formats for validation that tooling should add. These validate paths, html selectors and app names. Please check the reference CLI for how these can be added. 1. Call the schematics with the original Tree. The tree should represent the initial state of the filesystem. The reference CLI uses the current directory for this. 1. Create a Sink and commit the result of the schematics to the Sink. Many sinks are provided by the library; FileSystemSink and DryRunSink are examples. 1. Output any logs propagated by the library, including debugging information. @@ -35,25 +38,30 @@ The tooling is responsible for the following tasks: The tooling API is composed of the following pieces: ## Engine + The `SchematicEngine` is responsible for loading and constructing `Collection`s and `Schematics`. When creating an engine, the tooling provides an `EngineHost` interface that understands how to create a `CollectionDescription` from a name, and how to create a `SchematicDescription`. # Schematics (Generators) + Schematics are generators and part of a `Collection`. ## Collection + A Collection is defined by a `collection.json` file (in the reference CLI). This JSON defines the following properties: -| Prop Name | Type | Description | -|---|---|---| -| **name** | `string` | The name of the collection. | -| **version** | `string` | Unused field. | +| Prop Name | Type | Description | +| ----------- | -------- | --------------------------- | +| **name** | `string` | The name of the collection. | +| **version** | `string` | Unused field. | ## Schematic # Operators, Sources and Rules + A `Source` is a generator of a `Tree`; it creates an entirely new root tree from nothing. A `Rule` is a transformation from one `Tree` to another. A `Schematic` (at the root) is a `Rule` that is normally applied on the filesystem. ## Operators + `FileOperator`s apply changes to a single `FileEntry` and return a new `FileEntry`. The result follows these rules: 1. If the `FileEntry` returned is null, a `DeleteAction` will be added to the action list. @@ -63,66 +71,71 @@ A `Source` is a generator of a `Tree`; it creates an entirely new root tree from It is impossible to create files using a `FileOperator`. ## Provided Operators + The Schematics library provides multiple `Operator` factories by default that cover basic use cases: -| FileOperator | Description | -|---|---| -| `contentTemplate(options: T)` | Apply a content template (see the Template section) | -| `pathTemplate(options: T)` | Apply a path template (see the Template section) | +| FileOperator | Description | +| -------------------------------- | -------------------------------------------------------------------- | +| `contentTemplate(options: T)` | Apply a content template (see the [Templating](#templating) section) | +| `pathTemplate(options: T)` | Apply a path template (see the [Templating](#templating) section) | ## Provided Sources + The Schematics library additionally provides multiple `Source` factories by default: -| Source | Description | -|---|---| -| `empty()` | Creates a source that returns an empty `Tree`. | -| `source(tree: Tree)` | Creates a `Source` that returns the `Tree` passed in as argument. | -| `url(/service/url: string)` | Loads a list of files from the given URL and returns a `Tree` with the files as `CreateAction` applied to an empty `Tree`. | -| `apply(source: Source, rules: Rule[])` | Apply a list of `Rule`s to a source, and return the resulting `Source`. | +| Source | Description | +| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `empty()` | Creates a source that returns an empty `Tree`. | +| `source(tree: Tree)` | Creates a `Source` that returns the `Tree` passed in as argument. | +| `url(/service/url: string)` | Loads a list of files from the given URL and returns a `Tree` with the files as `CreateAction` applied to an empty `Tree`. | +| `apply(source: Source, rules: Rule[])` | Apply a list of `Rule`s to a source, and return the resulting `Source`. | ## Provided Rules -The schematics library also provides `Rule` factories by default: -| Rule | Description | -|---|---| -| `noop()` | Returns the input `Tree` as is. | -| `chain(rules: Rule[])` | Returns a `Rule` that's the concatenation of other `Rule`s. | -| `forEach(op: FileOperator)` | Returns a `Rule` that applies an operator to every file of the input `Tree`. | -| `move(root: string)` | Moves all the files from the input to a subdirectory. | -| `merge(other: Tree)` | Merge the input `Tree` with the other `Tree`. | -| `contentTemplate(options: T)` | Apply a content template (see the Template section) to the entire `Tree`. | -| `pathTemplate(options: T)` | Apply a path template (see the Template section) to the entire `Tree`. | -| `template(options: T)` | Apply both path and content templates (see the Template section) to the entire `Tree`. | -| `filter(predicate: FilePredicate)` | Returns the input `Tree` with files that do not pass the `FilePredicate`. | +The schematics library also provides `Rule` factories by default: +| Rule | Description | +| ------------------------------------------- | -------------------------------------------------------------------------------------- | +| `noop()` | Returns the input `Tree` as is. | +| `chain(rules: Rule[])` | Returns a `Rule` that's the concatenation of other `Rule`s. | +| `forEach(op: FileOperator)` | Returns a `Rule` that applies an operator to every file of the input `Tree`. | +| `move(root: string)` | Moves all the files from the input to a subdirectory. | +| `merge(other: Tree)` | Merge the input `Tree` with the other `Tree`. | +| `contentTemplate(options: T)` | Apply a content template (see the Template section) to the entire `Tree`. | +| `pathTemplate(options: T)` | Apply a path template (see the Template section) to the entire `Tree`. | +| `template(options: T)` | Apply both path and content templates (see the Template section) to the entire `Tree`. | +| `filter(predicate: FilePredicate)` | Returns the input `Tree` with files that do not pass the `FilePredicate`. | # Templating + As referenced above, some functions are based upon a file templating system, which consists of path and content templating. The system operates on placeholders defined inside files or their paths as loaded in the `Tree` and fills these in as defined in the following, using values passed into the `Rule` which applies the templating (i.e. `template(options: T)`). ## Path Templating -| Placeholder | Description | -|---|---| -| __variable__ | Replaced with the value of `variable`. | -| __variable@function__ | Replaced with the result of the call `function(variable)`. Can be chained to the left (`__variable@function1@function2__ ` etc). | + +| Placeholder | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `__variable__` | Replaced with the value of `variable`. | +| `__variable@function__` | Replaced with the result of the call `function(variable)`. Can be chained to the left (`__variable@function1@function2__ ` etc). | ## Content Templating -| Placeholder | Description | -|---|---| -| <%= expression %> | Replaced with the result of the call of the given expression. This only supports direct expressions, no structural (for/if/...) JavaScript. | -| <%- expression %> | Same as above, but the value of the result will be escaped for HTML when inserted (i.e. replacing '<' with '<') | -| <% inline code %> | Inserts the given code into the template structure, allowing to insert structural JavaScript. | -| <%# text %> | A comment, which gets entirely dropped. | +| Placeholder | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `<%= expression %>` | Replaced with the result of the call of the given expression. This only supports direct expressions, no structural (for/if/...) JavaScript. | +| `<%- expression %>` | Same as above, but the value of the result will be escaped for HTML when inserted (i.e. replacing '<' with '\<') | +| `<% inline code %>` | Inserts the given code into the template structure, allowing to insert structural JavaScript. | +| `<%# text %>` | A comment, which gets entirely dropped. | # Examples ## Simple + An example of a simple Schematics which creates a "hello world" file, using an option to determine its path: ```typescript -import {Tree} from '@angular-devkit/schematics'; +import { Tree } from '@angular-devkit/schematics'; export default function MySchematic(options: any) { return (tree: Tree) => { @@ -138,6 +151,7 @@ A few things from this example: 1. It returns a [`Rule`](src/engine/interface.ts#L73), which is a transformation from a `Tree` to another `Tree`. ## Templating + A simplified example of a Schematics which creates a file containing a new Class, using an option to determine its name: ```typescript @@ -152,8 +166,15 @@ export class <%= classify(name) %> { import { strings } from '@angular-devkit/core'; import { - Rule, SchematicContext, SchematicsException, Tree, - apply, branchAndMerge, mergeWith, template, url, + Rule, + SchematicContext, + SchematicsException, + Tree, + apply, + branchAndMerge, + mergeWith, + template, + url, } from '@angular-devkit/schematics'; import { Schema as ClassOptions } from './schema'; @@ -163,15 +184,12 @@ export default function (options: ClassOptions): Rule { throw new SchematicsException('Option (name) is required.'); } - const templateSource = apply( - url('https://github.com/files'), - [ - template({ - ...strings, - ...options, - }), - ] - ); + const templateSource = apply(url('https://github.com/files'), [ + template({ + ...strings, + ...options, + }), + ]); return branchAndMerge(mergeWith(templateSource)); }; @@ -179,14 +197,8 @@ export default function (options: ClassOptions): Rule { ``` Additional things from this example: + 1. `strings` provides the used `dasherize` and `classify` functions, among others. 1. The files are on-disk in the same root directory as the `index.ts` and loaded into a `Tree`. 1. Then the `template` `Rule` fills in the specified templating placeholders. For this, it only knows about the variables and functions passed to it via the options-object. 1. Finally, the resulting `Tree`, containing the new file, is merged with the existing files of the project which the Schematic is run on. - -# Future Work -Schematics is not done yet. Here's a list of things we are considering: - -* Smart defaults for Options. Having a JavaScript function for default values based on other default values. -* Prompt for input options. This should only be prompted for the original schematics, dependencies to other schematics should not trigger another prompting. -* Tasks for running tooling-specific jobs before and after a schematics has been scaffolded. Such tasks can involve initialize git, or npm install. A specific list of tasks should be provided by the tool, with unsupported tasks generating an error. diff --git a/packages/angular_devkit/schematics/collection-schema.json b/packages/angular_devkit/schematics/collection-schema.json index c390eaa8d771..6489280417e2 100644 --- a/packages/angular_devkit/schematics/collection-schema.json +++ b/packages/angular_devkit/schematics/collection-schema.json @@ -59,17 +59,12 @@ "description": "Whether or not this schematic can be called from an external schematic, or a tool. This implies hidden: true." } }, - "required": [ - "factory", - "description" - ] + "required": ["factory", "description"] } }, "version": { "type": "string" } }, - "required": [ - "schematics" - ] + "required": ["schematics"] } diff --git a/packages/angular_devkit/schematics/package.json b/packages/angular_devkit/schematics/package.json index 39bfb63c111f..9c26a6570c65 100644 --- a/packages/angular_devkit/schematics/package.json +++ b/packages/angular_devkit/schematics/package.json @@ -1,6 +1,6 @@ { "name": "@angular-devkit/schematics", - "version": "0.0.0", + "version": "0.0.0-PLACEHOLDER", "description": "Angular Schematics - Library", "main": "src/index.js", "typings": "src/index.d.ts", @@ -13,8 +13,10 @@ "schematics" ], "dependencies": { - "@angular-devkit/core": "0.0.0", - "ora": "5.4.0", + "@angular-devkit/core": "0.0.0-PLACEHOLDER", + "jsonc-parser": "3.2.0", + "magic-string": "0.26.7", + "ora": "5.4.1", "rxjs": "6.6.7" } } diff --git a/packages/angular_devkit/schematics/src/_golden-api.ts b/packages/angular_devkit/schematics/src/_golden-api.ts deleted file mode 100644 index 9d9ea4ddacbe..000000000000 --- a/packages/angular_devkit/schematics/src/_golden-api.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -export * from './index'; - -export * from './formats/index'; - -export * from './workflow/index'; diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts index a3ca4caa515e..d89690c31932 100644 --- a/packages/angular_devkit/schematics/src/engine/engine.ts +++ b/packages/angular_devkit/schematics/src/engine/engine.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BaseException, PriorityQueue, logging } from '@angular-devkit/core'; import { Observable, from as observableFrom } from 'rxjs'; import { concatMap } from 'rxjs/operators'; @@ -32,13 +33,16 @@ import { } from './interface'; import { SchematicImpl } from './schematic'; - export class UnknownUrlSourceProtocol extends BaseException { - constructor(url: string) { super(`Unknown Protocol on url "${url}".`); } + constructor(url: string) { + super(`Unknown Protocol on url "${url}".`); + } } export class UnknownCollectionException extends BaseException { - constructor(name: string) { super(`Unknown collection "${name}".`); } + constructor(name: string) { + super(`Unknown collection "${name}".`); + } } export class CircularCollectionException extends BaseException { @@ -60,7 +64,9 @@ export class PrivateSchematicException extends BaseException { } export class SchematicEngineConflictingException extends BaseException { - constructor() { super(`A schematic was called from a different engine as its parent.`); } + constructor() { + super(`A schematic was called from a different engine as its parent.`); + } } export class UnregisteredTaskException extends BaseException { @@ -77,21 +83,27 @@ export class UnknownTaskDependencyException extends BaseException { } export class CollectionImpl - implements Collection { - constructor(private _description: CollectionDescription, - private _engine: SchematicEngine, - public readonly baseDescriptions?: Array>) { + implements Collection +{ + constructor( + private _description: CollectionDescription, + private _engine: SchematicEngine, + public readonly baseDescriptions?: Array>, + ) {} + + get description() { + return this._description; + } + get name() { + return this.description.name || ''; } - - get description() { return this._description; } - get name() { return this.description.name || ''; } createSchematic(name: string, allowPrivate = false): Schematic { return this._engine.createSchematic(name, this, allowPrivate); } - listSchematicNames(): string[] { - return this._engine.listSchematicNames(this); + listSchematicNames(includeHidden?: boolean): string[] { + return this._engine.listSchematicNames(this, includeHidden); } } @@ -117,7 +129,7 @@ export class TaskScheduler { return new Set(); } - const tasks = dependencies.map(dep => { + const tasks = dependencies.map((dep) => { const task = this._taskIds.get(dep); if (!task) { throw new UnknownTaskDependencyException(dep); @@ -129,7 +141,7 @@ export class TaskScheduler { return new Set(tasks); } - schedule(taskConfiguration: TaskConfiguration): TaskId { + schedule(taskConfiguration: TaskConfiguration): TaskId { const dependencies = this._mapDependencies(taskConfiguration.dependencies); const priority = this._calculatePriority(dependencies); @@ -155,23 +167,26 @@ export class TaskScheduler { return tasks; } - } - export class SchematicEngine - implements Engine { - + implements Engine +{ private _collectionCache = new Map>(); - private _schematicCache - = new WeakMap, Map>>(); + private _schematicCache = new WeakMap< + Collection, + Map> + >(); private _taskSchedulers = new Array(); - constructor(private _host: EngineHost, protected _workflow?: Workflow) { - } + constructor(private _host: EngineHost, protected _workflow?: Workflow) {} - get workflow() { return this._workflow || null; } - get defaultMergeStrategy() { return this._host.defaultMergeStrategy || MergeStrategy.Default; } + get workflow() { + return this._workflow || null; + } + get defaultMergeStrategy() { + return this._host.defaultMergeStrategy || MergeStrategy.Default; + } createCollection( name: string, @@ -239,13 +254,14 @@ export class SchematicEngine = { - debug: parent && parent.debug || false, + debug: (parent && parent.debug) || false, engine: this, - logger: (parent && parent.logger && parent.logger.createChild(schematic.description.name)) - || new logging.NullLogger(), + logger: + (parent && parent.logger && parent.logger.createChild(schematic.description.name)) || + new logging.NullLogger(), schematic, - strategy: (parent && parent.strategy !== undefined) - ? parent.strategy : this.defaultMergeStrategy, + strategy: + parent && parent.strategy !== undefined ? parent.strategy : this.defaultMergeStrategy, interactive, addTask, }; @@ -259,7 +275,7 @@ export class SchematicEngine( + function addTask( task: TaskConfigurationGenerator, dependencies?: Array, ): TaskId { @@ -322,12 +338,15 @@ export class SchematicEngine): string[] { - const names = this._host.listSchematicNames(collection.description); + listSchematicNames( + collection: Collection, + includeHidden?: boolean, + ): string[] { + const names = this._host.listSchematicNames(collection.description, includeHidden); if (collection.baseDescriptions) { for (const base of collection.baseDescriptions) { - names.push(...this._host.listSchematicNames(base)); + names.push(...this._host.listSchematicNames(base, includeHidden)); } } @@ -345,8 +364,10 @@ export class SchematicEngine): Source { switch (url.protocol) { - case 'null:': return () => new NullTree(); - case 'empty:': return () => empty(); + case 'null:': + return () => new NullTree(); + case 'empty:': + return () => empty(); default: const hostSource = this._host.createSourceFromUrl(url, context); if (!hostSource) { @@ -360,25 +381,25 @@ export class SchematicEngine { const executors = new Map(); - const taskObservable = observableFrom(this._taskSchedulers) - .pipe( - concatMap(scheduler => scheduler.finalize()), - concatMap(task => { - const { name, options } = task.configuration; + const taskObservable = observableFrom(this._taskSchedulers).pipe( + concatMap((scheduler) => scheduler.finalize()), + concatMap((task) => { + const { name, options } = task.configuration; - const executor = executors.get(name); - if (executor) { - return executor(options, task.context); - } + const executor = executors.get(name); + if (executor) { + return executor(options, task.context); + } - return this._host.createTaskExecutor(name) - .pipe(concatMap(executor => { - executors.set(name, executor); + return this._host.createTaskExecutor(name).pipe( + concatMap((executor) => { + executors.set(name, executor); - return executor(options, task.context); - })); - }), - ); + return executor(options, task.context); + }), + ); + }), + ); return taskObservable; } diff --git a/packages/angular_devkit/schematics/src/engine/index.ts b/packages/angular_devkit/schematics/src/engine/index.ts index 9f8b476cc297..28afcc8f9f2f 100644 --- a/packages/angular_devkit/schematics/src/engine/index.ts +++ b/packages/angular_devkit/schematics/src/engine/index.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + export * from './engine'; export * from './interface'; export * from './schematic'; diff --git a/packages/angular_devkit/schematics/src/engine/interface.ts b/packages/angular_devkit/schematics/src/engine/interface.ts index dea18164acac..e0d788fa6387 100644 --- a/packages/angular_devkit/schematics/src/engine/interface.ts +++ b/packages/angular_devkit/schematics/src/engine/interface.ts @@ -1,17 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { analytics, logging } from '@angular-devkit/core'; + +import { logging } from '@angular-devkit/core'; import { Observable } from 'rxjs'; import { Url } from 'url'; import { FileEntry, MergeStrategy, Tree } from '../tree/interface'; import { Workflow } from '../workflow/interface'; - export interface TaskConfiguration { name: string; dependencies?: Array; @@ -22,8 +22,10 @@ export interface TaskConfigurationGenerator { toConfiguration(): TaskConfiguration; } -export type TaskExecutor - = (options: T | undefined, context: SchematicContext) => Promise | Observable; +export type TaskExecutor = ( + options: T | undefined, + context: SchematicContext, +) => Promise | Observable; export interface TaskExecutorFactory { readonly name: string; @@ -36,7 +38,7 @@ export interface TaskId { export interface TaskInfo { readonly id: number; - readonly priority: number; + readonly priority: number; readonly configuration: TaskConfiguration; readonly context: SchematicContext; } @@ -61,15 +63,16 @@ export type CollectionDescription = Collecti * needs to run. The SchematicMetadataT and CollectionMetadataT type parameters contain additional * metadata that you want to store while remaining type-safe. */ -export type SchematicDescription = SchematicMetadataT & { +export type SchematicDescription< + CollectionMetadataT extends object, + SchematicMetadataT extends object, +> = SchematicMetadataT & { readonly collection: CollectionDescription; readonly name: string; readonly private?: boolean; readonly hidden?: boolean; }; - /** * The Host for the Engine. Specifically, the piece of the tooling responsible for resolving * collections and schematics descriptions. The SchematicMetadataT and CollectionMetadataT type @@ -80,15 +83,19 @@ export interface EngineHost, ): CollectionDescription; - listSchematicNames(collection: CollectionDescription): string[]; + listSchematicNames( + collection: CollectionDescription, + includeHidden?: boolean, + ): string[]; createSchematicDescription( - name: string, - collection: CollectionDescription): - SchematicDescription | null; + name: string, + collection: CollectionDescription, + ): SchematicDescription | null; getSchematicRuleFactory( - schematic: SchematicDescription, - collection: CollectionDescription): RuleFactory; + schematic: SchematicDescription, + collection: CollectionDescription, + ): RuleFactory; createSourceFromUrl( url: Url, context: TypedSchematicContext, @@ -107,7 +114,6 @@ export interface EngineHost; - listSchematicNames(): string[]; + listSchematicNames(includeHidden?: boolean): string[]; } - /** * A Schematic as created by the Engine. This should be used by the tool to execute the main * schematics, or by rules to execute other schematics as well. @@ -180,41 +184,38 @@ export interface Schematic; } - /** * A SchematicContext. Contains information necessary for Schematics to execute some rules, for * example when using another schematics, as we need the engine and collection. */ -export interface TypedSchematicContext { +export interface TypedSchematicContext< + CollectionMetadataT extends object, + SchematicMetadataT extends object, +> { readonly debug: boolean; readonly engine: Engine; readonly logger: logging.LoggerApi; readonly schematic: Schematic; readonly strategy: MergeStrategy; readonly interactive: boolean; - addTask(task: TaskConfigurationGenerator, dependencies?: Array): TaskId; - - // This might be undefined if the feature is unsupported. - /** @deprecated since version 11 - as it's unused. */ - readonly analytics?: analytics.Analytics; + addTask( + task: TaskConfigurationGenerator, + dependencies?: Array, + ): TaskId; } - /** * This is used by the Schematics implementations in order to avoid needing to have typing from * the tooling. Schematics are not specific to a tool. */ export type SchematicContext = TypedSchematicContext<{}, {}>; - /** * A rule factory, which is normally the way schematics are implemented. Returned by the tooling * after loading a schematic description. */ export type RuleFactory = (options: T) => Rule; - /** * A FileOperator applies changes synchronously to a FileEntry. An async operator returns * asynchronously. We separate them so that the type system can catch early errors. @@ -222,7 +223,6 @@ export type RuleFactory = (options: T) => Rule; export type FileOperator = (entry: FileEntry) => FileEntry | null; export type AsyncFileOperator = (tree: FileEntry) => Observable; - /** * A source is a function that generates a Tree from a specific context. A rule transforms a tree * into another tree from a specific context. In both cases, an Observable can be returned if @@ -233,5 +233,7 @@ export type AsyncFileOperator = (tree: FileEntry) => Observable Tree | Observable; -export type Rule = (tree: Tree, context: SchematicContext) => - Tree | Observable | Rule | Promise | void; +export type Rule = ( + tree: Tree, + context: SchematicContext, +) => Tree | Observable | Rule | Promise | void; diff --git a/packages/angular_devkit/schematics/src/engine/schematic.ts b/packages/angular_devkit/schematics/src/engine/schematic.ts index ba16af352624..f98e46c69f9d 100644 --- a/packages/angular_devkit/schematics/src/engine/schematic.ts +++ b/packages/angular_devkit/schematics/src/engine/schematic.ts @@ -1,12 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BaseException } from '@angular-devkit/core'; -import { Observable, of as observableOf } from 'rxjs'; +import { Observable } from 'rxjs'; import { concatMap, first, map } from 'rxjs/operators'; import { callRule } from '../rules/call'; import { Tree } from '../tree/interface'; @@ -21,28 +22,32 @@ import { TypedSchematicContext, } from './interface'; - export class InvalidSchematicsNameException extends BaseException { constructor(name: string) { super(`Schematics has invalid name: "${name}".`); } } - export class SchematicImpl - implements Schematic { - - constructor(private _description: SchematicDescription, - private _factory: RuleFactory<{}>, - private _collection: Collection, - private _engine: Engine) { + implements Schematic +{ + constructor( + private _description: SchematicDescription, + private _factory: RuleFactory<{}>, + private _collection: Collection, + private _engine: Engine, + ) { if (!_description.name.match(/^[-@/_.a-zA-Z0-9]+$/)) { throw new InvalidSchematicsNameException(_description.name); } } - get description() { return this._description; } - get collection() { return this._collection; } + get description() { + return this._description; + } + get collection() { + return this._collection; + } call( options: OptionT, @@ -52,36 +57,37 @@ export class SchematicImpl { const context = this._engine.createContext(this, parentContext, executionOptions); - return host - .pipe( - first(), - concatMap(tree => this._engine.transformOptions(this, options, context).pipe( - map(o => [tree, o] as [Tree, OptionT]), - )), - concatMap(([tree, transformedOptions]) => { - let input: Tree; - let scoped = false; - if (executionOptions && executionOptions.scope) { - scoped = true; - input = new ScopedTree(tree, executionOptions.scope); - } else { - input = tree; - } + return host.pipe( + first(), + concatMap((tree) => + this._engine + .transformOptions(this, options, context) + .pipe(map((o) => [tree, o] as [Tree, OptionT])), + ), + concatMap(([tree, transformedOptions]) => { + let input: Tree; + let scoped = false; + if (executionOptions && executionOptions.scope) { + scoped = true; + input = new ScopedTree(tree, executionOptions.scope); + } else { + input = tree; + } - return callRule(this._factory(transformedOptions), observableOf(input), context).pipe( - map(output => { - if (output === input) { - return tree; - } else if (scoped) { - tree.merge(output); + return callRule(this._factory(transformedOptions), input, context).pipe( + map((output) => { + if (output === input) { + return tree; + } else if (scoped) { + tree.merge(output); - return tree; - } else { - return output; - } - }), - ); - }), - ); + return tree; + } else { + return output; + } + }), + ); + }), + ); } } diff --git a/packages/angular_devkit/schematics/src/engine/schematic_spec.ts b/packages/angular_devkit/schematics/src/engine/schematic_spec.ts index 5f16ed347644..4d69673f4e5d 100644 --- a/packages/angular_devkit/schematics/src/engine/schematic_spec.ts +++ b/packages/angular_devkit/schematics/src/engine/schematic_spec.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-non-null-assertion + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { logging } from '@angular-devkit/core'; import { of as observableOf } from 'rxjs'; import { chain } from '../rules/base'; @@ -14,7 +15,6 @@ import { branch, empty } from '../tree/static'; import { CollectionDescription, Engine, Rule, Schematic, SchematicDescription } from './interface'; import { SchematicImpl } from './schematic'; - type CollectionT = { description: string; }; @@ -30,27 +30,25 @@ const context = { logger: new logging.NullLogger(), strategy: MergeStrategy.Default, }; -const engine: Engine = { +const engine: Engine = ({ createContext: (schematic: Schematic<{}, {}>) => ({ engine, schematic, ...context }), transformOptions: (_: {}, options: {}) => observableOf(options), defaultMergeStrategy: MergeStrategy.Default, -} as {} as Engine; +} as {}) as Engine; const collection = { name: 'collection', description: 'description', } as CollectionDescription; - function files(tree: Tree) { const files: string[] = []; - tree.visit(x => files.push(x)); + tree.visit((x) => files.push(x)); return files; } - describe('Schematic', () => { - it('works with a rule', done => { + it('works with a rule', (done) => { let inner: Tree | null = null; const desc: SchematicDescription = { collection, @@ -65,17 +63,18 @@ describe('Schematic', () => { }, }; - const schematic = new SchematicImpl(desc, desc.factory, null !, engine); - schematic.call({}, observableOf(empty())) + const schematic = new SchematicImpl(desc, desc.factory, null!, engine); + schematic + .call({}, observableOf(empty())) .toPromise() - .then(x => { - expect(files(inner !)).toEqual([]); + .then((x) => { + expect(files(inner!)).toEqual([]); expect(files(x)).toEqual(['/a/b/c']); }) .then(done, done.fail); }); - it('works with a rule that returns an observable', done => { + it('works with a rule that returns an observable', (done) => { let inner: Tree | null = null; const desc: SchematicDescription = { collection, @@ -89,31 +88,35 @@ describe('Schematic', () => { }, }; - - const schematic = new SchematicImpl(desc, desc.factory, null !, engine); - schematic.call({}, observableOf(empty())) + const schematic = new SchematicImpl(desc, desc.factory, null!, engine); + schematic + .call({}, observableOf(empty())) .toPromise() - .then(x => { - expect(files(inner !)).toEqual([]); + .then((x) => { + expect(files(inner!)).toEqual([]); expect(files(x)).toEqual([]); expect(inner).not.toBe(x); }) .then(done, done.fail); }); - it('works with nested chained function rules', done => { + it('works with nested chained function rules', (done) => { let chainCount = 0; let oneCount = 0; let twoCount = 0; let threeCount = 0; const one = () => { return chain([ - () => { oneCount++; }, + () => { + oneCount++; + }, ]); }; const two = () => { return chain([ - () => { twoCount++; }, + () => { + twoCount++; + }, ]); }; const three = () => { @@ -127,7 +130,9 @@ describe('Schematic', () => { path: '/a/b/c', factory: () => { return chain([ - () => { chainCount++; }, + () => { + chainCount++; + }, one, two, three, @@ -135,10 +140,11 @@ describe('Schematic', () => { }, }; - const schematic = new SchematicImpl(desc, desc.factory, null !, engine); - schematic.call({}, observableOf(empty())) + const schematic = new SchematicImpl(desc, desc.factory, null!, engine); + schematic + .call({}, observableOf(empty())) .toPromise() - .then(_x => { + .then((_x) => { expect(chainCount).toBe(1); expect(oneCount).toBe(1); expect(twoCount).toBe(1); @@ -147,7 +153,7 @@ describe('Schematic', () => { .then(done, done.fail); }); - it('can be called with a scope', done => { + it('can be called with a scope', (done) => { const desc: SchematicDescription = { collection, name: 'test', @@ -158,13 +164,13 @@ describe('Schematic', () => { }, }; - const schematic = new SchematicImpl(desc, desc.factory, null !, engine); - schematic.call({}, observableOf(empty()), {}, { scope: 'base' }) + const schematic = new SchematicImpl(desc, desc.factory, null!, engine); + schematic + .call({}, observableOf(empty()), {}, { scope: 'base' }) .toPromise() - .then(x => { + .then((x) => { expect(files(x)).toEqual(['/base/a/b/c']); }) .then(done, done.fail); }); - }); diff --git a/packages/angular_devkit/schematics/src/exception/exception.ts b/packages/angular_devkit/schematics/src/exception/exception.ts index f7f419bfc145..29f3a418918a 100644 --- a/packages/angular_devkit/schematics/src/exception/exception.ts +++ b/packages/angular_devkit/schematics/src/exception/exception.ts @@ -1,23 +1,26 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { BaseException } from '@angular-devkit/core'; +import { BaseException } from '@angular-devkit/core'; // Used by schematics to throw exceptions. export class SchematicsException extends BaseException {} - // Exceptions export class FileDoesNotExistException extends BaseException { - constructor(path: string) { super(`Path "${path}" does not exist.`); } + constructor(path: string) { + super(`Path "${path}" does not exist.`); + } } export class FileAlreadyExistException extends BaseException { - constructor(path: string) { super(`Path "${path}" already exist.`); } + constructor(path: string) { + super(`Path "${path}" already exist.`); + } } export class ContentHasMutatedException extends BaseException { constructor(path: string) { @@ -25,7 +28,9 @@ export class ContentHasMutatedException extends BaseException { } } export class InvalidUpdateRecordException extends BaseException { - constructor() { super(`Invalid record instance.`); } + constructor() { + super(`Invalid record instance.`); + } } export class MergeConflictException extends BaseException { constructor(path: string) { @@ -40,5 +45,7 @@ export class UnsuccessfulWorkflowExecution extends BaseException { } export class UnimplementedException extends BaseException { - constructor() { super('This function is unimplemented.'); } + constructor() { + super('This function is unimplemented.'); + } } diff --git a/packages/angular_devkit/schematics/src/formats/format-validator.ts b/packages/angular_devkit/schematics/src/formats/format-validator.ts index 863a5b3e214a..6879f58d9b4d 100644 --- a/packages/angular_devkit/schematics/src/formats/format-validator.ts +++ b/packages/angular_devkit/schematics/src/formats/format-validator.ts @@ -1,15 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { JsonObject, JsonValue, schema } from '@angular-devkit/core'; import { Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; - export function formatValidator( data: JsonValue, dataSchema: JsonObject, @@ -21,7 +21,5 @@ export function formatValidator( registry.addFormat(format); } - return registry - .compile(dataSchema) - .pipe(mergeMap(validator => validator(data))); + return registry.compile(dataSchema).pipe(mergeMap((validator) => validator(data))); } diff --git a/packages/angular_devkit/schematics/src/formats/html-selector.ts b/packages/angular_devkit/schematics/src/formats/html-selector.ts index dbea9afbb627..aede3534799d 100644 --- a/packages/angular_devkit/schematics/src/formats/html-selector.ts +++ b/packages/angular_devkit/schematics/src/formats/html-selector.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { schema } from '@angular-devkit/core'; // As per https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name @@ -14,18 +15,18 @@ import { schema } from '@angular-devkit/core'; // NOTE: This should eventually be broken out into two formats: full and partial (allows for prefix) const unicodeRanges = [ - [0xC0, 0xD6], - [0xD8, 0xF6], - [0xF8, 0x37D], - [0x37F, 0x1FFF], - [0x200C, 0x200D], - [0x203F, 0x2040], - [0x2070, 0x218F], - [0x2C00, 0x2FEF], - [0x3001, 0xD7FF], - [0xF900, 0xFDCF], - [0xFDF0, 0xFFFD], - [0x10000, 0xEFFFF], + [0xc0, 0xd6], + [0xd8, 0xf6], + [0xf8, 0x37d], + [0x37f, 0x1fff], + [0x200c, 0x200d], + [0x203f, 0x2040], + [0x2070, 0x218f], + [0x2c00, 0x2fef], + [0x3001, 0xd7ff], + [0xf900, 0xfdcf], + [0xfdf0, 0xfffd], + [0x10000, 0xeffff], ]; function isValidElementName(name: string) { diff --git a/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts b/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts index 411d1df67df9..492521ab0fef 100644 --- a/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts +++ b/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts @@ -1,67 +1,73 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { map } from 'rxjs/operators'; import { formatValidator } from './format-validator'; import { htmlSelectorFormat } from './html-selector'; describe('Schematics HTML selector format', () => { - it('accepts correct selectors', done => { + it('accepts correct selectors', (done) => { const data = { selector: 'my-selector' }; const dataSchema = { properties: { selector: { type: 'string', format: 'html-selector' } }, }; formatValidator(data, dataSchema, [htmlSelectorFormat]) - .pipe(map(result => expect(result.success).toBe(true))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(true))) + .toPromise() + .then(done, done.fail); }); - it('rejects selectors starting with invalid characters', done => { + it('rejects selectors starting with invalid characters', (done) => { const data = { selector: 'my-selector$' }; const dataSchema = { properties: { selector: { type: 'string', format: 'html-selector' } }, }; formatValidator(data, dataSchema, [htmlSelectorFormat]) - .pipe(map(result => expect(result.success).toBe(false))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(false))) + .toPromise() + .then(done, done.fail); }); - it('rejects selectors starting with number', done => { + it('rejects selectors starting with number', (done) => { const data = { selector: '1selector' }; const dataSchema = { properties: { selector: { type: 'string', format: 'html-selector' } }, }; formatValidator(data, dataSchema, [htmlSelectorFormat]) - .pipe(map(result => expect(result.success).toBe(false))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(false))) + .toPromise() + .then(done, done.fail); }); - it('accepts selectors with non-letter after dash', done => { + it('accepts selectors with non-letter after dash', (done) => { const data = { selector: 'my-1selector' }; const dataSchema = { properties: { selector: { type: 'string', format: 'html-selector' } }, }; formatValidator(data, dataSchema, [htmlSelectorFormat]) - .pipe(map(result => expect(result.success).toBe(true))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(true))) + .toPromise() + .then(done, done.fail); }); - it('accepts selectors with unicode', done => { + it('accepts selectors with unicode', (done) => { const data = { selector: 'app-root😀' }; const dataSchema = { properties: { selector: { type: 'string', format: 'html-selector' } }, }; formatValidator(data, dataSchema, [htmlSelectorFormat]) - .pipe(map(result => expect(result.success).toBe(true))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(true))) + .toPromise() + .then(done, done.fail); }); }); diff --git a/packages/angular_devkit/schematics/src/formats/index.ts b/packages/angular_devkit/schematics/src/formats/index.ts index 2cfab19db670..ed061076c784 100644 --- a/packages/angular_devkit/schematics/src/formats/index.ts +++ b/packages/angular_devkit/schematics/src/formats/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -8,11 +8,9 @@ import { schema } from '@angular-devkit/core'; import { htmlSelectorFormat } from './html-selector'; -export { htmlSelectorFormat } from './html-selector'; import { pathFormat } from './path'; + +export { htmlSelectorFormat } from './html-selector'; export { pathFormat } from './path'; -export const standardFormats: schema.SchemaFormat[] = [ - htmlSelectorFormat, - pathFormat, -]; +export const standardFormats: schema.SchemaFormat[] = [htmlSelectorFormat, pathFormat]; diff --git a/packages/angular_devkit/schematics/src/formats/path.ts b/packages/angular_devkit/schematics/src/formats/path.ts index 0c854957f15a..30b103da325b 100644 --- a/packages/angular_devkit/schematics/src/formats/path.ts +++ b/packages/angular_devkit/schematics/src/formats/path.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -8,7 +8,6 @@ import { normalize, schema } from '@angular-devkit/core'; - export const pathFormat: schema.SchemaFormat = { name: 'path', formatter: { diff --git a/packages/angular_devkit/schematics/src/formats/path_spec.ts b/packages/angular_devkit/schematics/src/formats/path_spec.ts index 24901f08a72f..17aeaf806cd0 100644 --- a/packages/angular_devkit/schematics/src/formats/path_spec.ts +++ b/packages/angular_devkit/schematics/src/formats/path_spec.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license @@ -10,27 +10,28 @@ import { map } from 'rxjs/operators'; import { formatValidator } from './format-validator'; import { pathFormat } from './path'; - describe('Schematics Path format', () => { - it('accepts correct Paths', done => { + it('accepts correct Paths', (done) => { const data = { path: 'a/b/c' }; const dataSchema = { properties: { path: { type: 'string', format: 'path' } }, }; formatValidator(data, dataSchema, [pathFormat]) - .pipe(map(result => expect(result.success).toBe(true))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(true))) + .toPromise() + .then(done, done.fail); }); - it('rejects Paths that are not normalized', done => { + it('rejects Paths that are not normalized', (done) => { const data = { path: 'a/b/c/../' }; const dataSchema = { properties: { path: { type: 'string', format: 'path' } }, }; formatValidator(data, dataSchema, [pathFormat]) - .pipe(map(result => expect(result.success).toBe(false))) - .toPromise().then(done, done.fail); + .pipe(map((result) => expect(result.success).toBe(false))) + .toPromise() + .then(done, done.fail); }); }); diff --git a/packages/angular_devkit/schematics/src/index.ts b/packages/angular_devkit/schematics/src/index.ts index d0951eb363ad..337d3b55ab9e 100644 --- a/packages/angular_devkit/schematics/src/index.ts +++ b/packages/angular_devkit/schematics/src/index.ts @@ -1,13 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + +import { strings } from '@angular-devkit/core'; +import * as formats from './formats/index'; import { FilePredicate, MergeStrategy, Tree as TreeInterface } from './tree/interface'; import { branch, empty, merge, partition } from './tree/static'; +import * as workflow from './workflow/index'; export { SchematicsException } from './exception/exception'; @@ -30,12 +34,7 @@ export * from './engine/schematic'; export * from './sink/dryrun'; export * from './sink/host'; export * from './sink/sink'; - -import * as formats from './formats/index'; -export { formats }; - -import * as workflow from './workflow/index'; -export { workflow }; +export { formats, strings, workflow }; export interface TreeConstructor { empty(): TreeInterface; @@ -47,15 +46,23 @@ export interface TreeConstructor { export type Tree = TreeInterface; export const Tree: TreeConstructor = { - empty() { return empty(); }, - branch(tree: TreeInterface) { return branch(tree); }, - merge(tree: TreeInterface, - other: TreeInterface, - strategy: MergeStrategy = MergeStrategy.Default) { + empty() { + return empty(); + }, + branch(tree: TreeInterface) { + return branch(tree); + }, + merge( + tree: TreeInterface, + other: TreeInterface, + strategy: MergeStrategy = MergeStrategy.Default, + ) { return merge(tree, other, strategy); }, partition(tree: TreeInterface, predicate: FilePredicate) { return partition(tree, predicate); }, - optimize(tree: TreeInterface) { return tree; }, + optimize(tree: TreeInterface) { + return tree; + }, }; diff --git a/packages/angular_devkit/schematics/src/rules/base.ts b/packages/angular_devkit/schematics/src/rules/base.ts index d5c390026145..739be3940cbf 100644 --- a/packages/angular_devkit/schematics/src/rules/base.ts +++ b/packages/angular_devkit/schematics/src/rules/base.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Observable, concat } from 'rxjs'; import { map, mapTo, toArray } from 'rxjs/operators'; import { FileOperator, Rule, Source } from '../engine/interface'; @@ -12,7 +13,7 @@ import { SchematicsException } from '../exception/exception'; import { FilterHostTree, HostTree } from '../tree/host-tree'; import { FileEntry, FilePredicate, MergeStrategy, Tree } from '../tree/interface'; import { ScopedTree } from '../tree/scoped'; -import { empty as staticEmpty, partition } from '../tree/static'; +import { partition, empty as staticEmpty } from '../tree/static'; import { callRule, callSource } from './call'; /** @@ -32,9 +33,14 @@ export function empty(): Source { /** * Chain multiple rules into a single rule. */ -export function chain(rules: Rule[]): Rule { - return (tree, context) => { - return rules.reduce>((acc, curr) => callRule(curr, acc, context), tree); +export function chain(rules: Iterable | AsyncIterable): Rule { + return async (initialTree, context) => { + let intermediateTree: Observable | undefined; + for await (const rule of rules) { + intermediateTree = callRule(rule, intermediateTree ?? initialTree, context); + } + + return () => intermediateTree; }; } @@ -42,7 +48,7 @@ export function chain(rules: Rule[]): Rule { * Apply multiple rules to a source, and returns the source transformed. */ export function apply(source: Source, rules: Rule[]): Source { - return context => callRule(chain(rules), callSource(source, context), context); + return (context) => callRule(chain(rules), callSource(source, context), context); } /** @@ -51,7 +57,7 @@ export function apply(source: Source, rules: Rule[]): Source { export function mergeWith(source: Source, strategy: MergeStrategy = MergeStrategy.Default): Rule { return (tree, context) => { return callSource(source, context).pipe( - map(sourceTree => tree.merge(sourceTree, strategy || context.strategy)), + map((sourceTree) => tree.merge(sourceTree, strategy || context.strategy)), mapTo(tree), ); }; @@ -62,23 +68,23 @@ export function noop(): Rule { } export function filter(predicate: FilePredicate): Rule { - return ((tree: Tree) => { + return (tree: Tree) => { if (HostTree.isHostTree(tree)) { return new FilterHostTree(tree, predicate); } else { throw new SchematicsException('Tree type is not supported.'); } - }); + }; } export function asSource(rule: Rule): Source { - return context => callRule(rule, staticEmpty(), context); + return (context) => callRule(rule, staticEmpty(), context); } export function branchAndMerge(rule: Rule, strategy = MergeStrategy.Default): Rule { return (tree, context) => { return callRule(rule, tree.branch(), context).pipe( - map(branch => tree.merge(branch, strategy || context.strategy)), + map((branch) => tree.merge(branch, strategy || context.strategy)), mapTo(tree), ); }; @@ -102,10 +108,7 @@ export function partitionApplyMerge( return (tree, context) => { const [yes, no] = partition(tree, predicate); - return concat( - callRule(ruleYes, yes, context), - callRule(ruleNo || noop(), no, context), - ).pipe( + return concat(callRule(ruleYes, yes, context), callRule(ruleNo || noop(), no, context)).pipe( toArray(), map(([yesTree, noTree]) => { yesTree.merge(noTree, context.strategy); @@ -162,7 +165,7 @@ export function applyToSubtree(path: string, rules: Rule[]): Rule { const scoped = new ScopedTree(tree, path); return callRule(chain(rules), scoped, context).pipe( - map(result => { + map((result) => { if (result === scoped) { return tree; } else { diff --git a/packages/angular_devkit/schematics/src/rules/base_spec.ts b/packages/angular_devkit/schematics/src/rules/base_spec.ts index 3f199ab9dbef..9a687cb6c71e 100644 --- a/packages/angular_devkit/schematics/src/rules/base_spec.ts +++ b/packages/angular_devkit/schematics/src/rules/base_spec.ts @@ -1,18 +1,14 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-implicit-dependencies -// tslint:disable:no-non-null-assertion + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Path, virtualFs } from '@angular-devkit/core'; -import { - HostTree, - MergeStrategy, - partitionApplyMerge, -} from '@angular-devkit/schematics'; +import { HostTree, MergeStrategy, partitionApplyMerge } from '@angular-devkit/schematics'; import { of as observableOf } from 'rxjs'; import { Rule, SchematicContext, Source } from '../engine/interface'; import { Tree } from '../tree/interface'; @@ -21,16 +17,14 @@ import { apply, applyToSubtree, chain } from './base'; import { callRule, callSource } from './call'; import { move } from './move'; - const context: SchematicContext = { engine: null, debug: false, strategy: MergeStrategy.Default, } as {} as SchematicContext; - describe('chain', () => { - it('works with simple rules', done => { + it('works with simple rules', (done) => { const rulesCalled: Tree[] = []; const tree0 = empty(); @@ -38,13 +32,13 @@ describe('chain', () => { const tree2 = empty(); const tree3 = empty(); - const rule0: Rule = (tree: Tree) => (rulesCalled[0] = tree, tree1); - const rule1: Rule = (tree: Tree) => (rulesCalled[1] = tree, tree2); - const rule2: Rule = (tree: Tree) => (rulesCalled[2] = tree, tree3); + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); - callRule(chain([ rule0, rule1, rule2 ]), observableOf(tree0), context) + callRule(chain([rule0, rule1, rule2]), observableOf(tree0), context) .toPromise() - .then(result => { + .then((result) => { expect(result).not.toBe(tree0); expect(rulesCalled[0]).toBe(tree0); expect(rulesCalled[1]).toBe(tree1); @@ -54,7 +48,61 @@ describe('chain', () => { .then(done, done.fail); }); - it('works with observable rules', done => { + it('works with a sync generator of rules', async () => { + const rulesCalled: Tree[] = []; + + const tree0 = empty(); + const tree1 = empty(); + const tree2 = empty(); + const tree3 = empty(); + + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); + + function* generateRules() { + yield rule0; + yield rule1; + yield rule2; + } + + const result = await callRule(chain(generateRules()), tree0, context).toPromise(); + + expect(result).not.toBe(tree0); + expect(rulesCalled[0]).toBe(tree0); + expect(rulesCalled[1]).toBe(tree1); + expect(rulesCalled[2]).toBe(tree2); + expect(result).toBe(tree3); + }); + + it('works with an async generator of rules', async () => { + const rulesCalled: Tree[] = []; + + const tree0 = empty(); + const tree1 = empty(); + const tree2 = empty(); + const tree3 = empty(); + + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); + + async function* generateRules() { + yield rule0; + yield rule1; + yield rule2; + } + + const result = await callRule(chain(generateRules()), tree0, context).toPromise(); + + expect(result).not.toBe(tree0); + expect(rulesCalled[0]).toBe(tree0); + expect(rulesCalled[1]).toBe(tree1); + expect(rulesCalled[2]).toBe(tree2); + expect(result).toBe(tree3); + }); + + it('works with observable rules', (done) => { const rulesCalled: Tree[] = []; const tree0 = empty(); @@ -62,13 +110,13 @@ describe('chain', () => { const tree2 = empty(); const tree3 = empty(); - const rule0: Rule = (tree: Tree) => (rulesCalled[0] = tree, observableOf(tree1)); - const rule1: Rule = (tree: Tree) => (rulesCalled[1] = tree, observableOf(tree2)); - const rule2: Rule = (tree: Tree) => (rulesCalled[2] = tree, tree3); + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), observableOf(tree1)); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), observableOf(tree2)); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); - callRule(chain([ rule0, rule1, rule2 ]), observableOf(tree0), context) + callRule(chain([rule0, rule1, rule2]), observableOf(tree0), context) .toPromise() - .then(result => { + .then((result) => { expect(result).not.toBe(tree0); expect(rulesCalled[0]).toBe(tree0); expect(rulesCalled[1]).toBe(tree1); @@ -80,7 +128,7 @@ describe('chain', () => { }); describe('apply', () => { - it('works with simple rules', done => { + it('works with simple rules', (done) => { const rulesCalled: Tree[] = []; const tree0 = empty(); const tree1 = empty(); @@ -88,13 +136,13 @@ describe('apply', () => { const tree3 = empty(); const source: Source = () => tree0; - const rule0: Rule = (tree: Tree) => (rulesCalled[0] = tree, tree1); - const rule1: Rule = (tree: Tree) => (rulesCalled[1] = tree, tree2); - const rule2: Rule = (tree: Tree) => (rulesCalled[2] = tree, tree3); + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), tree1); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), tree2); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); - callSource(apply(source, [ rule0, rule1, rule2 ]), context) + callSource(apply(source, [rule0, rule1, rule2]), context) .toPromise() - .then(result => { + .then((result) => { expect(result).not.toBe(tree0); expect(rulesCalled[0]).toBe(tree0); expect(rulesCalled[1]).toBe(tree1); @@ -104,7 +152,7 @@ describe('apply', () => { .then(done, done.fail); }); - it('works with observable rules', done => { + it('works with observable rules', (done) => { const rulesCalled: Tree[] = []; const tree0 = empty(); const tree1 = empty(); @@ -112,13 +160,13 @@ describe('apply', () => { const tree3 = empty(); const source: Source = () => tree0; - const rule0: Rule = (tree: Tree) => (rulesCalled[0] = tree, observableOf(tree1)); - const rule1: Rule = (tree: Tree) => (rulesCalled[1] = tree, observableOf(tree2)); - const rule2: Rule = (tree: Tree) => (rulesCalled[2] = tree, tree3); + const rule0: Rule = (tree: Tree) => ((rulesCalled[0] = tree), observableOf(tree1)); + const rule1: Rule = (tree: Tree) => ((rulesCalled[1] = tree), observableOf(tree2)); + const rule2: Rule = (tree: Tree) => ((rulesCalled[2] = tree), tree3); - callSource(apply(source, [ rule0, rule1, rule2 ]), context) + callSource(apply(source, [rule0, rule1, rule2]), context) .toPromise() - .then(result => { + .then((result) => { expect(result).not.toBe(tree0); expect(rulesCalled[0]).toBe(tree0); expect(rulesCalled[1]).toBe(tree1); @@ -130,7 +178,7 @@ describe('apply', () => { }); describe('partitionApplyMerge', () => { - it('works with simple rules', done => { + it('works with simple rules', (done) => { const host = new virtualFs.test.TestHost({ '/test1': '', '/test2': '', @@ -153,7 +201,7 @@ describe('partitionApplyMerge', () => { callRule(partitionApplyMerge(predicate, ruleYes, ruleNo), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('/test1')).toBe(false); expect(result.exists('/test2')).toBe(false); }) @@ -162,7 +210,7 @@ describe('partitionApplyMerge', () => { }); describe('applyToSubtree', () => { - it('works', done => { + it('works', (done) => { const tree = new HostTree(); tree.create('a/b/file1', 'hello world'); tree.create('a/b/file2', 'hello world'); @@ -170,7 +218,7 @@ describe('applyToSubtree', () => { callRule(applyToSubtree('a/b', [move('x')]), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('a/b/x/file1')).toBe(true); expect(result.exists('a/b/x/file2')).toBe(true); expect(result.exists('a/c/file3')).toBe(true); diff --git a/packages/angular_devkit/schematics/src/rules/call.ts b/packages/angular_devkit/schematics/src/rules/call.ts index a9f2762e3c96..346517f98cef 100644 --- a/packages/angular_devkit/schematics/src/rules/call.ts +++ b/packages/angular_devkit/schematics/src/rules/call.ts @@ -1,17 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { BaseException, isPromise } from '@angular-devkit/core'; -import { Observable, from, isObservable, of as observableOf, throwError } from 'rxjs'; -import { defaultIfEmpty, last, mergeMap, tap } from 'rxjs/operators'; + +import { BaseException } from '@angular-devkit/core'; +import { Observable, defer, isObservable } from 'rxjs'; +import { defaultIfEmpty, mergeMap } from 'rxjs/operators'; import { Rule, SchematicContext, Source } from '../engine/interface'; import { Tree, TreeSymbol } from '../tree/interface'; - function _getTypeOfResult(value?: {}): string { if (value === undefined) { return 'undefined'; @@ -32,7 +32,6 @@ function _getTypeOfResult(value?: {}): string { } } - /** * When a rule or source returns an invalid value. */ @@ -42,75 +41,59 @@ export class InvalidRuleResultException extends BaseException { } } - export class InvalidSourceResultException extends BaseException { constructor(value?: {}) { super(`Invalid source result: ${_getTypeOfResult(value)}.`); } } - export function callSource(source: Source, context: SchematicContext): Observable { - const result = source(context); + return defer(async () => { + let result = source(context); - if (isObservable(result)) { - // Only return the last Tree, and make sure it's a Tree. - return result.pipe( - defaultIfEmpty(), - last(), - tap(inner => { - if (!inner || !(TreeSymbol in inner)) { - throw new InvalidSourceResultException(inner); - } - }), - ); - } else if (result && TreeSymbol in result) { - return observableOf(result); - } else { - return throwError(new InvalidSourceResultException(result)); - } -} + if (isObservable(result)) { + result = await result.pipe(defaultIfEmpty()).toPromise(); + } + if (result && TreeSymbol in result) { + return result as Tree; + } + + throw new InvalidSourceResultException(result); + }); +} export function callRule( rule: Rule, input: Tree | Observable, context: SchematicContext, ): Observable { - return (isObservable(input) ? input : observableOf(input)).pipe(mergeMap(inputTree => { - const result = rule(inputTree, context); + if (isObservable(input)) { + return input.pipe(mergeMap((inputTree) => callRuleAsync(rule, inputTree, context))); + } else { + return defer(() => callRuleAsync(rule, input, context)); + } +} - if (!result) { - return observableOf(inputTree); - } else if (typeof result == 'function') { - // This is considered a Rule, chain the rule and return its output. - return callRule(result, inputTree, context); - } else if (isObservable(result)) { - // Only return the last Tree, and make sure it's a Tree. - return result.pipe( - defaultIfEmpty(), - last(), - tap(inner => { - if (!inner || !(TreeSymbol in inner)) { - throw new InvalidRuleResultException(inner); - } - }), - ); - } else if (isPromise(result)) { - return from(result).pipe( - mergeMap(inner => { - if (typeof inner === 'function') { - // This is considered a Rule, chain the rule and return its output. - return callRule(inner, inputTree, context); - } else { - return observableOf(inputTree); - } - }), - ); - } else if (TreeSymbol in result) { - return observableOf(result); - } else { - return throwError(new InvalidRuleResultException(result)); - } - })); +async function callRuleAsync(rule: Rule, tree: Tree, context: SchematicContext): Promise { + let result = await rule(tree, context); + + while (typeof result === 'function') { + // This is considered a Rule, chain the rule and return its output. + result = await result(tree, context); + } + + if (typeof result === 'undefined') { + return tree; + } + + if (isObservable(result)) { + result = await result.pipe(defaultIfEmpty(tree)).toPromise(); + } + + if (result && TreeSymbol in result) { + return result as Tree; + } + + throw new InvalidRuleResultException(result); } diff --git a/packages/angular_devkit/schematics/src/rules/call_spec.ts b/packages/angular_devkit/schematics/src/rules/call_spec.ts index b4ec2d4b43e5..a8c8ea5896af 100644 --- a/packages/angular_devkit/schematics/src/rules/call_spec.ts +++ b/packages/angular_devkit/schematics/src/rules/call_spec.ts @@ -1,13 +1,13 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-non-null-assertion -// tslint:disable:no-any -// tslint:disable:no-implicit-dependencies + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { MergeStrategy } from '@angular-devkit/schematics'; import { of as observableOf } from 'rxjs'; import { Rule, SchematicContext, Source } from '../engine/interface'; @@ -20,67 +20,74 @@ import { callSource, } from './call'; - const context: SchematicContext = { engine: null, debug: false, strategy: MergeStrategy.Default, } as {} as SchematicContext; - describe('callSource', () => { - it('errors if undefined source', done => { + it('errors if undefined source', (done) => { const source0: any = () => undefined; callSource(source0, context) .toPromise() - .then(() => done.fail(), err => { - expect(err).toEqual(new InvalidSourceResultException()); - }) + .then( + () => done.fail(), + (err) => { + expect(err).toEqual(new InvalidSourceResultException()); + }, + ) .then(done, done.fail); }); - it('errors if invalid source object', done => { + it('errors if invalid source object', (done) => { const source0: Source = () => ({} as Tree); callSource(source0, context) .toPromise() - .then(() => done.fail(), err => { - expect(err).toEqual(new InvalidSourceResultException({})); - }) + .then( + () => done.fail(), + (err) => { + expect(err).toEqual(new InvalidSourceResultException({})); + }, + ) .then(done, done.fail); }); - it('errors if Observable of invalid source object', done => { + it('errors if Observable of invalid source object', (done) => { const source0: Source = () => observableOf({} as Tree); callSource(source0, context) .toPromise() - .then(() => done.fail(), err => { - expect(err).toEqual(new InvalidSourceResultException({})); - }) + .then( + () => done.fail(), + (err) => { + expect(err).toEqual(new InvalidSourceResultException({})); + }, + ) .then(done, done.fail); }); - it('works with a Tree', done => { + it('works with a Tree', (done) => { const tree0 = empty(); const source0: Source = () => tree0; callSource(source0, context) .toPromise() - .then(tree => { + .then((tree) => { expect(tree).toBe(tree0); }) .then(done, done.fail); }); - it('works with an Observable', done => { + it('works with an Observable', (done) => { const tree0 = empty(); const source0: Source = () => observableOf(tree0); callSource(source0, context) .toPromise() - .then(tree => { + .then((tree) => { expect(tree).toBe(tree0); }) .then(done, done.fail); @@ -88,61 +95,53 @@ describe('callSource', () => { }); describe('callRule', () => { - it('errors if invalid source object', done => { - const tree0 = observableOf(empty()); + it('should throw InvalidRuleResultException when rule result is non-Tree object', async () => { const rule0: Rule = () => ({} as Tree); - callRule(rule0, tree0, context) - .toPromise() - .then(() => done.fail(), err => { - expect(err).toEqual(new InvalidRuleResultException({})); - }) - .then(done, done.fail); + await expectAsync(callRule(rule0, empty(), context).toPromise()).toBeRejectedWithError( + InvalidRuleResultException, + ); }); - it('errors if Observable of invalid source object', done => { - const tree0 = observableOf(empty()); - const rule0: Rule = () => observableOf({} as Tree); + it('should throw InvalidRuleResultException when rule result is null', async () => { + const rule0: Rule = () => null as unknown as Tree; - callRule(rule0, tree0, context) - .toPromise() - .then(() => done.fail(), err => { - expect(err).toEqual(new InvalidRuleResultException({})); - }) - .then(done, done.fail); + await expectAsync(callRule(rule0, empty(), context).toPromise()).toBeRejectedWithError( + InvalidRuleResultException, + ); }); - it('works with undefined result', done => { + it('works with undefined result', (done) => { const tree0 = empty(); const rule0: Rule = () => undefined; callRule(rule0, observableOf(tree0), context) .toPromise() - .then(tree => { + .then((tree) => { expect(tree).toBe(tree0); }) .then(done, done.fail); }); - it('works with a Tree', done => { + it('works with a Tree', (done) => { const tree0 = empty(); const rule0: Rule = () => tree0; callRule(rule0, observableOf(tree0), context) .toPromise() - .then(tree => { + .then((tree) => { expect(tree).toBe(tree0); }) .then(done, done.fail); }); - it('works with an Observable', done => { + it('works with an Observable', (done) => { const tree0 = empty(); const rule0: Rule = () => observableOf(tree0); callRule(rule0, observableOf(tree0), context) .toPromise() - .then(tree => { + .then((tree) => { expect(tree).toBe(tree0); }) .then(done, done.fail); diff --git a/packages/angular_devkit/schematics/src/rules/move.ts b/packages/angular_devkit/schematics/src/rules/move.ts index 211d00f01837..e753926ad8b3 100644 --- a/packages/angular_devkit/schematics/src/rules/move.ts +++ b/packages/angular_devkit/schematics/src/rules/move.ts @@ -1,15 +1,15 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { join, normalize } from '@angular-devkit/core'; import { Rule } from '../engine/interface'; import { noop } from './base'; - export function move(from: string, to?: string): Rule { if (to === undefined) { to = from; @@ -23,14 +23,14 @@ export function move(from: string, to?: string): Rule { return noop; } - return tree => { + return (tree) => { if (tree.exists(fromPath)) { // fromPath is a file tree.rename(fromPath, toPath); } else { // fromPath is a directory - tree.getDir(fromPath).visit(path => { - tree.rename(path, join(toPath, path.substr(fromPath.length))); + tree.getDir(fromPath).visit((path) => { + tree.rename(path, join(toPath, path.slice(fromPath.length))); }); } diff --git a/packages/angular_devkit/schematics/src/rules/move_spec.ts b/packages/angular_devkit/schematics/src/rules/move_spec.ts index 96bfcc896331..7854eb050b43 100644 --- a/packages/angular_devkit/schematics/src/rules/move_spec.ts +++ b/packages/angular_devkit/schematics/src/rules/move_spec.ts @@ -1,23 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-non-null-assertion + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { of as observableOf } from 'rxjs'; import { SchematicContext } from '../engine/interface'; import { HostTree } from '../tree/host-tree'; import { callRule } from './call'; import { move } from './move'; - -const context: SchematicContext = null !; - +const context: SchematicContext = null!; describe('move', () => { - it('works on moving the whole structure', done => { + it('works on moving the whole structure', (done) => { const tree = new HostTree(); tree.create('a/b/file1', 'hello world'); tree.create('a/b/file2', 'hello world'); @@ -25,7 +24,7 @@ describe('move', () => { callRule(move('sub'), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('sub/a/b/file1')).toBe(true); expect(result.exists('sub/a/b/file2')).toBe(true); expect(result.exists('sub/a/c/file3')).toBe(true); @@ -33,7 +32,7 @@ describe('move', () => { .then(done, done.fail); }); - it('works on moving a subdirectory structure', done => { + it('works on moving a subdirectory structure', (done) => { const tree = new HostTree(); tree.create('a/b/file1', 'hello world'); tree.create('a/b/file2', 'hello world'); @@ -41,7 +40,7 @@ describe('move', () => { callRule(move('a/b', 'sub'), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('sub/file1')).toBe(true); expect(result.exists('sub/file2')).toBe(true); expect(result.exists('a/c/file3')).toBe(true); @@ -49,7 +48,7 @@ describe('move', () => { .then(done, done.fail); }); - it('works on moving a directory into a subdirectory of itself', done => { + it('works on moving a directory into a subdirectory of itself', (done) => { const tree = new HostTree(); tree.create('a/b/file1', 'hello world'); tree.create('a/b/file2', 'hello world'); @@ -57,7 +56,7 @@ describe('move', () => { callRule(move('a/b', 'a/b/c'), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('a/b/c/file1')).toBe(true); expect(result.exists('a/b/c/file2')).toBe(true); expect(result.exists('a/c/file3')).toBe(true); @@ -65,7 +64,7 @@ describe('move', () => { .then(done, done.fail); }); - it('works on moving a directory into a parent of itself', done => { + it('works on moving a directory into a parent of itself', (done) => { const tree = new HostTree(); tree.create('a/b/file1', 'hello world'); tree.create('a/b/file2', 'hello world'); @@ -73,7 +72,7 @@ describe('move', () => { callRule(move('a/b', 'a'), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('file1')).toBe(false); expect(result.exists('file2')).toBe(false); expect(result.exists('a/file1')).toBe(true); @@ -83,7 +82,7 @@ describe('move', () => { .then(done, done.fail); }); - it('becomes a noop with identical from and to', done => { + it('becomes a noop with identical from and to', (done) => { const tree = new HostTree(); tree.create('a/b/file1', 'hello world'); tree.create('a/b/file2', 'hello world'); @@ -91,7 +90,7 @@ describe('move', () => { callRule(move(''), observableOf(tree), context) .toPromise() - .then(result => { + .then((result) => { expect(result.exists('a/b/file1')).toBe(true); expect(result.exists('a/b/file2')).toBe(true); expect(result.exists('a/c/file3')).toBe(true); diff --git a/packages/angular_devkit/schematics/src/rules/random.ts b/packages/angular_devkit/schematics/src/rules/random.ts index a2575f57a5b0..13dac8b40a58 100644 --- a/packages/angular_devkit/schematics/src/rules/random.ts +++ b/packages/angular_devkit/schematics/src/rules/random.ts @@ -1,41 +1,44 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { Source } from '../engine/interface'; import { HostTree } from '../tree/host-tree'; - function generateStringOfLength(l: number) { - return new Array(l).fill(0).map(_x => { - return 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]; - }).join(''); + return new Array(l) + .fill(0) + .map((_x) => { + return 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]; + }) + .join(''); } - function random(from: number, to: number) { return Math.floor(Math.random() * (to - from)) + from; } - export interface RandomOptions { root?: string; multi?: boolean | number; multiFiles?: boolean | number; } - -export default function(options: RandomOptions): Source { +export default function (options: RandomOptions): Source { return () => { - const root = ('root' in options) ? options.root : '/'; + const root = 'root' in options ? options.root : '/'; const map = new HostTree(); - const nbFiles = ('multiFiles' in options) - ? (typeof options.multiFiles == 'number' ? options.multiFiles : random(2, 12)) - : 1; + const nbFiles = + 'multiFiles' in options + ? typeof options.multiFiles == 'number' + ? options.multiFiles + : random(2, 12) + : 1; for (let i = 0; i < nbFiles; i++) { const path = 'a/b/c/d/e/f'.slice(Math.random() * 10); diff --git a/packages/angular_devkit/schematics/src/rules/rename.ts b/packages/angular_devkit/schematics/src/rules/rename.ts deleted file mode 100644 index 2405d5302273..000000000000 --- a/packages/angular_devkit/schematics/src/rules/rename.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { normalize } from '@angular-devkit/core'; -import { Rule } from '../engine/interface'; -import { FilePredicate } from '../tree/interface'; -import { forEach } from './base'; - - -export function rename(match: FilePredicate, to: FilePredicate): Rule { - return forEach(entry => { - if (match(entry.path, entry)) { - return { - content: entry.content, - path: normalize(to(entry.path, entry)), - }; - } else { - return entry; - } - }); -} diff --git a/packages/angular_devkit/schematics/src/rules/rename_spec.ts b/packages/angular_devkit/schematics/src/rules/rename_spec.ts deleted file mode 100644 index c9fdda3cdd4a..000000000000 --- a/packages/angular_devkit/schematics/src/rules/rename_spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -// tslint:disable:no-non-null-assertion -import { of as observableOf } from 'rxjs'; -import { SchematicContext } from '../engine/interface'; -import { HostTree } from '../tree/host-tree'; -import { callRule } from './call'; -import { rename } from './rename'; - - -const context: SchematicContext = null !; - - -describe('rename', () => { - it('works', done => { - const tree = new HostTree(); - tree.create('a/b/file1', 'hello world'); - tree.create('a/b/file2', 'hello world'); - tree.create('a/c/file3', 'hello world'); - - let i = 0; - - // Rename all files that contain 'b' to 'hello'. - callRule(rename(x => !!x.match(/b/), () => 'hello' + (i++)), observableOf(tree), context) - .toPromise() - .then(result => { - expect(result.exists('a/b/file1')).toBe(false); - expect(result.exists('a/b/file2')).toBe(false); - expect(result.exists('hello0')).toBe(true); - expect(result.exists('hello1')).toBe(true); - expect(result.exists('a/c/file3')).toBe(true); - }) - .then(done, done.fail); - }); - - it('works (2)', done => { - const tree = new HostTree(); - tree.create('a/b/file1', 'hello world'); - tree.create('a/b/file2', 'hello world'); - tree.create('a/c/file3', 'hello world'); - - let i = 0; - - // Rename all files that contain 'b' to 'hello'. - callRule(rename(x => !!x.match(/b/), x => x + (i++)), observableOf(tree), context) - .toPromise() - .then(result => { - expect(result.exists('a/b/file1')).toBe(false); - expect(result.exists('a/b/file2')).toBe(false); - expect(result.exists('a/b/file10')).toBe(true); - expect(result.exists('a/b/file21')).toBe(true); - expect(result.exists('a/c/file3')).toBe(true); - }) - .then(done, done.fail); - }); -}); diff --git a/packages/angular_devkit/schematics/src/rules/schematic.ts b/packages/angular_devkit/schematics/src/rules/schematic.ts index 22002c131ff6..f7f2deb75319 100644 --- a/packages/angular_devkit/schematics/src/rules/schematic.ts +++ b/packages/angular_devkit/schematics/src/rules/schematic.ts @@ -1,17 +1,17 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { of as observableOf } from 'rxjs'; import { last, map } from 'rxjs/operators'; import { ExecutionOptions, Rule, SchematicContext } from '../engine/interface'; import { MergeStrategy, Tree } from '../tree/interface'; import { branch } from '../tree/static'; - /** * Run a schematic from a separate collection. * @@ -26,12 +26,15 @@ export function externalSchematic( executionOptions?: Partial, ): Rule { return (input: Tree, context: SchematicContext) => { - const collection = context.engine.createCollection(collectionName, context.schematic.collection); + const collection = context.engine.createCollection( + collectionName, + context.schematic.collection, + ); const schematic = collection.createSchematic(schematicName); return schematic.call(options, observableOf(branch(input)), context, executionOptions).pipe( last(), - map(x => { + map((x) => { input.merge(x, MergeStrategy.AllowOverwriteConflict); return input; @@ -40,7 +43,6 @@ export function externalSchematic( }; } - /** * Run a schematic from the same collection. * @@ -58,7 +60,7 @@ export function schematic( return schematic.call(options, observableOf(branch(input)), context, executionOptions).pipe( last(), - map(x => { + map((x) => { // We allow overwrite conflict here because they're the only merge conflict we particularly // don't want to deal with; the input tree might have an OVERWRITE which the sub input.merge(x, MergeStrategy.AllowOverwriteConflict); diff --git a/packages/angular_devkit/schematics/src/rules/template.ts b/packages/angular_devkit/schematics/src/rules/template.ts index 5b34acf90444..a45bcbb494b1 100644 --- a/packages/angular_devkit/schematics/src/rules/template.ts +++ b/packages/angular_devkit/schematics/src/rules/template.ts @@ -1,43 +1,43 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { BaseException, normalize, template as templateImpl } from '@angular-devkit/core'; import { TextDecoder } from 'util'; import { FileOperator, Rule } from '../engine/interface'; import { FileEntry } from '../tree/interface'; import { chain, composeFileOperators, forEach, when } from './base'; -import { rename } from './rename'; - export const TEMPLATE_FILENAME_RE = /\.template$/; - export class OptionIsNotDefinedException extends BaseException { - constructor(name: string) { super(`Option "${name}" is not defined.`); } + constructor(name: string) { + super(`Option "${name}" is not defined.`); + } } - export class UnknownPipeException extends BaseException { - constructor(name: string) { super(`Pipe "${name}" is not defined.`); } + constructor(name: string) { + super(`Pipe "${name}" is not defined.`); + } } - export class InvalidPipeException extends BaseException { - constructor(name: string) { super(`Pipe "${name}" is invalid.`); } + constructor(name: string) { + super(`Pipe "${name}" is invalid.`); + } } - export type PathTemplateValue = boolean | string | number | undefined; export type PathTemplatePipeFunction = (x: string) => PathTemplateValue; export type PathTemplateData = { - [key: string]: PathTemplateValue | PathTemplateData | PathTemplatePipeFunction, + [key: string]: PathTemplateValue | PathTemplateData | PathTemplatePipeFunction; }; - export interface PathTemplateOptions { // Interpolation start and end strings. interpolationStart: string; @@ -62,7 +62,7 @@ export function applyContentTemplate(options: T): FileOperator { content: Buffer.from(templateImpl(decodedContent, {})(options)), }; } catch (e) { - if (e.code === 'ERR_ENCODING_INVALID_ENCODED_DATA') { + if ((e as NodeJS.ErrnoException).code === 'ERR_ENCODING_INVALID_ENCODED_DATA') { return entry; } @@ -71,12 +71,10 @@ export function applyContentTemplate(options: T): FileOperator { }; } - export function contentTemplate(options: T): Rule { return forEach(applyContentTemplate(options)); } - export function applyPathTemplate( data: T, options: PathTemplateOptions = { @@ -150,24 +148,27 @@ export function applyPathTemplate( }; } - export function pathTemplate(options: T): Rule { return forEach(applyPathTemplate(options)); } - /** * Remove every `.template` suffix from file names. */ export function renameTemplateFiles(): Rule { - return rename( - path => !!path.match(TEMPLATE_FILENAME_RE), - path => path.replace(TEMPLATE_FILENAME_RE, ''), - ); + return forEach((entry) => { + if (entry.path.match(TEMPLATE_FILENAME_RE)) { + return { + content: entry.content, + path: normalize(entry.path.replace(TEMPLATE_FILENAME_RE, '')), + }; + } else { + return entry; + } + }); } - -export function template(options: T): Rule { +export function template(options: T): Rule { return chain([ contentTemplate(options), // Force cast to PathTemplateData. We need the type for the actual pathTemplate() call, @@ -177,16 +178,15 @@ export function template(options: T): Rule { ]); } - -export function applyTemplates(options: T): Rule { +export function applyTemplates(options: T): Rule { return forEach( when( - path => path.endsWith('.template'), + (path) => path.endsWith('.template'), composeFileOperators([ applyContentTemplate(options), // See above for this weird cast. applyPathTemplate(options as {} as PathTemplateData), - entry => { + (entry) => { return { content: entry.content, path: entry.path.replace(TEMPLATE_FILENAME_RE, ''), diff --git a/packages/angular_devkit/schematics/src/rules/template_spec.ts b/packages/angular_devkit/schematics/src/rules/template_spec.ts index 56e99f9a90dd..2bafab94dd9c 100644 --- a/packages/angular_devkit/schematics/src/rules/template_spec.ts +++ b/packages/angular_devkit/schematics/src/rules/template_spec.ts @@ -1,11 +1,12 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -// tslint:disable:no-implicit-dependencies + +/* eslint-disable import/no-extraneous-dependencies */ import { normalize } from '@angular-devkit/core'; import { UnitTestTree } from '@angular-devkit/schematics/testing'; import { of as observableOf } from 'rxjs'; @@ -22,7 +23,6 @@ import { applyTemplates, } from './template'; - function _entry(path?: string, content?: string): FileEntry { if (!path) { path = 'a/b/c'; @@ -37,7 +37,6 @@ function _entry(path?: string, content?: string): FileEntry { }; } - describe('applyPathTemplate', () => { function _applyPathTemplate(path: string, options: {}): string | null { const newEntry = applyPathTemplate(options)(_entry(path)); @@ -61,26 +60,29 @@ describe('applyPathTemplate', () => { }); it('works with complex _________...', () => { - expect(_applyPathTemplate( - '/_____' + 'a' + '___a__' + '_/' + '__a___/__b___', - { _a: 0, a: 1, b: 2, _: '.' }, - )).toBe('/.a0_/1_/2_'); - - expect(_applyPathTemplate( - '_____' + '_____' + '_____' + '___', - { _: '.' }, - )).toBe('...___'); + expect( + _applyPathTemplate('/_____' + 'a' + '___a__' + '_/' + '__a___/__b___', { + _a: 0, + a: 1, + b: 2, + _: '.', + }), + ).toBe('/.a0_/1_/2_'); + + expect(_applyPathTemplate('_____' + '_____' + '_____' + '___', { _: '.' })).toBe('...___'); }); it('works with functions', () => { let arg = ''; - expect(_applyPathTemplate('/a__c__b', { - c: (x: string) => { - arg = x; - - return 'hello'; - }, - })).toBe('/ahellob'); + expect( + _applyPathTemplate('/a__c__b', { + c: (x: string) => { + arg = x; + + return 'hello'; + }, + }), + ).toBe('/ahellob'); expect(arg).toBe('/a__c__b'); }); @@ -88,17 +90,21 @@ describe('applyPathTemplate', () => { let called = ''; let called2 = ''; - expect(_applyPathTemplate('/a__c@d__b', { - c: 1, - d: (x: string) => (called = x, 2), - })).toBe('/a2b'); + expect( + _applyPathTemplate('/a__c@d__b', { + c: 1, + d: (x: string) => ((called = x), 2), + }), + ).toBe('/a2b'); expect(called).toBe('1'); - expect(_applyPathTemplate('/a__c@d@e__b', { - c: 10, - d: (x: string) => (called = x, 20), - e: (x: string) => (called2 = x, 30), - })).toBe('/a30b'); + expect( + _applyPathTemplate('/a__c@d@e__b', { + c: 10, + d: (x: string) => ((called = x), 20), + e: (x: string) => ((called2 = x), 30), + }), + ).toBe('/a30b'); expect(called).toBe('10'); expect(called2).toBe('20'); }); @@ -109,12 +115,12 @@ describe('applyPathTemplate', () => { it('errors out on undefined or invalid pipes', () => { expect(() => _applyPathTemplate('/a__b@d__c', { b: 1 })).toThrow(new UnknownPipeException('d')); - expect(() => _applyPathTemplate('/a__b@d__c', { b: 1, d: 1 })) - .toThrow(new InvalidPipeException('d')); + expect(() => _applyPathTemplate('/a__b@d__c', { b: 1, d: 1 })).toThrow( + new InvalidPipeException('d'), + ); }); }); - describe('contentTemplate', () => { function _applyContentTemplate(content: string, options: {}) { const newEntry = applyContentTemplate(options)(_entry('', content)); @@ -130,38 +136,45 @@ describe('contentTemplate', () => { }); it('works with if', () => { - expect(_applyContentTemplate('a<% if (a) { %>b<% } %>c', { - value: 123, - a: true, - })).toBe('abc'); - expect(_applyContentTemplate('a<% if (a) { %>b<% } %>c', { - value: 123, - a: false, - })).toBe('ac'); + expect( + _applyContentTemplate('a<% if (a) { %>b<% } %>c', { + value: 123, + a: true, + }), + ).toBe('abc'); + expect( + _applyContentTemplate('a<% if (a) { %>b<% } %>c', { + value: 123, + a: false, + }), + ).toBe('ac'); }); it('works with for', () => { - expect(_applyContentTemplate('a<% for (let i = 0; i < value; i++) { %>1<% } %>b', { - value: 5, - })).toBe('a11111b'); + expect( + _applyContentTemplate('a<% for (let i = 0; i < value; i++) { %>1<% } %>b', { + value: 5, + }), + ).toBe('a11111b'); }); it('escapes HTML', () => { - expect(_applyContentTemplate('a<%- html %>b', { - html: ' - - - - - - `, + [ + '', + '', + '', + '', + '', + '', + ].join(''), ); } diff --git a/tests/legacy-cli/e2e/tests/basic/serve.ts b/tests/legacy-cli/e2e/tests/basic/serve.ts index 03457450cae8..f0e893f2c538 100644 --- a/tests/legacy-cli/e2e/tests/basic/serve.ts +++ b/tests/legacy-cli/e2e/tests/basic/serve.ts @@ -1,26 +1,26 @@ -import { request } from '../../utils/http'; +import fetch from 'node-fetch'; import { killAllProcesses } from '../../utils/process'; import { ngServe } from '../../utils/project'; export default async function () { try { // Serve works without HMR - await ngServe('--no-hmr'); - await verifyResponse(); - killAllProcesses(); + const noHmrPort = await ngServe('--no-hmr'); + await verifyResponse(noHmrPort); + await killAllProcesses(); // Serve works with HMR - await ngServe('--hmr'); - await verifyResponse(); + const hmrPort = await ngServe('--hmr'); + await verifyResponse(hmrPort); } finally { - killAllProcesses(); + await killAllProcesses(); } } -async function verifyResponse(): Promise { - const response = await request('http://localhost:4200/'); +async function verifyResponse(port: number): Promise { + const response = await fetch(`http://localhost:${port}/`); - if (!/<\/app-root>/.test(response)) { + if (!/<\/app-root>/.test(await response.text())) { throw new Error('Response does not match expected value.'); } } diff --git a/tests/legacy-cli/e2e/tests/basic/size-tracking.ts b/tests/legacy-cli/e2e/tests/basic/size-tracking.ts deleted file mode 100644 index d648b50f6639..000000000000 --- a/tests/legacy-cli/e2e/tests/basic/size-tracking.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { appendToFile, moveDirectory, prependToFile, replaceInFile, writeFile } from '../../utils/fs'; -import { ng } from '../../utils/process'; - - -export default async function () { - // Store the production build for artifact storage on CircleCI - if (process.env['CIRCLECI']) { - - // Add initial app routing. - // This is done automatically on a new app with --routing but must be done manually on - // existing apps. - const appRoutingModulePath = 'src/app/app-routing.module.ts'; - await writeFile(appRoutingModulePath, ` - import { NgModule } from '@angular/core'; - import { Routes, RouterModule } from '@angular/router'; - - const routes: Routes = []; - - @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] - }) - export class AppRoutingModule { } - `); - await prependToFile('src/app/app.module.ts', - `import { AppRoutingModule } from './app-routing.module';`); - await replaceInFile('src/app/app.module.ts', `imports: [`, `imports: [ AppRoutingModule,`); - await appendToFile('src/app/app.component.html', ''); - - // Add a lazy module. - await ng('generate', 'module', 'lazy', '--route=lazy', '--module=app.module'); - - // Build without hashing and with named chunks to keep have consistent file names. - await ng('build', '--output-hashing=none', '--named-chunks=true'); - - // Upload to the store_artifacts dir listed in .circleci/config.yml - await moveDirectory('dist', '/tmp/dist'); - } -} diff --git a/tests/legacy-cli/e2e/tests/basic/standalone.ts b/tests/legacy-cli/e2e/tests/basic/standalone.ts new file mode 100644 index 000000000000..204d0572f87f --- /dev/null +++ b/tests/legacy-cli/e2e/tests/basic/standalone.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + * + * @fileoverview + * Tests the minimal conversion of a newly generated application + * to use a single standalone component. + */ + +import { writeFile } from '../../utils/fs'; +import { ng } from '../../utils/process'; + +/** + * An application main file that uses a standalone component with + * bootstrapApplication to start the application. `ng-template` and + * `ngIf` are used to ensure that `CommonModule` and `imports` are + * working in standalone mode. + */ +const STANDALONE_MAIN_CONTENT = ` +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { bootstrapApplication, provideProtractorTestingSupport } from '@angular/platform-browser'; + +@Component({ + selector: 'app-root', + standalone: true, + template: \` + +
+ {{name}} app is running! +
+
+ \`, + imports: [CommonModule], +}) +export class AppComponent { + name = 'test-project'; + isVisible = true; +} + +bootstrapApplication(AppComponent, { + providers: [ provideProtractorTestingSupport() ], +}); +`; + +export default async function () { + // Update to a standalone application + await writeFile('src/main.ts', STANDALONE_MAIN_CONTENT); + + // Execute a production build + await ng('build'); + + // Perform the default E2E tests + await ng('e2e', 'test-project'); +} diff --git a/tests/legacy-cli/e2e/tests/basic/styles-array.ts b/tests/legacy-cli/e2e/tests/basic/styles-array.ts index 23016d015bce..7466fb640759 100644 --- a/tests/legacy-cli/e2e/tests/basic/styles-array.ts +++ b/tests/legacy-cli/e2e/tests/basic/styles-array.ts @@ -1,9 +1,9 @@ -import { oneLineTrim } from 'common-tags'; +import { getGlobalVariable } from '../../utils/env'; import { expectFileToMatch, writeMultipleFiles } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -export default async function() { +export default async function () { await writeMultipleFiles({ 'src/string-style.css': '.string-style { color: red }', 'src/input-style.css': '.input-style { color: red }', @@ -12,7 +12,7 @@ export default async function() { 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', }); - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appArchitect = workspaceJson.projects['test-project'].architect; appArchitect.build.options.styles = [ { input: 'src/string-style.css' }, @@ -27,7 +27,7 @@ export default async function() { ]; }); - await ng('build', '--extract-css', '--configuration=development'); + const { stdout } = await ng('build', '--configuration=development'); await expectFileToMatch('dist/test-project/styles.css', '.string-style'); await expectFileToMatch('dist/test-project/styles.css', '.input-style'); @@ -36,9 +36,16 @@ export default async function() { await expectFileToMatch('dist/test-project/renamed-lazy-style.css', '.pre-rename-lazy-style'); await expectFileToMatch( 'dist/test-project/index.html', - oneLineTrim` - - - `, + '', ); + + if (getGlobalVariable('argv')['esbuild']) { + // EXPERIMENTAL_ESBUILD: esbuild does not yet output build stats + return; + } + + // Non injected styles should be listed under lazy chunk files + if (!/Lazy Chunk Files.*\srenamed-lazy-style\.css/m.test(stdout)) { + throw new Error(`Expected "renamed-lazy-style.css" to be listed under "Lazy Chunk Files".`); + } } diff --git a/tests/legacy-cli/e2e/tests/basic/test.ts b/tests/legacy-cli/e2e/tests/basic/test.ts index 9ae72b9026d1..3c0c2d99ee68 100644 --- a/tests/legacy-cli/e2e/tests/basic/test.ts +++ b/tests/legacy-cli/e2e/tests/basic/test.ts @@ -1,9 +1,41 @@ import { ng } from '../../utils/process'; -import { moveFile } from '../../utils/fs'; +import { writeMultipleFiles } from '../../utils/fs'; -export default function () { +export default async function () { // make sure both --watch=false work - return ng('test', '--watch=false') - .then(() => moveFile('./karma.conf.js', './karma.conf.bis.js')) - .then(() => ng('test', '--watch=false', '--karmaConfig=karma.conf.bis.js')); + await ng('test', '--watch=false'); + + // Works with custom config + await writeMultipleFiles({ + './karma.conf.bis.js': ` + // Karma configuration file, see link for more information + // https://karma-runner.github.io/1.0/config/configuration-file.html + module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['ChromeHeadless'], + singleRun: false, + restartOnFileChange: true + }); + }; + `, + }); + + await ng('test', '--watch=false', '--karma-config=karma.conf.bis.js'); } diff --git a/tests/legacy-cli/e2e/tests/build/allow-js.ts b/tests/legacy-cli/e2e/tests/build/allow-js.ts deleted file mode 100644 index 4025a00f6ce0..000000000000 --- a/tests/legacy-cli/e2e/tests/build/allow-js.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ng } from '../../utils/process'; -import { updateTsConfig } from '../../utils/project'; -import { appendToFile, writeFile } from '../../utils/fs'; - -export default async function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - await writeFile('src/my-js-file.js', 'console.log(1); export const a = 2;'); - await appendToFile('src/main.ts', ` - import { a } from './my-js-file'; - console.log(a); - `); - - await updateTsConfig(json => { - json['compilerOptions'].allowJs = true; - }); - - await ng('build', '--configuration=development'); - await ng('build', '--aot', '--configuration=development'); -} diff --git a/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-with-schematic.ts b/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-with-schematic.ts new file mode 100644 index 000000000000..6aa407d4981b --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-with-schematic.ts @@ -0,0 +1,38 @@ +import { getGlobalVariable } from '../../../utils/env'; +import { appendToFile, expectFileToMatch } from '../../../utils/fs'; +import { installPackage } from '../../../utils/packages'; +import { ng } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +const snapshots = require('../../../ng-snapshot/package.json'); + +export default async function () { + await appendToFile('src/app/app.component.html', ''); + await ng('generate', 'app-shell', '--project', 'test-project'); + + const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; + if (isSnapshotBuild) { + const packagesToInstall: string[] = []; + await updateJsonFile('package.json', (packageJson) => { + const dependencies = packageJson['dependencies']; + // Iterate over all of the packages to update them to the snapshot version. + for (const [name, version] of Object.entries( + snapshots.dependencies as { [p: string]: string }, + )) { + if (name in dependencies && dependencies[name] !== version) { + packagesToInstall.push(version); + } + } + }); + + for (const pkg of packagesToInstall) { + await installPackage(pkg); + } + } + + await ng('run', 'test-project:app-shell:development'); + await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/); + + await ng('run', 'test-project:app-shell'); + await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/); +} diff --git a/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-with-service-worker.ts b/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-with-service-worker.ts new file mode 100644 index 000000000000..08566a1e1639 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-with-service-worker.ts @@ -0,0 +1,56 @@ +import { getGlobalVariable } from '../../../utils/env'; +import { appendToFile, expectFileToMatch, writeFile } from '../../../utils/fs'; +import { installPackage } from '../../../utils/packages'; +import { ng } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +const snapshots = require('../../../ng-snapshot/package.json'); + +export default async function () { + await appendToFile('src/app/app.component.html', ''); + await ng('generate', 'service-worker', '--project', 'test-project'); + await ng('generate', 'app-shell', '--project', 'test-project'); + + const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; + if (isSnapshotBuild) { + const packagesToInstall: string[] = []; + await updateJsonFile('package.json', (packageJson) => { + const dependencies = packageJson['dependencies']; + // Iterate over all of the packages to update them to the snapshot version. + for (const [name, version] of Object.entries( + snapshots.dependencies as { [p: string]: string }, + )) { + if (name in dependencies && dependencies[name] !== version) { + packagesToInstall.push(version); + } + } + }); + + for (const pkg of packagesToInstall) { + await installPackage(pkg); + } + } + + await writeFile( + 'e2e/app.e2e-spec.ts', + ` + import { browser, by, element } from 'protractor'; + + it('should have ngsw in normal state', () => { + browser.get('/'); + // Wait for service worker to load. + browser.sleep(2000); + browser.waitForAngularEnabled(false); + browser.get('/ngsw/state'); + // Should have updated, and be in normal state. + expect(element(by.css('pre')).getText()).not.toContain('Last update check: never'); + expect(element(by.css('pre')).getText()).toContain('Driver state: NORMAL'); + }); + `, + ); + + await ng('run', 'test-project:app-shell:production'); + await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/); + + await ng('e2e', '--configuration=production'); +} diff --git a/tests/legacy-cli/e2e/tests/build/assets.ts b/tests/legacy-cli/e2e/tests/build/assets.ts index 5961a5ac587b..72ce987e6ea3 100644 --- a/tests/legacy-cli/e2e/tests/build/assets.ts +++ b/tests/legacy-cli/e2e/tests/build/assets.ts @@ -16,9 +16,11 @@ export default async function () { await expectToFail(() => expectFileToExist('dist/test-project/assets/.gitkeep')); // Ensure `followSymlinks` option follows symlinks - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect['build'].options.assets = [{ glob: '**/*', input: 'src/assets', output: 'assets', followSymlinks: true }]; + appArchitect['build'].options.assets = [ + { glob: '**/*', input: 'src/assets', output: 'assets', followSymlinks: true }, + ]; }); fs.mkdirSync('dirToSymlink/subdir1', { recursive: true }); fs.mkdirSync('dirToSymlink/subdir2/subsubdir1', { recursive: true }); diff --git a/tests/legacy-cli/e2e/tests/build/barrel-file.ts b/tests/legacy-cli/e2e/tests/build/barrel-file.ts index 048ad4599a26..a06302dbe696 100644 --- a/tests/legacy-cli/e2e/tests/build/barrel-file.ts +++ b/tests/legacy-cli/e2e/tests/build/barrel-file.ts @@ -1,7 +1,7 @@ import { replaceInFile, writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; -export default async function() { +export default async function () { await writeFile('src/app/index.ts', `export { AppModule } from './app.module';`); await replaceInFile('src/main.ts', './app/app.module', './app'); await ng('build', '--configuration=development'); diff --git a/tests/legacy-cli/e2e/tests/build/base-href.ts b/tests/legacy-cli/e2e/tests/build/base-href.ts index 610cb282a6d5..82496bddb291 100644 --- a/tests/legacy-cli/e2e/tests/build/base-href.ts +++ b/tests/legacy-cli/e2e/tests/build/base-href.ts @@ -1,10 +1,10 @@ import { ng } from '../../utils/process'; import { expectFileToMatch } from '../../utils/fs'; - -export default function() { +export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. - return ng('build', '--base-href', '/myUrl', '--configuration=development') - .then(() => expectFileToMatch('dist/test-project/index.html', //)); + return ng('build', '--base-href', '/myUrl', '--configuration=development').then(() => + expectFileToMatch('dist/test-project/index.html', //), + ); } diff --git a/tests/legacy-cli/e2e/tests/build/build-app-shell-with-schematic.ts b/tests/legacy-cli/e2e/tests/build/build-app-shell-with-schematic.ts deleted file mode 100644 index cacc9accb7ab..000000000000 --- a/tests/legacy-cli/e2e/tests/build/build-app-shell-with-schematic.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getGlobalVariable } from '../../utils/env'; -import { appendToFile, expectFileToMatch } from '../../utils/fs'; -import { installPackage } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; - -const snapshots = require('../../ng-snapshot/package.json'); - -export default async function () { - await appendToFile('src/app/app.component.html', ''); - await ng('generate', 'appShell', '--client-project', 'test-project'); - - const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; - if (isSnapshotBuild) { - const packagesToInstall = []; - await updateJsonFile('package.json', (packageJson) => { - const dependencies = packageJson['dependencies']; - // Iterate over all of the packages to update them to the snapshot version. - for (const [name, version] of Object.entries(snapshots.dependencies)) { - if (name in dependencies && dependencies[name] !== version) { - packagesToInstall.push(version); - } - } - }); - - for (const pkg of packagesToInstall) { - await installPackage(pkg); - } - } - - await ng('run', 'test-project:app-shell:development'); - await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/); - - await ng('run', 'test-project:app-shell'); - await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/); -} diff --git a/tests/legacy-cli/e2e/tests/build/build-app-shell.ts b/tests/legacy-cli/e2e/tests/build/build-app-shell.ts deleted file mode 100644 index 42d1a41bbd94..000000000000 --- a/tests/legacy-cli/e2e/tests/build/build-app-shell.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { stripIndent } from 'common-tags'; -import { getGlobalVariable } from '../../utils/env'; -import { expectFileToMatch, writeFile } from '../../utils/fs'; -import { installWorkspacePackages } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { readNgVersion } from '../../utils/version'; - -export default function() { - let platformServerVersion = readNgVersion(); - - if (getGlobalVariable('argv')['ng-snapshots']) { - platformServerVersion = require('../../ng-snapshot/package.json') - .dependencies['@angular/platform-server']; - } - - return Promise.resolve() - .then(() => - updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect['server'] = { - builder: '@angular-devkit/build-angular:server', - options: { - outputPath: 'dist/test-project-server', - main: 'src/main.server.ts', - tsConfig: 'tsconfig.server.json', - }, - }; - appArchitect['app-shell'] = { - builder: '@angular-devkit/build-angular:app-shell', - options: { - browserTarget: 'test-project:build', - serverTarget: 'test-project:server', - route: '/shell', - }, - }; - }), - ) - .then(() => - writeFile( - './tsconfig.server.json', - ` - { - "extends": "./tsconfig.app.json", - "compilerOptions": { - "outDir": "../dist-server", - "baseUrl": "./", - "module": "commonjs", - "types": [] - }, - "files": [ - "src/main.server.ts" - ], - "include": [ - "src/**/*.d.ts" - ], - "angularCompilerOptions": { - "entryModule": "src/app/app.server.module#AppServerModule" - } - } - `, - ), - ) - .then(() => - writeFile( - './src/main.server.ts', - ` - import { enableProdMode } from '@angular/core'; - - import { environment } from './environments/environment'; - - if (environment.production) { - enableProdMode(); - } - - export { AppServerModule } from './app/app.server.module'; - export { renderModule, renderModuleFactory } from '@angular/platform-server'; - `, - ), - ) - .then(() => - writeFile( - './src/app/app.component.html', - stripIndent` - Hello World - - `, - ), - ) - .then(() => - writeFile( - './src/app/app.module.ts', - stripIndent` - import { BrowserModule } from '@angular/platform-browser'; - import { NgModule } from '@angular/core'; - import { RouterModule } from '@angular/router'; - - import { AppComponent } from './app.component'; - - @NgModule({ - imports: [ - BrowserModule.withServerTransition({ appId: 'appshell-play' }), - RouterModule - ], - declarations: [AppComponent], - bootstrap: [AppComponent] - }) - export class AppModule { } - `, - ), - ) - .then(() => - writeFile( - './src/app/app.server.module.ts', - stripIndent` - import {NgModule} from '@angular/core'; - import {ServerModule} from '@angular/platform-server'; - import { Routes, RouterModule } from '@angular/router'; - - import { AppModule } from './app.module'; - import { AppComponent } from './app.component'; - import { ShellComponent } from './shell.component'; - - const routes: Routes = [ - { path: 'shell', component: ShellComponent } - ]; - - @NgModule({ - imports: [ - // The AppServerModule should import your AppModule followed - // by the ServerModule from @angular/platform-server. - AppModule, - ServerModule, - RouterModule.forRoot(routes), - ], - // Since the bootstrapped component is not inherited from your - // imported AppModule, it needs to be repeated here. - bootstrap: [AppComponent], - declarations: [ShellComponent], - }) - export class AppServerModule {} - `, - ), - ) - .then(() => - writeFile( - './src/app/shell.component.ts', - stripIndent` - import { Component } from '@angular/core'; - @Component({ - selector: 'app-shell', - template: '

shell Works!

', - styles: [] - }) - export class ShellComponent {} - `, - ), - ) - .then(() => - updateJsonFile('package.json', packageJson => { - const dependencies = packageJson['dependencies']; - dependencies['@angular/platform-server'] = platformServerVersion; - }).then(() => installWorkspacePackages()), - ) - .then(() => ng('run', 'test-project:app-shell')) - .then(() => expectFileToMatch('dist/test-project/index.html', /shell Works!/)); -} diff --git a/tests/legacy-cli/e2e/tests/build/build-errors.ts b/tests/legacy-cli/e2e/tests/build/build-errors.ts deleted file mode 100644 index b8abc5b4dad3..000000000000 --- a/tests/legacy-cli/e2e/tests/build/build-errors.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { writeFile, appendToFile, readFile, replaceInFile } from '../../utils/fs'; -import { getGlobalVariable } from '../../utils/env'; -import { expectToFail } from '../../utils/utils'; - -const extraErrors = [ - `Final loader didn't return a Buffer or String`, - `doesn't contain a valid alias configuration`, - `main.ts is not part of the TypeScript compilation.`, -]; - -export default function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - if (process.platform.startsWith('win')) { - return Promise.resolve(); - } - - // Skip this test in Angular 2/4. - if (getGlobalVariable('argv').ng2 || getGlobalVariable('argv').ng4) { - return Promise.resolve(); - } - - let origContent: string; - - return ( - Promise.resolve() - // Save the original contents of `./src/app/app.component.ts`. - .then(() => readFile('./src/app/app.component.ts')) - .then(contents => (origContent = contents)) - // Check `part of the TypeScript compilation` errors. - // These should show an error only for the missing file. - .then(() => - updateJsonFile('./tsconfig.app.json', configJson => { - (configJson.include = undefined), (configJson.files = ['src/main.ts']); - }), - ) - .then(() => expectToFail(() => ng('build'))) - .then(({ message }) => { - if (!message.includes('polyfills.ts is missing from the TypeScript compilation')) { - throw new Error(`Expected missing TS file error, got this instead:\n${message}`); - } - if (extraErrors.some(e => message.includes(e))) { - throw new Error(`Did not expect extra errors but got:\n${message}`); - } - }) - .then(() => - updateJsonFile('./tsconfig.app.json', configJson => { - configJson.include = ['src/**/*.ts']; - configJson.exclude = ['**/**.spec.ts']; - configJson.files = undefined; - }), - ) - // Check simple single syntax errors. - // These shouldn't skip emit and just show a TS error. - .then(() => appendToFile('./src/app/app.component.ts', ']]]')) - .then(() => expectToFail(() => ng('build'))) - .then(({ message }) => { - if (!message.includes('Declaration or statement expected.')) { - throw new Error(`Expected syntax error, got this instead:\n${message}`); - } - if (extraErrors.some(e => message.includes(e))) { - throw new Error(`Did not expect extra errors but got:\n${message}`); - } - }) - .then(() => writeFile('./src/app/app.component.ts', origContent)) - // Check errors when files were not emitted due to static analysis errors. - .then(() => replaceInFile('./src/app/app.component.ts', `'app-root'`, `(() => 'app-root')()`)) - .then(() => expectToFail(() => ng('build', '--aot'))) - .then(({ message }) => { - if ( - !message.includes('Function calls are not supported') && - !message.includes('Function expressions are not supported in decorators') && - !message.includes('selector must be a string') - ) { - throw new Error(`Expected static analysis error, got this instead:\n${message}`); - } - if (extraErrors.some(e => message.includes(e))) { - throw new Error(`Did not expect extra errors but got:\n${message}`); - } - }) - .then(() => writeFile('./src/app/app.component.ts', origContent)) - ); -} \ No newline at end of file diff --git a/tests/legacy-cli/e2e/tests/build/build-optimizer.ts b/tests/legacy-cli/e2e/tests/build/build-optimizer.ts index 2cdbeceb868a..768fbb4b914c 100644 --- a/tests/legacy-cli/e2e/tests/build/build-optimizer.ts +++ b/tests/legacy-cli/e2e/tests/build/build-optimizer.ts @@ -2,14 +2,17 @@ import { ng } from '../../utils/process'; import { expectFileToMatch, expectFileToExist } from '../../utils/fs'; import { expectToFail } from '../../utils/utils'; - export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. return ng('build', '--aot', '--build-optimizer') - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/main.js', /\.decorators =/))) + .then(() => + expectToFail(() => expectFileToMatch('dist/test-project/main.js', /\.decorators =/)), + ) .then(() => ng('build')) .then(() => expectToFail(() => expectFileToExist('dist/vendor.js'))) - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/main.js', /\.decorators =/))) + .then(() => + expectToFail(() => expectFileToMatch('dist/test-project/main.js', /\.decorators =/)), + ) .then(() => expectToFail(() => ng('build', '--aot=false', '--build-optimizer'))); } diff --git a/tests/legacy-cli/e2e/tests/build/bundle-budgets.ts b/tests/legacy-cli/e2e/tests/build/bundle-budgets.ts index f4076d470f3f..fec3dea7d93e 100644 --- a/tests/legacy-cli/e2e/tests/build/bundle-budgets.ts +++ b/tests/legacy-cli/e2e/tests/build/bundle-budgets.ts @@ -1,30 +1,29 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; export default async function () { // Error - await updateJsonFile('angular.json', json => { + await updateJsonFile('angular.json', (json) => { json.projects['test-project'].architect.build.configurations.production.budgets = [ { type: 'all', maximumError: '100b' }, ]; }); - const errorMessage = await expectToFail(() => ng('build')); + const { message: errorMessage } = await expectToFail(() => ng('build')); if (!/Error.+budget/.test(errorMessage)) { throw new Error('Budget error: all, max error.'); } // Warning - await updateJsonFile('angular.json', json => { + await updateJsonFile('angular.json', (json) => { json.projects['test-project'].architect.build.configurations.production.budgets = [ { type: 'all', minimumWarning: '100mb' }, ]; @@ -36,7 +35,7 @@ export default async function () { } // Pass - await updateJsonFile('angular.json', json => { + await updateJsonFile('angular.json', (json) => { json.projects['test-project'].architect.build.configurations.production.budgets = [ { type: 'allScript', maximumError: '100mb' }, ]; diff --git a/tests/legacy-cli/e2e/tests/build/chunk-hash.ts b/tests/legacy-cli/e2e/tests/build/chunk-hash.ts deleted file mode 100644 index 0c119a65ee78..000000000000 --- a/tests/legacy-cli/e2e/tests/build/chunk-hash.ts +++ /dev/null @@ -1,105 +0,0 @@ -import * as fs from 'fs'; - -import {ng} from '../../utils/process'; -import {writeFile, prependToFile, replaceInFile} from '../../utils/fs'; - -const OUTPUT_RE = /(main|polyfills|vendor|inline|styles|\d+)\.[a-z0-9]+\.(chunk|bundle)\.(js|css)$/; - -function generateFileHashMap(): Map { - const hashes = new Map(); - - fs.readdirSync('./dist') - .forEach(name => { - if (!name.match(OUTPUT_RE)) { - return; - } - - const [module, hash] = name.split('.'); - hashes.set(module, hash); - }); - - return hashes; -} - -function validateHashes( - oldHashes: Map, - newHashes: Map, - shouldChange: Array): void { - - console.log(' Validating hashes...'); - console.log(` Old hashes: ${JSON.stringify([...oldHashes])}`); - console.log(` New hashes: ${JSON.stringify([...newHashes])}`); - - oldHashes.forEach((hash, module) => { - if (hash == newHashes.get(module)) { - if (shouldChange.includes(module)) { - throw new Error(`Module "${module}" did not change hash (${hash})...`); - } - } else if (!shouldChange.includes(module)) { - throw new Error(`Module "${module}" changed hash (${hash})...`); - } - }); -} - -export default function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - return; - - let oldHashes: Map; - let newHashes: Map; - // First, collect the hashes. - return Promise.resolve() - .then(() => ng('generate', 'module', 'lazy', '--routing')) - .then(() => prependToFile('src/app/app.module.ts', ` - import { RouterModule } from '@angular/router'; - import { ReactiveFormsModule } from '@angular/forms'; - `)) - .then(() => replaceInFile('src/app/app.module.ts', 'imports: [', `imports: [ - RouterModule.forRoot([{ path: "lazy", loadChildren: "./lazy/lazy.module#LazyModule" }]), - ReactiveFormsModule, - `)) - .then(() => ng('build', '--output-hashing=all', '--configuration=development')) - .then(() => { - oldHashes = generateFileHashMap(); - }) - .then(() => ng('build', '--output-hashing=all', '--configuration=development')) - .then(() => { - newHashes = generateFileHashMap(); - }) - .then(() => { - validateHashes(oldHashes, newHashes, []); - oldHashes = newHashes; - }) - .then(() => writeFile('src/styles.css', 'body { background: blue; }')) - .then(() => ng('build', '--output-hashing=all', '--configuration=development')) - .then(() => { - newHashes = generateFileHashMap(); - }) - .then(() => { - validateHashes(oldHashes, newHashes, ['styles']); - oldHashes = newHashes; - }) - .then(() => writeFile('src/app/app.component.css', 'h1 { margin: 10px; }')) - .then(() => ng('build', '--output-hashing=all', '--configuration=development')) - .then(() => { - newHashes = generateFileHashMap(); - }) - .then(() => { - validateHashes(oldHashes, newHashes, ['main']); - oldHashes = newHashes; - }) - .then(() => prependToFile('src/app/lazy/lazy.module.ts', ` - import { ReactiveFormsModule } from '@angular/forms'; - `)) - .then(() => replaceInFile('src/app/lazy/lazy.module.ts', 'imports: [', ` - imports: [ - ReactiveFormsModule, - `)) - .then(() => ng('build', '--output-hashing=all', '--configuration=development')) - .then(() => { - newHashes = generateFileHashMap(); - }) - .then(() => { - validateHashes(oldHashes, newHashes, ['inline', '0']); - }); -} diff --git a/tests/legacy-cli/e2e/tests/build/config-file-fallback.ts b/tests/legacy-cli/e2e/tests/build/config-file-fallback.ts index 3dd987431b8a..53298b573e12 100644 --- a/tests/legacy-cli/e2e/tests/build/config-file-fallback.ts +++ b/tests/legacy-cli/e2e/tests/build/config-file-fallback.ts @@ -1,8 +1,7 @@ -import {ng} from '../../utils/process'; -import {moveFile} from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { moveFile } from '../../utils/fs'; - -export default function() { +export default function () { return Promise.resolve() .then(() => ng('build')) .then(() => moveFile('angular.json', '.angular.json')) diff --git a/tests/legacy-cli/e2e/tests/build/css-urls.ts b/tests/legacy-cli/e2e/tests/build/css-urls.ts index f2dc6ee838ad..fed62b8a0b32 100644 --- a/tests/legacy-cli/e2e/tests/build/css-urls.ts +++ b/tests/legacy-cli/e2e/tests/build/css-urls.ts @@ -3,7 +3,7 @@ import { expectFileToMatch, expectFileToExist, expectFileMatchToExist, - writeMultipleFiles + writeMultipleFiles, } from '../../utils/fs'; import { copyProjectAsset } from '../../utils/assets'; import { expectToFail } from '../../utils/utils'; @@ -15,90 +15,164 @@ const imgSvg = ` `; export default function () { - return Promise.resolve() - // Verify absolute/relative paths in global/component css. - .then(() => writeMultipleFiles({ - 'src/styles.css': ` + return ( + Promise.resolve() + // Verify absolute/relative paths in global/component css. + .then(() => + writeMultipleFiles({ + 'src/styles.css': ` h1 { background: url('https://github.com/assets/global-img-absolute.svg'); } h2 { background: url('https://github.com/assets/global-img-relative.png'); } `, - 'src/app/app.component.css': ` + 'src/app/app.component.css': ` h3 { background: url('https://github.com/assets/component-img-absolute.svg'); } h4 { background: url('https://github.com/assets/component-img-relative.png'); } `, - 'src/assets/global-img-absolute.svg': imgSvg, - 'src/assets/component-img-absolute.svg': imgSvg - })) - .then(() => copyProjectAsset('images/spectrum.png', './src/assets/global-img-relative.png')) - .then(() => copyProjectAsset('images/spectrum.png', './src/assets/component-img-relative.png')) - .then(() => ng('build', '--extract-css', '--aot', '--configuration=development')) - // Check paths are correctly generated. - .then(() => expectFileToMatch('dist/test-project/styles.css', 'assets/global-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /url\('\/assets\/global-img-absolute\.svg'\)/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /global-img-relative\.png/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - '/assets/component-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/main.js', - /component-img-relative\.png/)) - // Check files are correctly created. - .then(() => expectToFail(() => expectFileToExist('dist/test-project/global-img-absolute.svg'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/component-img-absolute.svg'))) - .then(() => expectFileMatchToExist('./dist/test-project', /global-img-relative\.png/)) - .then(() => expectFileMatchToExist('./dist/test-project', /component-img-relative\.png/)) - // Check urls with deploy-url scheme are used as is. - .then(() => ng('build', '--base-href=/base/', '--deploy-url=http://deploy.url/', - '--extract-css', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /url\(\'\/assets\/global-img-absolute\.svg\'\)/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - /url\(\'\/assets\/component-img-absolute\.svg\'\)/)) - // Check urls with base-href scheme are used as is (with deploy-url). - .then(() => ng('build', '--base-href=http://base.url/', '--deploy-url=deploy/', - '--extract-css', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /url\(\'\/assets\/global-img-absolute\.svg\'\)/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - /url\(\'\/assets\/component-img-absolute\.svg\'\)/)) - // Check urls with deploy-url and base-href scheme only use deploy-url. - .then(() => ng('build', '--base-href=http://base.url/', '--deploy-url=http://deploy.url/', - '--extract-css', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /url\(\'\/assets\/global-img-absolute\.svg\'\)/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - /url\(\'\/assets\/component-img-absolute\.svg\'\)/)) - // Check with base-href and deploy-url flags. - .then(() => ng('build', '--base-href=/base/', '--deploy-url=deploy/', - '--extract-css', '--aot', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - '/assets/global-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /global-img-relative\.png/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - '/assets/component-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/main.js', - /deploy\/component-img-relative\.png/)) - // Check with identical base-href and deploy-url flags. - .then(() => ng('build', '--base-href=/base/', '--deploy-url=/base/', - '--extract-css', '--aot', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - '/assets/global-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /global-img-relative\.png/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - '/assets/component-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/main.js', - /\/base\/component-img-relative\.png/)) - // Check with only base-href flag. - .then(() => ng('build', '--base-href=/base/', - '--extract-css', '--aot', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - '/assets/global-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /global-img-relative\.png/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - '/assets/component-img-absolute.svg')) - .then(() => expectFileToMatch('dist/test-project/main.js', - /component-img-relative\.png/)); + 'src/assets/global-img-absolute.svg': imgSvg, + 'src/assets/component-img-absolute.svg': imgSvg, + }), + ) + .then(() => copyProjectAsset('images/spectrum.png', './src/assets/global-img-relative.png')) + .then(() => + copyProjectAsset('images/spectrum.png', './src/assets/component-img-relative.png'), + ) + .then(() => ng('build', '--aot', '--configuration=development')) + // Check paths are correctly generated. + .then(() => + expectFileToMatch('dist/test-project/styles.css', 'assets/global-img-absolute.svg'), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/styles.css', + /url\('\/assets\/global-img-absolute\.svg'\)/, + ), + ) + .then(() => expectFileToMatch('dist/test-project/styles.css', /global-img-relative\.png/)) + .then(() => + expectFileToMatch('dist/test-project/main.js', '/assets/component-img-absolute.svg'), + ) + .then(() => expectFileToMatch('dist/test-project/main.js', /component-img-relative\.png/)) + // Check files are correctly created. + .then(() => + expectToFail(() => expectFileToExist('dist/test-project/global-img-absolute.svg')), + ) + .then(() => + expectToFail(() => expectFileToExist('dist/test-project/component-img-absolute.svg')), + ) + .then(() => expectFileMatchToExist('./dist/test-project', /global-img-relative\.png/)) + .then(() => expectFileMatchToExist('./dist/test-project', /component-img-relative\.png/)) + // Check urls with deploy-url scheme are used as is. + .then(() => + ng( + 'build', + '--base-href=/base/', + '--deploy-url=http://deploy.url/', + '--configuration=development', + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/styles.css', + /url\(\'\/assets\/global-img-absolute\.svg\'\)/, + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/main.js', + /url\(\'\/assets\/component-img-absolute\.svg\'\)/, + ), + ) + // Check urls with base-href scheme are used as is (with deploy-url). + .then(() => + ng( + 'build', + '--base-href=http://base.url/', + '--deploy-url=deploy/', + '--configuration=development', + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/styles.css', + /url\(\'\/assets\/global-img-absolute\.svg\'\)/, + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/main.js', + /url\(\'\/assets\/component-img-absolute\.svg\'\)/, + ), + ) + // Check urls with deploy-url and base-href scheme only use deploy-url. + .then(() => + ng( + 'build', + '--base-href=http://base.url/', + '--deploy-url=http://deploy.url/', + '--configuration=development', + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/styles.css', + /url\(\'\/assets\/global-img-absolute\.svg\'\)/, + ), + ) + .then(() => + expectFileToMatch( + 'dist/test-project/main.js', + /url\(\'\/assets\/component-img-absolute\.svg\'\)/, + ), + ) + // Check with base-href and deploy-url flags. + .then(() => + ng( + 'build', + '--base-href=/base/', + '--deploy-url=deploy/', + '--aot', + '--configuration=development', + ), + ) + .then(() => + expectFileToMatch('dist/test-project/styles.css', '/assets/global-img-absolute.svg'), + ) + .then(() => expectFileToMatch('dist/test-project/styles.css', /global-img-relative\.png/)) + .then(() => + expectFileToMatch('dist/test-project/main.js', '/assets/component-img-absolute.svg'), + ) + .then(() => + expectFileToMatch('dist/test-project/main.js', /deploy\/component-img-relative\.png/), + ) + // Check with identical base-href and deploy-url flags. + .then(() => + ng( + 'build', + '--base-href=/base/', + '--deploy-url=/base/', + '--aot', + '--configuration=development', + ), + ) + .then(() => + expectFileToMatch('dist/test-project/styles.css', '/assets/global-img-absolute.svg'), + ) + .then(() => expectFileToMatch('dist/test-project/styles.css', /global-img-relative\.png/)) + .then(() => + expectFileToMatch('dist/test-project/main.js', '/assets/component-img-absolute.svg'), + ) + .then(() => + expectFileToMatch('dist/test-project/main.js', /\/base\/component-img-relative\.png/), + ) + // Check with only base-href flag. + .then(() => ng('build', '--base-href=/base/', '--aot', '--configuration=development')) + .then(() => + expectFileToMatch('dist/test-project/styles.css', '/assets/global-img-absolute.svg'), + ) + .then(() => expectFileToMatch('dist/test-project/styles.css', /global-img-relative\.png/)) + .then(() => + expectFileToMatch('dist/test-project/main.js', '/assets/component-img-absolute.svg'), + ) + .then(() => expectFileToMatch('dist/test-project/main.js', /component-img-relative\.png/)) + ); } diff --git a/tests/legacy-cli/e2e/tests/build/delete-output-path.ts b/tests/legacy-cli/e2e/tests/build/delete-output-path.ts deleted file mode 100644 index 62f06fef8700..000000000000 --- a/tests/legacy-cli/e2e/tests/build/delete-output-path.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {ng} from '../../utils/process'; -import {expectToFail} from '../../utils/utils'; -import {deleteFile, expectFileToExist} from '../../utils/fs'; -import {getGlobalVariable} from '../../utils/env'; - -export default function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - return ng('build') - // This is supposed to fail since there's a missing file - .then(() => deleteFile('src/app/app.component.ts')) - // The build fails but we don't delete the output of the previous build. - .then(() => expectToFail(() => ng('build', '--delete-output-path=false'))) - .then(() => expectFileToExist('dist')) - // By default, output path is always cleared. - .then(() => expectToFail(() => ng('build', '--configuration=development'))) - .then(() => expectToFail(() => expectFileToExist('dist/test-project'))); -} diff --git a/tests/legacy-cli/e2e/tests/build/deploy-url.ts b/tests/legacy-cli/e2e/tests/build/deploy-url.ts index ca9a82ae3f03..1a54a5016f2d 100644 --- a/tests/legacy-cli/e2e/tests/build/deploy-url.ts +++ b/tests/legacy-cli/e2e/tests/build/deploy-url.ts @@ -3,25 +3,26 @@ import { copyProjectAsset } from '../../utils/assets'; import { appendToFile, expectFileToMatch, writeMultipleFiles } from '../../utils/fs'; export default function () { - return Promise.resolve() - .then(() => writeMultipleFiles({ - 'src/styles.css': 'div { background: url("https://github.com/assets/more.png"); }', - 'src/lazy.ts': 'export const lazy = "lazy";', - })) - .then(() => appendToFile('src/main.ts', 'import("./lazy");')) - // use image with file size >10KB to prevent inlining - .then(() => copyProjectAsset('images/spectrum.png', './src/assets/more.png')) - .then(() => ng('build', '--deploy-url=deployUrl/', '--extract-css', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/index.html', 'deployUrl/main.js')) - // verify --deploy-url isn't applied to extracted css urls - .then(() => expectFileToMatch('dist/test-project/styles.css', - /url\(['"]?more\.png['"]?\)/)) - .then(() => ng('build', '--deploy-url=http://example.com/some/path/', '--extract-css', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/index.html', 'http://example.com/some/path/main.js')) - // verify --deploy-url is applied to non-extracted css urls - .then(() => ng('build', '--deploy-url=deployUrl/', '--extract-css=false', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.js', - /\(['"]?deployUrl\/more\.png['"]?\)/)) - .then(() => expectFileToMatch('dist/test-project/runtime.js', - /__webpack_require__\.p\s*=\s*"deployUrl\/";/)); + return ( + Promise.resolve() + .then(() => + writeMultipleFiles({ + 'src/styles.css': 'div { background: url("https://github.com/assets/more.png"); }', + 'src/lazy.ts': 'export const lazy = "lazy";', + }), + ) + .then(() => appendToFile('src/main.ts', 'import("./lazy");')) + // use image with file size >10KB to prevent inlining + .then(() => copyProjectAsset('images/spectrum.png', './src/assets/more.png')) + .then(() => ng('build', '--deploy-url=deployUrl/', '--configuration=development')) + .then(() => expectFileToMatch('dist/test-project/index.html', 'deployUrl/main.js')) + // verify --deploy-url isn't applied to extracted css urls + .then(() => expectFileToMatch('dist/test-project/styles.css', /url\(['"]?more\.png['"]?\)/)) + .then(() => + ng('build', '--deploy-url=http://example.com/some/path/', '--configuration=development'), + ) + .then(() => + expectFileToMatch('dist/test-project/index.html', 'http://example.com/some/path/main.js'), + ) + ); } diff --git a/tests/legacy-cli/e2e/tests/build/differential-cache.ts b/tests/legacy-cli/e2e/tests/build/differential-cache.ts deleted file mode 100644 index 5ac16a5fcfac..000000000000 --- a/tests/legacy-cli/e2e/tests/build/differential-cache.ts +++ /dev/null @@ -1,95 +0,0 @@ -import * as crypto from 'crypto'; -import * as fs from 'fs'; -import { rimraf, replaceInFile } from '../../utils/fs'; -import { ng } from '../../utils/process'; - -function generateFileHashMap(): Map { - const hashes = new Map(); - - fs.readdirSync('./dist/test-project').forEach(name => { - const data = fs.readFileSync('./dist/test-project/' + name); - const hash = crypto - .createHash('sha1') - .update(data) - .digest('hex'); - - hashes.set(name, hash); - }); - - return hashes; -} - -function validateHashes( - oldHashes: Map, - newHashes: Map, - shouldChange: Array, -): void { - oldHashes.forEach((hash, name) => { - if (hash === newHashes.get(name)) { - if (shouldChange.includes(name)) { - throw new Error(`"${name}" did not change hash (${hash})...`); - } - } else if (!shouldChange.includes(name)) { - throw new Error(`"${name}" changed hash (${hash})...`); - } - }); -} - -export default async function() { - // Skip on CI due to large variability of performance - if (process.env['CI']) { - return; - } - - let oldHashes: Map; - let newHashes: Map; - - // Enable Differential loading to run both size checks - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - - // Remove the cache so that an initial build and build with cache can be tested - await rimraf('./node_modules/.cache'); - - let start = Date.now(); - await ng('build', '--configuration=development'); - let initial = Date.now() - start; - oldHashes = generateFileHashMap(); - - start = Date.now(); - await ng('build', '--configuration=development'); - let cached = Date.now() - start; - newHashes = generateFileHashMap(); - - validateHashes(oldHashes, newHashes, []); - - if (cached > initial * 0.70) { - throw new Error( - `Cached build time [${cached}] should not be greater than 70% of initial build time [${initial}].`, - ); - } - - // Remove the cache so that an initial build and build with cache can be tested - await rimraf('./node_modules/.cache'); - - start = Date.now(); - await ng('build'); - initial = Date.now() - start; - oldHashes = generateFileHashMap(); - - start = Date.now(); - await ng('build'); - cached = Date.now() - start; - newHashes = generateFileHashMap(); - - if (cached > initial * 0.70) { - throw new Error( - `Cached build time [${cached}] should not be greater than 70% of initial build time [${initial}].`, - ); - } - - validateHashes(oldHashes, newHashes, []); -} diff --git a/tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts b/tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts deleted file mode 100644 index cc0be82b7d95..000000000000 --- a/tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { createHash } from 'crypto'; -import { - appendToFile, - expectFileToMatch, - prependToFile, - readFile, - replaceInFile, - writeFile, -} from '../../utils/fs'; -import { ng } from '../../utils/process'; - -export default async function () { - // Enable Differential loading - await replaceInFile('.browserslistrc', 'not IE 11', 'IE 11'); - - const appRoutingModulePath = 'src/app/app-routing.module.ts'; - - // Add app routing. - // This is done automatically on a new app with --routing. - await writeFile( - appRoutingModulePath, - ` - import { NgModule } from '@angular/core'; - import { Routes, RouterModule } from '@angular/router'; - - const routes: Routes = []; - - @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] - }) - export class AppRoutingModule { } - `, - ); - await prependToFile( - 'src/app/app.module.ts', - `import { AppRoutingModule } from './app-routing.module';`, - ); - await replaceInFile('src/app/app.module.ts', `imports: [`, `imports: [ AppRoutingModule,`); - await appendToFile('src/app/app.component.html', ''); - - await ng('generate', 'module', 'lazy', '--module=app.module', '--route', 'lazy'); - - await ng( - 'build', - '--subresource-integrity', - '--output-hashing=none', - '--output-path=dist/first', - ); - - // Second build used to ensure cached files use correct integrity values - await ng( - 'build', - '--subresource-integrity', - '--output-hashing=none', - '--output-path=dist/second', - ); - - const chunkId = '730'; - const codeHashES5 = createHash('sha384') - .update(await readFile(`dist/first/${chunkId}-es5.js`)) - .digest('base64'); - const codeHashes2017 = createHash('sha384') - .update(await readFile(`dist/first/${chunkId}-es2017.js`)) - .digest('base64'); - - await expectFileToMatch('dist/first/runtime-es5.js', 'sha384-' + codeHashES5); - await expectFileToMatch('dist/first/runtime-es2017.js', 'sha384-' + codeHashes2017); - - await expectFileToMatch('dist/second/runtime-es5.js', 'sha384-' + codeHashES5); - await expectFileToMatch('dist/second/runtime-es2017.js', 'sha384-' + codeHashes2017); -} diff --git a/tests/legacy-cli/e2e/tests/build/differential-loading-watch.ts b/tests/legacy-cli/e2e/tests/build/differential-loading-watch.ts deleted file mode 100644 index ebf2b6ff2b35..000000000000 --- a/tests/legacy-cli/e2e/tests/build/differential-loading-watch.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { expectFileToExist, replaceInFile } from '../../utils/fs'; -import { execAndWaitForOutputToMatch } from '../../utils/process'; - -export default async function () { - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - - await execAndWaitForOutputToMatch('ng', ['build', '--watch', '--configuration=development'], /Initial Total/i); - await expectFileToExist('dist/test-project/runtime-es2017.js'); - await expectFileToExist('dist/test-project/main-es2017.js'); -} diff --git a/tests/legacy-cli/e2e/tests/build/differential-loading.ts b/tests/legacy-cli/e2e/tests/build/differential-loading.ts deleted file mode 100644 index d3fc49dfe68f..000000000000 --- a/tests/legacy-cli/e2e/tests/build/differential-loading.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { oneLineTrim } from 'common-tags'; -import { appendToFile, expectFileToMatch, replaceInFile, writeMultipleFiles } from '../../utils/fs'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; - -export default async function () { - // Enable Differential loading to run both size checks - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - - await writeMultipleFiles({ - 'src/string-script.js': "console.log('string-script'); var number = 1+1;", - 'src/pre-rename-script.js': "console.log('pre-rename-script');", - }); - - await updateJsonFile('angular.json', configJson => { - const appArchitect = configJson.projects['test-project'].architect; - appArchitect.build.options.scripts = [ - { input: 'src/string-script.js' }, - { input: 'src/pre-rename-script.js', bundleName: 'renamed-script' }, - ]; - }); - - await ng('build', '--extract-css', '--vendor-chunk', '--optimization', '--configuration=development'); - - // index.html lists the right bundles - await expectFileToMatch( - 'dist/test-project/index.html', - oneLineTrim` - - - - - - - - - - - `, - ); - - await expectFileToMatch('dist/test-project/vendor-es2017.js', /class \w{constructor\(/); - await expectToFail(() => expectFileToMatch('dist/test-project/vendor-es5.js', /class \w{constructor\(/)); -} diff --git a/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts b/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts new file mode 100644 index 000000000000..bba2ad7e826b --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts @@ -0,0 +1,30 @@ +import { join } from 'path'; +import { createDir, expectFileNotToExist, expectFileToExist, writeFile } from '../../utils/fs'; +import { silentNg } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + const cachePath = '.angular/cache'; + const staleCachePath = join(cachePath, 'v1.0.0'); + + // No need to include all applications code to verify disk cache existence. + await writeFile('src/main.ts', 'console.log(1);'); + + // Enable cache for all environments + await updateJsonFile('angular.json', (config) => { + config.cli ??= {}; + config.cli.cache = { + environment: 'all', + enabled: true, + path: cachePath, + }; + }); + + // Create a dummy stale disk cache directory. + await createDir(staleCachePath); + await expectFileToExist(staleCachePath); + + await silentNg('build'); + await expectFileToExist(cachePath); + await expectFileNotToExist(staleCachePath); +} diff --git a/tests/legacy-cli/e2e/tests/build/disk-cache.ts b/tests/legacy-cli/e2e/tests/build/disk-cache.ts new file mode 100644 index 000000000000..1873905646ca --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/disk-cache.ts @@ -0,0 +1,56 @@ +import { expectFileNotToExist, expectFileToExist, rimraf, writeFile } from '../../utils/fs'; +import { silentNg } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +const defaultCachePath = '.angular/cache'; +const overriddenCachePath = '.cache/angular-cli'; + +export default async function () { + const originalCIValue = process.env['CI']; + + // No need to include all applications code to verify disk cache existence. + await writeFile('src/main.ts', 'console.log(1);'); + + try { + // Should be enabled by default. + process.env['CI'] = '0'; + await configureAndRunTest(); + + // Should not write cache when it's disabled + await configureAndRunTest({ enabled: false }); + await expectFileNotToExist(defaultCachePath); + + // Should not write cache by default when in CI. + process.env['CI'] = '1'; + await configureAndRunTest(); + await expectFileNotToExist(defaultCachePath); + + // Should write cache when it's enabled and 'environment' is set to 'all' or 'ci'. + await configureAndRunTest({ environment: 'all' }); + await expectFileToExist(defaultCachePath); + + // Should write cache to custom path when configured. + await configureAndRunTest({ environment: 'ci', path: overriddenCachePath }); + await expectFileNotToExist(defaultCachePath); + await expectFileToExist(overriddenCachePath); + } finally { + process.env['CI'] = originalCIValue; + } +} + +async function configureAndRunTest(cacheOptions?: { + environment?: 'ci' | 'local' | 'all'; + enabled?: boolean; + path?: string; +}): Promise { + await Promise.all([ + rimraf(overriddenCachePath), + rimraf(defaultCachePath), + updateJsonFile('angular.json', (config) => { + config.cli ??= {}; + config.cli.cache = cacheOptions; + }), + ]); + + await silentNg('build'); +} diff --git a/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts b/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts new file mode 100644 index 000000000000..0a3681549d3d --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts @@ -0,0 +1,11 @@ +import { join } from 'path'; +import { execWithEnv } from '../../utils/process'; + +export default async function () { + // Set the esbuild native binary path to a non-existent file to simulate a spawn error. + // The build should still succeed by falling back to the WASM variant of esbuild. + await execWithEnv('ng', ['build'], { + ...process.env, + 'ESBUILD_BINARY_PATH': join(__dirname, 'esbuild-bin-no-exist-xyz'), + }); +} diff --git a/tests/legacy-cli/e2e/tests/build/extract-licenses.ts b/tests/legacy-cli/e2e/tests/build/extract-licenses.ts index 7d0b61d501f4..29325de2ada0 100644 --- a/tests/legacy-cli/e2e/tests/build/extract-licenses.ts +++ b/tests/legacy-cli/e2e/tests/build/extract-licenses.ts @@ -2,7 +2,7 @@ import { expectFileToExist, expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; -export default async function() { +export default async function () { // Licenses should be left intact if extraction is disabled await ng('build', '--extract-licenses=false', '--output-hashing=none'); diff --git a/tests/legacy-cli/e2e/tests/build/jit-prod.ts b/tests/legacy-cli/e2e/tests/build/jit-prod.ts index 66162c5a52c7..1abb7a0709d2 100644 --- a/tests/legacy-cli/e2e/tests/build/jit-prod.ts +++ b/tests/legacy-cli/e2e/tests/build/jit-prod.ts @@ -1,10 +1,9 @@ import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; - export default async function () { // Make prod use JIT. - await updateJsonFile('angular.json', configJson => { + await updateJsonFile('angular.json', (configJson) => { const appArchitect = configJson.projects['test-project'].architect; appArchitect.build.configurations['production'].aot = false; appArchitect.build.configurations['production'].buildOptimizer = false; diff --git a/tests/legacy-cli/e2e/tests/build/json.ts b/tests/legacy-cli/e2e/tests/build/json.ts index dd8e78f8b5e7..41718132f384 100644 --- a/tests/legacy-cli/e2e/tests/build/json.ts +++ b/tests/legacy-cli/e2e/tests/build/json.ts @@ -2,7 +2,7 @@ import { expectFileToExist } from '../../utils/fs'; import { expectGitToBeClean } from '../../utils/git'; import { ng } from '../../utils/process'; -export default async function() { +export default async function () { await ng('build', '--stats-json', '--configuration=development'); await expectFileToExist('./dist/test-project/stats.json'); await expectGitToBeClean(); diff --git a/tests/legacy-cli/e2e/tests/build/lazy-load-syntax.ts b/tests/legacy-cli/e2e/tests/build/lazy-load-syntax.ts index abc97433a7ab..62f3cf294ba1 100644 --- a/tests/legacy-cli/e2e/tests/build/lazy-load-syntax.ts +++ b/tests/legacy-cli/e2e/tests/build/lazy-load-syntax.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { appendToFile, prependToFile, readFile, replaceInFile, writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; @@ -15,29 +16,37 @@ export default async function () { // Add app routing. // This is done automatically on a new app with --routing. - await writeFile(appRoutingModulePath, ` - import { NgModule } from '@angular/core'; - import { Routes, RouterModule } from '@angular/router'; + await writeFile( + appRoutingModulePath, + ` + import { NgModule } from '@angular/core'; + import { Routes, RouterModule } from '@angular/router'; - const routes: Routes = []; + const routes: Routes = []; - @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] - }) - export class AppRoutingModule { } - `); - await prependToFile('src/app/app.module.ts', - `import { AppRoutingModule } from './app-routing.module';`); + @NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] + }) + export class AppRoutingModule { } + `, + ); + await prependToFile( + 'src/app/app.module.ts', + `import { AppRoutingModule } from './app-routing.module';`, + ); await replaceInFile('src/app/app.module.ts', `imports: [`, `imports: [ AppRoutingModule,`); await appendToFile('src/app/app.component.html', ''); const originalAppRoutingModule = await readFile(appRoutingModulePath); // helper to replace loadChildren const replaceLoadChildren = async (route: string) => { - const content = originalAppRoutingModule.replace('const routes: Routes = [];', ` - const routes: Routes = [{ path: 'lazy', loadChildren: ${route} }]; - `); + const content = originalAppRoutingModule.replace( + 'const routes: Routes = [];', + ` + const routes: Routes = [{ path: 'lazy', loadChildren: ${route} }]; + `, + ); return writeFile(appRoutingModulePath, content); }; @@ -45,34 +54,41 @@ export default async function () { // Add lazy route. await ng('generate', 'module', 'lazy', '--routing'); await ng('generate', 'component', 'lazy/lazy-comp'); - await replaceInFile('src/app/lazy/lazy-routing.module.ts', 'const routes: Routes = [];', ` - import { LazyCompComponent } from './lazy-comp/lazy-comp.component'; - const routes: Routes = [{ path: '', component: LazyCompComponent }]; - `); + await replaceInFile( + 'src/app/lazy/lazy-routing.module.ts', + 'const routes: Routes = [];', + ` + import { LazyCompComponent } from './lazy-comp/lazy-comp.component'; + const routes: Routes = [{ path: '', component: LazyCompComponent }]; + `, + ); // Add lazy route e2e - await writeFile('e2e/src/app.e2e-spec.ts', ` - import { browser, logging, element, by } from 'protractor'; + await writeFile( + 'e2e/src/app.e2e-spec.ts', + ` + import { browser, logging, element, by } from 'protractor'; - describe('workspace-project App', () => { - it('should display lazy route', async () => { - await browser.get(browser.baseUrl + '/lazy'); - expect(await element(by.css('app-lazy-comp p')).getText()).toEqual('lazy-comp works!'); - }); + describe('workspace-project App', () => { + it('should display lazy route', async () => { + await browser.get(browser.baseUrl + '/lazy'); + expect(await element(by.css('app-lazy-comp p')).getText()).toEqual('lazy-comp works!'); + }); - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - })); + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + })); + }); }); - }); - `); + `, + ); // Convert the default config to use JIT and prod to just do AOT. - // This way we can use `ng e2e` to test JIT and `ng e2e --prod` to test AOT. - await updateJsonFile('angular.json', json => { + // This way we can use `ng e2e` to test JIT and `ng e2e --configuration=production` to test AOT. + await updateJsonFile('angular.json', (json) => { const buildTarget = json['projects'][projectName]['architect']['build']; buildTarget['options']['aot'] = true; buildTarget['configurations']['development']['aot'] = false; diff --git a/tests/legacy-cli/e2e/tests/build/library-with-demo-app.ts b/tests/legacy-cli/e2e/tests/build/library-with-demo-app.ts new file mode 100644 index 000000000000..02066a53070a --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library-with-demo-app.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { createDir, writeFile } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + await ng('generate', 'library', 'mylib'); + await createLibraryEntryPoint('secondary', 'SecondaryModule', 'index.ts'); + await createLibraryEntryPoint('another', 'AnotherModule', 'index.ts'); + + // Scenario #1 where we use wildcard path mappings for secondary entry-points. + await updateJsonFile('tsconfig.json', (json) => { + json.compilerOptions.paths = { 'mylib': ['dist/mylib'], 'mylib/*': ['dist/mylib/*'] }; + }); + + await writeFile( + 'src/app/app.module.ts', + ` + import {NgModule} from '@angular/core'; + import {BrowserModule} from '@angular/platform-browser'; + import {SecondaryModule} from 'mylib/secondary'; + import {AnotherModule} from 'mylib/another'; + + import {AppComponent} from './app.component'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + SecondaryModule, + AnotherModule, + BrowserModule + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + ); + + await ng('build', 'mylib'); + await ng('build'); + + // Scenario #2 where we don't use wildcard path mappings. + await updateJsonFile('tsconfig.json', (json) => { + json.compilerOptions.paths = { + 'mylib': ['dist/mylib'], + 'mylib/secondary': ['dist/mylib/secondary'], + 'mylib/another': ['dist/mylib/another'], + }; + }); + + await ng('build'); +} + +async function createLibraryEntryPoint(name: string, moduleName: string, entryFileName: string) { + await createDir(`projects/mylib/${name}`); + await writeFile( + `projects/mylib/${name}/${entryFileName}`, + ` + import {NgModule} from '@angular/core'; + + @NgModule({}) + export class ${moduleName} {} + `, + ); + + await writeFile( + `projects/mylib/${name}/ng-package.json`, + JSON.stringify({ + lib: { + entryFile: entryFileName, + }, + }), + ); +} diff --git a/tests/legacy-cli/e2e/tests/build/library/lib-consumption-full-aot.ts b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-full-aot.ts new file mode 100644 index 000000000000..c20142e4a229 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-full-aot.ts @@ -0,0 +1,13 @@ +import { ng } from '../../../utils/process'; +import { libraryConsumptionSetup } from './setup'; + +export default async function () { + await libraryConsumptionSetup(); + + // Build library in full mode (development) + await ng('build', 'my-lib', '--configuration=development'); + + // Check that the e2e succeeds prod and non prod mode + await ng('e2e', '--configuration=production'); + await ng('e2e', '--configuration=development'); +} diff --git a/tests/legacy-cli/e2e/tests/build/library/lib-consumption-full-jit.ts b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-full-jit.ts new file mode 100644 index 000000000000..070fc614f9f8 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-full-jit.ts @@ -0,0 +1,26 @@ +import { updateJsonFile } from '../../../utils/project'; +import { expectFileToMatch } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; +import { libraryConsumptionSetup } from './setup'; + +export default async function () { + await libraryConsumptionSetup(); + + // Build library in full mode (development) + await ng('build', 'my-lib', '--configuration=development'); + + // JIT linking + await updateJsonFile('angular.json', (config) => { + const build = config.projects['test-project'].architect.build; + build.options.aot = false; + build.configurations.production.buildOptimizer = false; + }); + + // Check that the e2e succeeds prod and non prod mode + await ng('e2e', '--configuration=production'); + await ng('e2e', '--configuration=development'); + + // Validate that sourcemaps for the library exists. + await ng('build', '--configuration=development'); + await expectFileToMatch('dist/test-project/main.js.map', 'projects/my-lib/src/public-api.ts'); +} diff --git a/tests/legacy-cli/e2e/tests/build/library/lib-consumption-partial-aot.ts b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-partial-aot.ts new file mode 100644 index 000000000000..647e8b1ee9b7 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-partial-aot.ts @@ -0,0 +1,13 @@ +import { ng } from '../../../utils/process'; +import { libraryConsumptionSetup } from './setup'; + +export default async function () { + await libraryConsumptionSetup(); + + // Build library in partial mode (production) + await ng('build', 'my-lib', '--configuration=production'); + + // Check that the e2e succeeds prod and non prod mode + await ng('e2e', '--configuration=production'); + await ng('e2e', '--configuration=development'); +} diff --git a/tests/legacy-cli/e2e/tests/build/library/lib-consumption-partial-jit.ts b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-partial-jit.ts new file mode 100644 index 000000000000..6c13b5468f2a --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-partial-jit.ts @@ -0,0 +1,21 @@ +import { updateJsonFile } from '../../../utils/project'; +import { ng } from '../../../utils/process'; +import { libraryConsumptionSetup } from './setup'; + +export default async function () { + await libraryConsumptionSetup(); + + // Build library in partial mode (production) + await ng('build', 'my-lib', '--configuration=production'); + + // JIT linking + await updateJsonFile('angular.json', (config) => { + const build = config.projects['test-project'].architect.build; + build.options.aot = false; + build.configurations.production.buildOptimizer = false; + }); + + // Check that the e2e succeeds prod and non prod mode + await ng('e2e', '--configuration=production'); + await ng('e2e', '--configuration=development'); +} diff --git a/tests/legacy-cli/e2e/tests/build/library/lib-consumption-sourcemaps.ts b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-sourcemaps.ts new file mode 100644 index 000000000000..484fcd21bcc3 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library/lib-consumption-sourcemaps.ts @@ -0,0 +1,14 @@ +import { expectFileToMatch } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; +import { libraryConsumptionSetup } from './setup'; + +export default async function () { + await libraryConsumptionSetup(); + + // Build library in full mode (development) + await ng('build', 'my-lib', '--configuration=development'); + + // Validate that sourcemaps for the library exists. + await ng('build', '--configuration=development'); + await expectFileToMatch('dist/test-project/main.js.map', 'projects/my-lib/src/public-api.ts'); +} diff --git a/tests/legacy-cli/e2e/tests/build/library/setup.ts b/tests/legacy-cli/e2e/tests/build/library/setup.ts new file mode 100644 index 000000000000..98bc6e816944 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/library/setup.ts @@ -0,0 +1,79 @@ +import { writeMultipleFiles } from '../../../utils/fs'; +import { silentNg } from '../../../utils/process'; + +export async function libraryConsumptionSetup(): Promise { + await silentNg('generate', 'library', 'my-lib'); + + // Force an external template + await writeMultipleFiles({ + 'projects/my-lib/src/lib/my-lib.component.html': `

my-lib works!

`, + 'projects/my-lib/src/lib/my-lib.component.ts': `import { Component } from '@angular/core'; + + @Component({ + selector: 'lib-my-lib', + templateUrl: './my-lib.component.html', + }) + export class MyLibComponent {}`, + './src/app/app.module.ts': ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + import { MyLibModule } from 'my-lib'; + + import { AppComponent } from './app.component'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + MyLibModule, + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + import { MyLibService } from 'my-lib'; + + @Component({ + selector: 'app-root', + template: '' + }) + export class AppComponent { + title = 'test-project'; + + constructor(myLibService: MyLibService) { + console.log(myLibService); + } + } + `, + 'e2e/src/app.e2e-spec.ts': ` + import { browser, logging, element, by } from 'protractor'; + import { AppPage } from './app.po'; + + describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display text from library component', async () => { + await page.navigateTo(); + expect(await element(by.css('lib-my-lib p')).getText()).toEqual('my-lib works!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + })); + }); + }); +`, + }); +} diff --git a/tests/legacy-cli/e2e/tests/build/material.ts b/tests/legacy-cli/e2e/tests/build/material.ts index 5963d3411d89..a010ea834791 100644 --- a/tests/legacy-cli/e2e/tests/build/material.ts +++ b/tests/legacy-cli/e2e/tests/build/material.ts @@ -1,5 +1,5 @@ import { getGlobalVariable } from '../../utils/env'; -import { replaceInFile } from '../../utils/fs'; +import { readFile, replaceInFile } from '../../utils/fs'; import { installPackage, installWorkspacePackages } from '../../utils/packages'; import { ng } from '../../utils/process'; import { isPrereleaseCli, updateJsonFile } from '../../utils/project'; @@ -7,7 +7,7 @@ import { isPrereleaseCli, updateJsonFile } from '../../utils/project'; const snapshots = require('../../ng-snapshot/package.json'); export default async function () { - const tag = await isPrereleaseCli() ? '@next' : ''; + let tag = (await isPrereleaseCli()) ? '@next' : ''; await ng('add', `@angular/material${tag}`, '--skip-confirmation'); const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; @@ -25,10 +25,15 @@ export default async function () { dependencies['@angular/material-moment-adapter'] = snapshots.dependencies['@angular/material-moment-adapter']; }); - await installWorkspacePackages(); } else { - await installPackage('@angular/material-moment-adapter'); + if (!tag) { + const installedMaterialVersion = JSON.parse(await readFile('package.json'))['dependencies'][ + '@angular/material' + ]; + tag = `@${installedMaterialVersion}`; + } + await installPackage(`@angular/material-moment-adapter${tag}`); } await installPackage('moment'); @@ -72,5 +77,5 @@ export default async function () { `, ); - await ng('e2e', '--prod'); + await ng('e2e', '--configuration=production'); } diff --git a/tests/legacy-cli/e2e/tests/build/multiple-configs.ts b/tests/legacy-cli/e2e/tests/build/multiple-configs.ts index 6897189758de..f25d05f7eff0 100644 --- a/tests/legacy-cli/e2e/tests/build/multiple-configs.ts +++ b/tests/legacy-cli/e2e/tests/build/multiple-configs.ts @@ -4,10 +4,9 @@ import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; export default async function () { - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appArchitect = workspaceJson.projects['test-project'].architect; // These are the default options, that we'll overwrite in subsequent configs. - // extractCss defaults to false // sourceMap defaults to true appArchitect['build'] = { ...appArchitect['build'], @@ -19,20 +18,14 @@ export default async function () { sourceMap: true, outputHashing: 'none', vendorChunk: true, - assets: [ - 'src/favicon.ico', - 'src/assets', - ], - styles: [ - 'src/styles.css', - ], + assets: ['src/favicon.ico', 'src/assets'], + styles: ['src/styles.css'], scripts: [], budgets: [], }, configurations: { development: { sourceMap: true, - extractCss: false, }, one: { assets: [], @@ -40,9 +33,6 @@ export default async function () { two: { sourceMap: false, }, - three: { - extractCss: false, // Defaults to false when not set. - }, }, }; @@ -53,28 +43,16 @@ export default async function () { await ng('build', '--configuration=development'); await expectFileToExist('dist/test-project/favicon.ico'); await expectFileToExist('dist/test-project/main.js.map'); - await expectFileToExist('dist/test-project/styles.js'); await expectFileToExist('dist/test-project/vendor.js'); await ng('build'); await expectFileToExist('dist/test-project/styles.css'); - // But using a config overrides prod. - await ng('build', '--configuration=three'); - await expectFileToExist('dist/test-project/styles.js'); - await expectToFail(() => expectFileToExist('dist/test-project/styles.css')); // Use two configurations. await ng('build', '--configuration=one,two', '--vendor-chunk=false'); await expectToFail(() => expectFileToExist('dist/test-project/favicon.ico')); await expectToFail(() => expectFileToExist('dist/test-project/main.js.map')); // Use two configurations and two overrides, one of which overrides a config. - await ng('build', '--configuration=one,two', '--vendor-chunk=false', '--sourceMap=true'); + await ng('build', '--configuration=one,two', '--vendor-chunk=false', '--source-map=true'); await expectToFail(() => expectFileToExist('dist/test-project/favicon.ico')); await expectFileToExist('dist/test-project/main.js.map'); await expectToFail(() => expectFileToExist('dist/test-project/vendor.js')); - // Use three configuration and check that last on value wins - await ng('build', '--configuration=one,two,three', '--vendor-chunk=false'); - await expectToFail(() => expectFileToExist('dist/test-project/favicon.ico')); - await expectToFail(() => expectFileToExist('dist/test-project/main.js.map')); - await expectToFail(() => expectFileToExist('dist/test-project/vendor.js')); - await expectFileToExist('dist/test-project/styles.js'); - await expectToFail(() => expectFileToExist('dist/test-project/styles.css')); } diff --git a/tests/legacy-cli/e2e/tests/build/no-angular-router.ts b/tests/legacy-cli/e2e/tests/build/no-angular-router.ts index b5c056f6e328..190f1e2a39b1 100644 --- a/tests/legacy-cli/e2e/tests/build/no-angular-router.ts +++ b/tests/legacy-cli/e2e/tests/build/no-angular-router.ts @@ -1,10 +1,9 @@ -import {ng} from '../../utils/process'; -import {expectFileToExist, moveFile} from '../../utils/fs'; -import {getGlobalVariable} from '../../utils/env'; +import { ng } from '../../utils/process'; +import { expectFileToExist, moveFile } from '../../utils/fs'; +import { getGlobalVariable } from '../../utils/env'; import * as path from 'path'; - -export default function() { +export default function () { const tmp = getGlobalVariable('tmp-root'); return Promise.resolve() diff --git a/tests/legacy-cli/e2e/tests/build/no-entry-module.ts b/tests/legacy-cli/e2e/tests/build/no-entry-module.ts index c1119b393149..11f7111fdb17 100644 --- a/tests/legacy-cli/e2e/tests/build/no-entry-module.ts +++ b/tests/legacy-cli/e2e/tests/build/no-entry-module.ts @@ -1,15 +1,13 @@ import { readFile, writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; - -export default async function() { +export default async function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. const mainTs = await readFile('src/main.ts'); - const newMainTs = mainTs - .replace(/platformBrowserDynamic.*?bootstrapModule.*?;/, '') - + 'console.log(AppModule);'; // Use AppModule to make sure it's imported properly. + const newMainTs = + mainTs.replace(/platformBrowserDynamic.*?bootstrapModule.*?;/, '') + 'console.log(AppModule);'; // Use AppModule to make sure it's imported properly. await writeFile('src/main.ts', newMainTs); await ng('build', '--configuration=development'); diff --git a/tests/legacy-cli/e2e/tests/build/no-sourcemap.ts b/tests/legacy-cli/e2e/tests/build/no-sourcemap.ts index 61b1e22e6fa6..5aca54f6bc5e 100644 --- a/tests/legacy-cli/e2e/tests/build/no-sourcemap.ts +++ b/tests/legacy-cli/e2e/tests/build/no-sourcemap.ts @@ -5,11 +5,17 @@ export default async function () { await ng('build', '--output-hashing=none', '--source-map', 'false'); await testForSourceMaps(3); - await ng('build', '--output-hashing=none', '--source-map', 'false', '--configuration=development'); + await ng( + 'build', + '--output-hashing=none', + '--source-map', + 'false', + '--configuration=development', + ); await testForSourceMaps(4); } -async function testForSourceMaps(expectedNumberOfFiles: number): Promise { +async function testForSourceMaps(expectedNumberOfFiles: number): Promise { const files = fs.readdirSync('./dist/test-project'); let count = 0; @@ -31,6 +37,8 @@ async function testForSourceMaps(expectedNumberOfFiles: number): Promise } if (count < expectedNumberOfFiles) { - throw new Error(`Javascript file count is low. Expected ${expectedNumberOfFiles} but found ${count}`); + throw new Error( + `Javascript file count is low. Expected ${expectedNumberOfFiles} but found ${count}`, + ); } } diff --git a/tests/legacy-cli/e2e/tests/build/output-dir.ts b/tests/legacy-cli/e2e/tests/build/output-dir.ts index 43d7f5c237cb..80d1176aeb09 100644 --- a/tests/legacy-cli/e2e/tests/build/output-dir.ts +++ b/tests/legacy-cli/e2e/tests/build/output-dir.ts @@ -1,21 +1,22 @@ -import {expectFileToExist} from '../../utils/fs'; -import {expectGitToBeClean} from '../../utils/git'; -import {ng} from '../../utils/process'; -import {updateJsonFile} from '../../utils/project'; -import {expectToFail} from '../../utils/utils'; +import { expectFileToExist } from '../../utils/fs'; +import { expectGitToBeClean } from '../../utils/git'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; - -export default function() { +export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. return ng('build', '--output-path', 'build-output', '--configuration=development') .then(() => expectFileToExist('./build-output/index.html')) .then(() => expectFileToExist('./build-output/main.js')) .then(() => expectToFail(expectGitToBeClean)) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.outputPath = 'config-build-output'; - })) + .then(() => + updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.outputPath = 'config-build-output'; + }), + ) .then(() => ng('build', '--configuration=development')) .then(() => expectFileToExist('./config-build-output/index.html')) .then(() => expectFileToExist('./config-build-output/main.js')) diff --git a/tests/legacy-cli/e2e/tests/build/output-hashing.ts b/tests/legacy-cli/e2e/tests/build/output-hashing.ts index 7afa86d646b5..aa16200498c2 100644 --- a/tests/legacy-cli/e2e/tests/build/output-hashing.ts +++ b/tests/legacy-cli/e2e/tests/build/output-hashing.ts @@ -2,7 +2,6 @@ import { copyProjectAsset } from '../../utils/assets'; import { expectFileMatchToExist, expectFileToMatch, writeMultipleFiles } from '../../utils/fs'; import { ng } from '../../utils/process'; - async function verifyMedia(fileNameRe: RegExp, content: RegExp) { const fileName = await expectFileMatchToExist('dist/test-project/', fileNameRe); await expectFileToMatch(`dist/test-project/${fileName}`, content); @@ -15,10 +14,10 @@ export default async function () { // use image with file size >10KB to prevent inlining await copyProjectAsset('images/spectrum.png', './src/assets/image.png'); await ng('build', '--output-hashing=all', '--configuration=development'); - await expectFileToMatch('dist/test-project/index.html', /runtime\.[0-9a-f]{20}\.js/); - await expectFileToMatch('dist/test-project/index.html', /main\.[0-9a-f]{20}\.js/); - await expectFileToMatch('dist/test-project/index.html', /styles\.[0-9a-f]{20}\.(css|js)/); - await verifyMedia(/styles\.[0-9a-f]{20}\.(css|js)/, /image\.[0-9a-f]{20}\.png/); + await expectFileToMatch('dist/test-project/index.html', /runtime\.[0-9a-f]{16}\.js/); + await expectFileToMatch('dist/test-project/index.html', /main\.[0-9a-f]{16}\.js/); + await expectFileToMatch('dist/test-project/index.html', /styles\.[0-9a-f]{16}\.(css|js)/); + await verifyMedia(/styles\.[0-9a-f]{16}\.(css|js)/, /image\.[0-9a-f]{16}\.png/); await ng('build', '--output-hashing=none', '--configuration=development'); await expectFileToMatch('dist/test-project/index.html', /runtime\.js/); @@ -30,11 +29,11 @@ export default async function () { await expectFileToMatch('dist/test-project/index.html', /runtime\.js/); await expectFileToMatch('dist/test-project/index.html', /main\.js/); await expectFileToMatch('dist/test-project/index.html', /styles\.(css|js)/); - await verifyMedia(/styles\.(css|js)/, /image\.[0-9a-f]{20}\.png/); + await verifyMedia(/styles\.(css|js)/, /image\.[0-9a-f]{16}\.png/); await ng('build', '--output-hashing=bundles', '--configuration=development'); - await expectFileToMatch('dist/test-project/index.html', /runtime\.[0-9a-f]{20}\.js/); - await expectFileToMatch('dist/test-project/index.html', /main\.[0-9a-f]{20}\.js/); - await expectFileToMatch('dist/test-project/index.html', /styles\.[0-9a-f]{20}\.(css|js)/); - await verifyMedia(/styles\.[0-9a-f]{20}\.(css|js)/, /image\.png/); + await expectFileToMatch('dist/test-project/index.html', /runtime\.[0-9a-f]{16}\.js/); + await expectFileToMatch('dist/test-project/index.html', /main\.[0-9a-f]{16}\.js/); + await expectFileToMatch('dist/test-project/index.html', /styles\.[0-9a-f]{16}\.(css|js)/); + await verifyMedia(/styles\.[0-9a-f]{16}\.(css|js)/, /image\.png/); } diff --git a/tests/legacy-cli/e2e/tests/build/platform-server.ts b/tests/legacy-cli/e2e/tests/build/platform-server.ts index 5169ca644293..7dedb5e06966 100644 --- a/tests/legacy-cli/e2e/tests/build/platform-server.ts +++ b/tests/legacy-cli/e2e/tests/build/platform-server.ts @@ -8,15 +8,15 @@ import { updateJsonFile } from '../../utils/project'; const snapshots = require('../../ng-snapshot/package.json'); export default async function () { - await ng('generate', 'universal', '--client-project', 'test-project'); + await ng('generate', 'universal', '--project', 'test-project'); const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; if (isSnapshotBuild) { - const packagesToInstall = []; + const packagesToInstall: string[] = []; await updateJsonFile('package.json', (packageJson) => { const dependencies = packageJson['dependencies']; // Iterate over all of the packages to update them to the snapshot version. - for (const [name, version] of Object.entries(snapshots.dependencies)) { + for (const [name, version] of Object.entries(snapshots.dependencies)) { if (name in dependencies && dependencies[name] !== version) { packagesToInstall.push(version); } @@ -32,7 +32,8 @@ export default async function () { './server.ts', ` import 'zone.js/dist/zone-node'; import * as fs from 'fs'; - import { AppServerModule, renderModule } from './src/main.server'; + import { renderModule } from '@angular/platform-server'; + import { AppServerModule } from './src/main.server'; renderModule(AppServerModule, { url: '/', @@ -48,15 +49,18 @@ export default async function () { await ng('run', 'test-project:server', '--optimization', 'false'); - await expectFileToMatch('dist/test-project/server/main.js', /exports.*AppServerModule|"AppServerModule":/); + await expectFileToMatch( + 'dist/test-project/server/main.js', + /exports.*AppServerModule|"AppServerModule":/, + ); await exec(normalize('node'), 'dist/test-project/server/main.js'); await expectFileToMatch( 'dist/test-project/server/index.html', /Here are some links to help you get started:<\/p>/, ); - // works with optimization and bundleDependencies enabled - await ng('run', 'test-project:server', '--optimization', '--bundleDependencies'); + // works with optimization + await ng('run', 'test-project:server', '--optimization'); await exec(normalize('node'), 'dist/test-project/server/main.js'); await expectFileToMatch( 'dist/test-project/server/index.html', diff --git a/tests/legacy-cli/e2e/tests/build/poll.ts b/tests/legacy-cli/e2e/tests/build/poll.ts index 9a63d5edb1c2..e2f3347324de 100644 --- a/tests/legacy-cli/e2e/tests/build/poll.ts +++ b/tests/legacy-cli/e2e/tests/build/poll.ts @@ -1,14 +1,11 @@ import { appendToFile } from '../../utils/fs'; -import { - killAllProcesses, - waitForAnyProcessOutputToMatch, -} from '../../utils/process'; +import { killAllProcesses, waitForAnyProcessOutputToMatch } from '../../utils/process'; import { ngServe } from '../../utils/project'; import { expectToFail, wait } from '../../utils/utils'; const webpackGoodRegEx = / Compiled successfully./; -export default async function() { +export default async function () { try { await ngServe('--poll=10000'); @@ -27,6 +24,6 @@ export default async function() { // But a rebuild should happen roughly within the 10 second window. await waitForAnyProcessOutputToMatch(webpackGoodRegEx, 7000); } finally { - killAllProcesses(); + await killAllProcesses(); } } diff --git a/tests/legacy-cli/e2e/tests/build/polyfills.ts b/tests/legacy-cli/e2e/tests/build/polyfills.ts index ad94bc58a3f1..e8184ffc4723 100644 --- a/tests/legacy-cli/e2e/tests/build/polyfills.ts +++ b/tests/legacy-cli/e2e/tests/build/polyfills.ts @@ -1,43 +1,29 @@ -import { oneLineTrim } from 'common-tags'; import { expectFileSizeToBeUnder, expectFileToExist, expectFileToMatch, getFileSize, - replaceInFile, } from '../../utils/fs'; import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; export default async function () { - // Enable Differential loading to run both size checks - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - await ng('build', '--aot=false', '--configuration=development'); - // files were created successfully - await expectFileToMatch('dist/test-project/polyfills-es5.js', 'core-js/proposals/reflect-metadata'); - await expectFileToMatch('dist/test-project/polyfills-es5.js', 'zone.js'); - await expectFileToMatch('dist/test-project/index.html', oneLineTrim` - - - - - `)), - ) - // also check when css isn't extracted - .then(() => ng('build', '--no-extract-css', '--configuration=development')) - // files were created successfully - .then(() => expectFileToMatch('dist/test-project/styles.js', '.string-style')) - .then(() => expectFileToMatch('dist/test-project/styles.js', '.input-style')) - .then(() => expectFileToMatch('dist/test-project/lazy-style.js', '.lazy-style')) - .then(() => expectFileToMatch('dist/test-project/renamed-style.js', '.pre-rename-style')) - .then(() => - expectFileToMatch('dist/test-project/renamed-lazy-style.js', '.pre-rename-lazy-style'), - ) - .then(() => - expectFileToMatch( - 'dist/test-project/renamed-lazy-style.js', - '.pre-rename-lazy-style', - ), - ) - // index.html lists the right bundles - .then(() => - expectFileToMatch( - 'dist/test-project/index.html', - oneLineTrim` - - - `, - ), - ) - ); -} diff --git a/tests/legacy-cli/e2e/tests/build/styles/imports.ts b/tests/legacy-cli/e2e/tests/build/styles/imports.ts deleted file mode 100644 index 9bfb6023775b..000000000000 --- a/tests/legacy-cli/e2e/tests/build/styles/imports.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - writeMultipleFiles, - expectFileToMatch, - replaceInFile -} from '../../../utils/fs'; -import { expectToFail } from '../../../utils/utils'; -import { ng } from '../../../utils/process'; -import { stripIndents } from 'common-tags'; -import { updateJsonFile } from '../../../utils/project'; -import { getGlobalVariable } from '../../../utils/env'; - -export default function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - const extensions = ['css', 'scss', 'less', 'styl']; - let promise = Promise.resolve(); - - extensions.forEach(ext => { - promise = promise.then(() => { - return writeMultipleFiles({ - [`src/styles.${ext}`]: stripIndents` - @import 'https://github.com/imported-styles.$%7Bext%7D'; - body { background-color: #00f; } - `, - [`src/imported-styles.${ext}`]: stripIndents` - p { background-color: #f00; } - `, - [`src/app/app.component.${ext}`]: stripIndents` - @import 'https://github.com/imported-component-styles.$%7Bext%7D'; - .outer { - .inner { - background: #fff; - } - } - `, - [`src/app/imported-component-styles.${ext}`]: stripIndents` - h1 { background: #000; } - `}) - // change files to use preprocessor - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: `src/styles.${ext}` }, - ]; - })) - .then(() => replaceInFile('src/app/app.component.ts', - './app.component.css', `./app.component.${ext}`)) - // run build app - .then(() => ng('build', '--extract-css', '--source-map', '--configuration=development')) - // verify global styles - .then(() => expectFileToMatch('dist/test-project/styles.css', - /body\s*{\s*background-color: #00f;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /p\s*{\s*background-color: #f00;\s*}/)) - // verify global styles sourcemap - .then(() => expectToFail(() => - expectFileToMatch('dist/test-project/styles.css', '"mappings":""'))) - // verify component styles - .then(() => expectFileToMatch('dist/test-project/main.js', - /.outer.*.inner.*background:\s*#[fF]+/)) - .then(() => expectFileToMatch('dist/test-project/main.js', - /h1.*background:\s*#000+/)) - // Also check imports work on ng test - .then(() => ng('test', '--watch=false')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: `src/styles.css` }, - ]; - })) - .then(() => replaceInFile('src/app/app.component.ts', - `./app.component.${ext}`, './app.component.css')); - }); - }); - - return promise; -} diff --git a/tests/legacy-cli/e2e/tests/build/styles/include-paths.ts b/tests/legacy-cli/e2e/tests/build/styles/include-paths.ts index fb4cf638900a..711bb7d3b8dc 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/include-paths.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/include-paths.ts @@ -1,74 +1,67 @@ -import { - writeMultipleFiles, - expectFileToMatch, - replaceInFile, - createDir -} from '../../../utils/fs'; +import { getGlobalVariable } from '../../../utils/env'; +import { writeMultipleFiles, expectFileToMatch, replaceInFile, createDir } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { updateJsonFile } from '../../../utils/project'; -export default function () { - return Promise.resolve() - .then(() => createDir('src/style-paths')) - .then(() => writeMultipleFiles({ - 'src/style-paths/_variables.scss': '$primary-color: red;', - 'src/styles.scss': ` +export default async function () { + // esbuild currently only supports Sass + const esbuild = getGlobalVariable('argv')['esbuild']; + + await createDir('src/style-paths'); + await writeMultipleFiles({ + 'src/style-paths/_variables.scss': '$primary-color: red;', + 'src/styles.scss': ` @import 'https://github.com/variables'; h1 { color: $primary-color; } - `, - 'src/app/app.component.scss': ` + `, + 'src/app/app.component.scss': ` @import 'https://github.com/variables'; h2 { background-color: $primary-color; } - `, - 'src/style-paths/variables.styl': '$primary-color = green', - 'src/styles.styl': ` - @import 'https://github.com/variables' - h3 - color: $primary-color - `, - 'src/app/app.component.styl': ` - @import 'https://github.com/variables' - h4 - background-color: $primary-color - `, - 'src/style-paths/variables.less': '@primary-color: #ADDADD;', - 'src/styles.less': ` + `, + 'src/style-paths/variables.less': '@primary-color: #ADDADD;', + 'src/styles.less': ` @import 'https://github.com/variables'; h5 { color: @primary-color; } - `, - 'src/app/app.component.less': ` + `, + 'src/app/app.component.less': ` @import 'https://github.com/variables'; h6 { color: @primary-color; } - ` - })) - .then(() => replaceInFile('src/app/app.component.ts', `'./app.component.css\'`, - `'./app.component.scss', './app.component.styl', './app.component.less'`)) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/styles.scss' }, - { input: 'src/styles.styl' }, - { input: 'src/styles.less' }, - ]; - appArchitect.build.options.stylePreprocessorOptions = { - includePaths: [ - 'src/style-paths' - ] - }; - })) - // files were created successfully - .then(() => ng('build', '--extract-css', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', /h3\s*{\s*color: #008000;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/main.js', /h4.*{.*color: #008000;.*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/)) - .then(() => ng('build', '--extract-css', '--aot', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', /h3\s*{\s*color: #008000;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/main.js', /h4.*{.*color: #008000;.*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/)); + `, + }); + + await replaceInFile( + 'src/app/app.component.ts', + `'./app.component.css\'`, + `'./app.component.scss'` + (esbuild ? '' : `, './app.component.less'`), + ); + + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [{ input: 'src/styles.scss' }]; + if (!esbuild) { + appArchitect.build.options.styles.push({ input: 'src/styles.less' }); + } + appArchitect.build.options.stylePreprocessorOptions = { + includePaths: ['src/style-paths'], + }; + }); + + await ng('build', '--configuration=development'); + await expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/); + await expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/); + if (!esbuild) { + // These checks are for the less files + await expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/); + await expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/); + } + + // esbuild currently only supports AOT and not JIT mode + if (!esbuild) { + await ng('build', '--no-aot', '--configuration=development'); + + await expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/); + await expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/); + await expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/); + await expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/); + } } diff --git a/tests/legacy-cli/e2e/tests/build/styles/less.ts b/tests/legacy-cli/e2e/tests/build/styles/less.ts index a5bde1be810d..da464cec3965 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/less.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/less.ts @@ -2,45 +2,50 @@ import { writeMultipleFiles, deleteFile, expectFileToMatch, - replaceInFile + replaceInFile, } from '../../../utils/fs'; import { expectToFail } from '../../../utils/utils'; import { ng } from '../../../utils/process'; -import { stripIndents } from 'common-tags'; import { updateJsonFile } from '../../../utils/project'; export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. return writeMultipleFiles({ - 'src/styles.less': stripIndents` + 'src/styles.less': ` @import 'https://github.com/imported-styles.less'; body { background-color: blue; } `, - 'src/imported-styles.less': stripIndents` - p { background-color: red; } - `, - 'src/app/app.component.less': stripIndents` + 'src/imported-styles.less': 'p { background-color: red; }', + 'src/app/app.component.less': ` .outer { .inner { background: #fff; } } - `}) + `, + }) .then(() => deleteFile('src/app/app.component.css')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/styles.less' }, - ]; - })) - .then(() => replaceInFile('src/app/app.component.ts', - './app.component.css', './app.component.less')) - .then(() => ng('build', '--extract-css', '--source-map', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /body\s*{\s*background-color: blue;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /p\s*{\s*background-color: red;\s*}/)) - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""'))) - .then(() => expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/)); + .then(() => + updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [{ input: 'src/styles.less' }]; + }), + ) + .then(() => + replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.less'), + ) + .then(() => ng('build', '--source-map', '--configuration=development')) + .then(() => + expectFileToMatch('dist/test-project/styles.css', /body\s*{\s*background-color: blue;\s*}/), + ) + .then(() => + expectFileToMatch('dist/test-project/styles.css', /p\s*{\s*background-color: red;\s*}/), + ) + .then(() => + expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""')), + ) + .then(() => + expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/), + ); } diff --git a/tests/legacy-cli/e2e/tests/build/styles/loaders.ts b/tests/legacy-cli/e2e/tests/build/styles/loaders.ts index aeed8399201c..8e8f560caf79 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/loaders.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/loaders.ts @@ -2,40 +2,39 @@ import { writeMultipleFiles, deleteFile, expectFileToMatch, - replaceInFile + replaceInFile, } from '../../../utils/fs'; import { ng } from '../../../utils/process'; -import { stripIndents } from 'common-tags'; import { updateJsonFile } from '../../../utils/project'; import { expectToFail } from '../../../utils/utils'; -export default function () { - return writeMultipleFiles({ - 'src/styles.scss': stripIndents` +export default async function () { + await writeMultipleFiles({ + 'src/styles.scss': ` @import 'https://github.com/imported-styles.scss'; body { background-color: blue; } `, - 'src/imported-styles.scss': stripIndents` - p { background-color: red; } - `, - 'src/app/app.component.scss': stripIndents` - .outer { - .inner { - background: #fff; - } + 'src/imported-styles.scss': 'p { background-color: red; }', + 'src/app/app.component.scss': ` + .outer { + .inner { + background: #fff; } - `}) - .then(() => deleteFile('src/app/app.component.css')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/styles.scss' }, - ]; - })) - .then(() => replaceInFile('src/app/app.component.ts', - './app.component.css', './app.component.scss')) - .then(() => ng('build', '--configuration=development')) - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/styles.css', /exports/))) - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/main-es5.js', - /".*module\.exports.*\.outer.*background:/))); + } + `, + }); + + await deleteFile('src/app/app.component.css'); + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [{ input: 'src/styles.scss' }]; + }); + await replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss'); + + await ng('build', '--configuration=development'); + + await expectToFail(() => expectFileToMatch('dist/test-project/styles.css', /exports/)); + await expectToFail(() => + expectFileToMatch('dist/test-project/main.js', /".*module\.exports.*\.outer.*background:/), + ); } diff --git a/tests/legacy-cli/e2e/tests/build/styles/material-import.ts b/tests/legacy-cli/e2e/tests/build/styles/material-import.ts deleted file mode 100644 index 00b7193522ec..000000000000 --- a/tests/legacy-cli/e2e/tests/build/styles/material-import.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { stripIndents } from 'common-tags'; -import { getGlobalVariable } from '../../../utils/env'; -import { - replaceInFile, - writeMultipleFiles, -} from '../../../utils/fs'; -import { installWorkspacePackages } from '../../../utils/packages'; -import { ng } from '../../../utils/process'; -import { isPrereleaseCli, updateJsonFile } from '../../../utils/project'; - -const snapshots = require('../../../ng-snapshot/package.json'); - -export default async function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; - const tag = await isPrereleaseCli() ? 'next' : 'latest'; - - await updateJsonFile('package.json', packageJson => { - const dependencies = packageJson['dependencies']; - dependencies['@angular/material'] = isSnapshotBuild ? snapshots.dependencies['@angular/material'] : tag; - dependencies['@angular/cdk'] = isSnapshotBuild ? snapshots.dependencies['@angular/cdk'] : tag; - }); - - await installWorkspacePackages(); - - for (const ext of ['css', 'scss', 'less', 'styl']) { - await writeMultipleFiles({ - [`src/styles.${ext}`]: stripIndents` - @import "https://github.com/~@angular/material/prebuilt-themes/indigo-pink.css"; - `, - [`src/app/app.component.${ext}`]: stripIndents` - @import "https://github.com/~@angular/material/prebuilt-themes/indigo-pink.css"; - `, - }); - - // change files to use preprocessor - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: `src/styles.${ext}` }, - ]; - }); - - await replaceInFile('src/app/app.component.ts', './app.component.css', `./app.component.${ext}`); - - // run build app - await ng('build', '--extract-css', '--source-map', '--configuration=development'); - await writeMultipleFiles({ - [`src/styles.${ext}`]: stripIndents` - @import "https://github.com/@angular/material/prebuilt-themes/indigo-pink.css"; - `, - [`src/app/app.component.${ext}`]: stripIndents` - @import "https://github.com/@angular/material/prebuilt-themes/indigo-pink.css"; - `, - }); - - await ng('build', '--extract-css', '--configuration=development'); - } -} diff --git a/tests/legacy-cli/e2e/tests/build/styles/node-sass.ts b/tests/legacy-cli/e2e/tests/build/styles/node-sass.ts deleted file mode 100644 index b6d8a827e58f..000000000000 --- a/tests/legacy-cli/e2e/tests/build/styles/node-sass.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - deleteFile, - expectFileToMatch, - replaceInFile, - writeMultipleFiles, -} from '../../../utils/fs'; -import { installPackage } from '../../../utils/packages'; -import { ng, silentExec } from '../../../utils/process'; -import { updateJsonFile } from '../../../utils/project'; -import { expectToFail } from '../../../utils/utils'; - - -export default async function () { - if (process.platform.startsWith('win')) { - return; - } - - await writeMultipleFiles({ - 'src/styles.scss': '@import \'./imported-styles.scss\';\nbody { background-color: blue; }', - 'src/imported-styles.scss': 'p { background-color: red; }', - 'src/app/app.component.scss': '.outer { .inner { background: #fff; } }', - }); - await deleteFile('src/app/app.component.css'); - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/styles.scss' }, - ]; - }); - await replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss'); - - await silentExec('rm', '-rf', 'node_modules/node-sass'); - await silentExec('rm', '-rf', 'node_modules/sass'); - await expectToFail(() => ng('build', '--extract-css', '--source-map', '--configuration=development')); - - await installPackage('node-sass'); - await silentExec('rm', '-rf', 'node_modules/sass'); - await ng('build', '--extract-css', '--source-map', '--configuration=development'); - - await expectFileToMatch('dist/test-project/styles.css', /body\s*{\s*background-color: blue;\s*}/); - await expectFileToMatch('dist/test-project/styles.css', /p\s*{\s*background-color: red;\s*}/); - await expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""')); - await expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/); - - await installPackage('node-gyp'); - await installPackage('fibers'); - await installPackage('sass'); - await silentExec('rm', '-rf', 'node_modules/node-sass'); - await ng('build', '--extract-css', '--source-map', '--configuration=development'); - - await expectFileToMatch('dist/test-project/styles.css', /body\s*{\s*background-color: blue;\s*}/); - await expectFileToMatch('dist/test-project/styles.css', /p\s*{\s*background-color: red;\s*}/); - await expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""')); - await expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/); -} diff --git a/tests/legacy-cli/e2e/tests/build/styles/preset-env.ts b/tests/legacy-cli/e2e/tests/build/styles/preset-env.ts deleted file mode 100644 index f169529e4f08..000000000000 --- a/tests/legacy-cli/e2e/tests/build/styles/preset-env.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expectFileToMatch, replaceInFile, writeMultipleFiles } from '../../../utils/fs'; -import { ng } from '../../../utils/process'; - -export default async function () { - await writeMultipleFiles({ - 'src/styles.css': `a { - all: initial; - }`, - }); - - // Enable IE 11 support - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - - await ng('build', '--configuration=development'); - await expectFileToMatch('dist/test-project/styles.css', 'z-index: auto'); - await expectFileToMatch('dist/test-project/styles.css', 'all: initial'); -} diff --git a/tests/legacy-cli/e2e/tests/build/styles/scss-legacy.ts b/tests/legacy-cli/e2e/tests/build/styles/scss-legacy.ts new file mode 100644 index 000000000000..c872d3170d53 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/scss-legacy.ts @@ -0,0 +1,55 @@ +import { + writeMultipleFiles, + deleteFile, + expectFileToMatch, + replaceInFile, +} from '../../../utils/fs'; +import { expectToFail } from '../../../utils/utils'; +import { execWithEnv } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; +import assert from 'assert'; + +export default async function () { + await writeMultipleFiles({ + 'src/styles.scss': ` + @import 'https://github.com/imported-styles.scss'; + body { background-color: blue; } + `, + 'src/imported-styles.scss': 'p { background-color: red; }', + 'src/app/app.component.scss': ` + .outer { + .inner { + background: #fff; + } + } + `, + }); + + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [{ input: 'src/styles.scss' }]; + }); + + await deleteFile('src/app/app.component.css'); + await replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss'); + + const { stderr } = await execWithEnv( + 'ng', + ['build', '--source-map', '--configuration=development'], + { + ...process.env, + NG_BUILD_LEGACY_SASS: '1', + }, + ); + + assert.match( + stderr, + /Warning: 'NG_BUILD_LEGACY_SASS'/, + `Expected stderr to contain 'NG_BUILD_LEGACY_SASS' usage warning`, + ); + + await expectFileToMatch('dist/test-project/styles.css', /body\s*{\s*background-color: blue;\s*}/); + await expectFileToMatch('dist/test-project/styles.css', /p\s*{\s*background-color: red;\s*}/); + await expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""')); + await expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/); +} diff --git a/tests/legacy-cli/e2e/tests/build/styles/scss-partial-resolution.ts b/tests/legacy-cli/e2e/tests/build/styles/scss-partial-resolution.ts new file mode 100644 index 000000000000..f3863ad72adb --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/scss-partial-resolution.ts @@ -0,0 +1,31 @@ +import { installPackage } from '../../../utils/packages'; +import { writeMultipleFiles, deleteFile, replaceInFile } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +export default async function () { + // Supports resolving node_modules with are pointing to partial files partial files. + // @material/button/button below points to @material/button/_button.scss + // https://unpkg.com/browse/@material/button@14.0.0/_button.scss + + await installPackage('@material/button@14.0.0'); + + await writeMultipleFiles({ + 'src/styles.scss': ` + @use '@material/button/button' as mat; + `, + 'src/app/app.component.scss': ` + @use '@material/button/button' as mat; + `, + }); + + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = ['src/styles.scss']; + }); + + await deleteFile('src/app/app.component.css'); + await replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss'); + + await ng('build', '--configuration=development'); +} diff --git a/tests/legacy-cli/e2e/tests/build/styles/scss.ts b/tests/legacy-cli/e2e/tests/build/styles/scss.ts index 34b5881c976f..6c68c1fc8240 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/scss.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/scss.ts @@ -2,45 +2,40 @@ import { writeMultipleFiles, deleteFile, expectFileToMatch, - replaceInFile + replaceInFile, } from '../../../utils/fs'; import { expectToFail } from '../../../utils/utils'; import { ng } from '../../../utils/process'; -import { stripIndents } from 'common-tags'; import { updateJsonFile } from '../../../utils/project'; -export default function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - return writeMultipleFiles({ - 'src/styles.scss': stripIndents` +export default async function () { + await writeMultipleFiles({ + 'src/styles.scss': ` @import 'https://github.com/imported-styles.scss'; body { background-color: blue; } `, - 'src/imported-styles.scss': stripIndents` - p { background-color: red; } - `, - 'src/app/app.component.scss': stripIndents` + 'src/imported-styles.scss': 'p { background-color: red; }', + 'src/app/app.component.scss': ` .outer { .inner { background: #fff; } } - `}) - .then(() => deleteFile('src/app/app.component.css')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/styles.scss' }, - ]; - })) - .then(() => replaceInFile('src/app/app.component.ts', - './app.component.css', './app.component.scss')) - .then(() => ng('build', '--extract-css', '--source-map', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /body\s*{\s*background-color: blue;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /p\s*{\s*background-color: red;\s*}/)) - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""'))) - .then(() => expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/)); + `, + }); + + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [{ input: 'src/styles.scss' }]; + }); + + await deleteFile('src/app/app.component.css'); + await replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss'); + + await ng('build', '--source-map', '--configuration=development'); + + await expectFileToMatch('dist/test-project/styles.css', /body\s*{\s*background-color: blue;\s*}/); + await expectFileToMatch('dist/test-project/styles.css', /p\s*{\s*background-color: red;\s*}/); + await expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""')); + await expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/); } diff --git a/tests/legacy-cli/e2e/tests/build/styles/stylus.ts b/tests/legacy-cli/e2e/tests/build/styles/stylus.ts deleted file mode 100644 index ae05f7048180..000000000000 --- a/tests/legacy-cli/e2e/tests/build/styles/stylus.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - writeMultipleFiles, - deleteFile, - expectFileToMatch, - replaceInFile -} from '../../../utils/fs'; -import { expectToFail } from '../../../utils/utils'; -import { ng } from '../../../utils/process'; -import { stripIndents } from 'common-tags'; -import { updateJsonFile } from '../../../utils/project'; - -export default function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - return writeMultipleFiles({ - 'src/styles.styl': stripIndents` - @import 'https://github.com/imported-styles.styl'; - body { background-color: blue; } - `, - 'src/imported-styles.styl': stripIndents` - p { background-color: red; } - `, - 'src/app/app.component.styl': stripIndents` - .outer { - .inner { - background: #fff; - } - } - `}) - .then(() => deleteFile('src/app/app.component.css')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'src/styles.styl' }, - ]; - })) - .then(() => replaceInFile('src/app/app.component.ts', - './app.component.css', './app.component.styl')) - .then(() => ng('build', '--extract-css', '--source-map', '--configuration=development')) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /body\s*{\s*background-color: #00f;\s*}/)) - .then(() => expectFileToMatch('dist/test-project/styles.css', - /p\s*{\s*background-color: #f00;\s*}/)) - .then(() => expectToFail(() => expectFileToMatch('dist/test-project/styles.css', '"mappings":""'))) - .then(() => expectFileToMatch('dist/test-project/main.js', /.outer.*.inner.*background:\s*#[fF]+/)); -} diff --git a/tests/legacy-cli/e2e/tests/build/styles/symlinked-global.ts b/tests/legacy-cli/e2e/tests/build/styles/symlinked-global.ts index c0b92b7cb102..2d37fe7dfab7 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/symlinked-global.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/symlinked-global.ts @@ -10,17 +10,11 @@ export default async function () { 'src/styles-for-link.scss': `p { color: blue }`, }); - symlinkSync( - resolve('src/styles-for-link.scss'), - resolve('src/styles-linked.scss'), - ); + symlinkSync(resolve('src/styles-for-link.scss'), resolve('src/styles-linked.scss')); - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - 'src/styles.scss', - 'src/styles-linked.scss', - ]; + appArchitect.build.options.styles = ['src/styles.scss', 'src/styles-linked.scss']; }); await ng('build', '--configuration=development'); diff --git a/tests/legacy-cli/e2e/tests/build/styles/tailwind-v2.ts b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v2.ts new file mode 100644 index 000000000000..0aef1276339f --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v2.ts @@ -0,0 +1,56 @@ +import { deleteFile, expectFileToMatch, writeFile } from '../../../utils/fs'; +import { installPackage, uninstallPackage } from '../../../utils/packages'; +import { ng, silentExec } from '../../../utils/process'; +import { expectToFail } from '../../../utils/utils'; + +export default async function () { + // Temporarily turn off caching until the build cache accounts for the presence of tailwind + // and its configuration file. Otherwise cached builds without tailwind will cause test failures. + await ng('cache', 'off'); + + // Create configuration file + await silentExec('npx', 'tailwindcss@2', 'init'); + + // Add Tailwind directives to a component style + await writeFile('src/app/app.component.css', '@tailwind base; @tailwind components;'); + + // Add Tailwind directives to a global style + await writeFile('src/styles.css', '@tailwind base; @tailwind components;'); + + // Ensure installation warning is present + const { stderr } = await ng('build', '--configuration=development'); + if (!stderr.includes("To enable Tailwind CSS, please install the 'tailwindcss' package.")) { + throw new Error(`Expected tailwind installation warning. STDERR:\n${stderr}`); + } + + // Tailwind directives should be unprocessed with missing package + await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); + await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); + + // Install Tailwind + await installPackage('tailwindcss@2'); + + // Build should succeed and process Tailwind directives + await ng('build', '--configuration=development'); + + // Check for Tailwind output + await expectFileToMatch('dist/test-project/styles.css', /::placeholder/); + await expectFileToMatch('dist/test-project/main.js', /::placeholder/); + await expectToFail(() => + expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'), + ); + await expectToFail(() => + expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'), + ); + + // Remove configuration file + await deleteFile('tailwind.config.js'); + + // Ensure Tailwind is disabled when no configuration file is present + await ng('build', '--configuration=development'); + await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); + await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); + + // Uninstall Tailwind + await uninstallPackage('tailwindcss'); +} diff --git a/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3-cjs.ts b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3-cjs.ts new file mode 100644 index 000000000000..3bf5ff9c4c5b --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3-cjs.ts @@ -0,0 +1,37 @@ +import { expectFileToMatch, writeFile } from '../../../utils/fs'; +import { installPackage, uninstallPackage } from '../../../utils/packages'; +import { ng, silentExec } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; +import { expectToFail } from '../../../utils/utils'; + +export default async function () { + // Temporarily turn off caching until the build cache accounts for the presence of tailwind + // and its configuration file. Otherwise cached builds without tailwind will cause test failures. + await ng('cache', 'off'); + + // Add type module in package.json. + await updateJsonFile('package.json', (json) => { + json['type'] = 'module'; + }); + + // Install Tailwind + await installPackage('tailwindcss@3'); + + // Create configuration file + await silentExec('npx', 'tailwindcss', 'init'); + + // Add Tailwind directives to a global style + await writeFile('src/styles.css', '@tailwind base; @tailwind components;'); + + // Build should succeed and process Tailwind directives + await ng('build', '--configuration=development'); + + // Check for Tailwind output + await expectFileToMatch('dist/test-project/styles.css', /::placeholder/); + await expectToFail(() => + expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'), + ); + + // Uninstall Tailwind + await uninstallPackage('tailwindcss'); +} diff --git a/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts new file mode 100644 index 000000000000..d07c37d304f3 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts @@ -0,0 +1,56 @@ +import { deleteFile, expectFileToMatch, writeFile } from '../../../utils/fs'; +import { installPackage, uninstallPackage } from '../../../utils/packages'; +import { ng, silentExec } from '../../../utils/process'; +import { expectToFail } from '../../../utils/utils'; + +export default async function () { + // Temporarily turn off caching until the build cache accounts for the presence of tailwind + // and its configuration file. Otherwise cached builds without tailwind will cause test failures. + await ng('cache', 'off'); + + // Create configuration file + await silentExec('npx', 'tailwindcss@3', 'init'); + + // Add Tailwind directives to a component style + await writeFile('src/app/app.component.css', '@tailwind base; @tailwind components;'); + + // Add Tailwind directives to a global style + await writeFile('src/styles.css', '@tailwind base; @tailwind components;'); + + // Ensure installation warning is present + const { stderr } = await ng('build', '--configuration=development'); + if (!stderr.includes("To enable Tailwind CSS, please install the 'tailwindcss' package.")) { + throw new Error('Expected tailwind installation warning'); + } + + // Tailwind directives should be unprocessed with missing package + await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); + await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); + + // Install Tailwind + await installPackage('tailwindcss@3'); + + // Build should succeed and process Tailwind directives + await ng('build', '--configuration=development'); + + // Check for Tailwind output + await expectFileToMatch('dist/test-project/styles.css', /::placeholder/); + await expectFileToMatch('dist/test-project/main.js', /::placeholder/); + await expectToFail(() => + expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'), + ); + await expectToFail(() => + expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'), + ); + + // Remove configuration file + await deleteFile('tailwind.config.js'); + + // Ensure Tailwind is disabled when no configuration file is present + await ng('build', '--configuration=development'); + await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); + await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); + + // Uninstall Tailwind + await uninstallPackage('tailwindcss'); +} diff --git a/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts b/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts deleted file mode 100644 index 51f317533384..000000000000 --- a/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { deleteFile, expectFileToMatch, writeFile } from '../../../utils/fs'; -import { installPackage, uninstallPackage } from '../../../utils/packages'; -import { ng, silentExec } from '../../../utils/process'; -import { expectToFail } from '../../../utils/utils'; - -export default async function () { - // Tailwind is not supported in Node.js 10 - if (process.version.startsWith('v10')) { - return; - } - - // Install Tailwind - await installPackage('tailwindcss'); - - // Create configuration file - await silentExec('npx', 'tailwindcss', 'init'); - - // Add Tailwind directives to a component style - await writeFile('src/app/app.component.css', '@tailwind base; @tailwind components;'); - - // Add Tailwind directives to a global style - await writeFile('src/styles.css', '@tailwind base; @tailwind components;'); - - // Build should succeed and process Tailwind directives - await ng('build', '--configuration=development'); - - // Check for Tailwind output - await expectFileToMatch('dist/test-project/styles.css', /::placeholder/); - await expectFileToMatch('dist/test-project/main.js', /::placeholder/); - await expectToFail(() => - expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'), - ); - await expectToFail(() => - expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'), - ); - - // Remove configuration file - await deleteFile('tailwind.config.js'); - - // Ensure Tailwind is disabled when no configuration file is present - await ng('build', '--configuration=development'); - await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); - await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); - - // Recreate configuration file - await silentExec('npx', 'tailwindcss', 'init'); - - // Uninstall Tailwind - await uninstallPackage('tailwindcss'); - - // Ensure installation warning is present - const { stderr } = await ng('build', '--configuration=development'); - if (!stderr.includes("To enable Tailwind CSS, please install the 'tailwindcss' package.")) { - throw new Error('Expected tailwind installation warning'); - } - - // Tailwind directives should be unprocessed with missing package - await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); - await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); -} diff --git a/tests/legacy-cli/e2e/tests/build/subresource-integrity.ts b/tests/legacy-cli/e2e/tests/build/subresource-integrity.ts deleted file mode 100644 index 6592fd373154..000000000000 --- a/tests/legacy-cli/e2e/tests/build/subresource-integrity.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expectFileToMatch } from '../../utils/fs'; -import { ng } from '../../utils/process'; -import { expectToFail } from '../../utils/utils'; - -const integrityRe = /integrity="\w+-[A-Za-z0-9\/\+=]+"/; - -export default async function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - // WEBPACK4_DISABLED - disabled pending a webpack 4 version - return; - - return ng('build') - .then(() => expectToFail(() => - expectFileToMatch('dist/test-project/index.html', integrityRe))) - .then(() => ng('build', '--sri')) - .then(() => expectFileToMatch('dist/test-project/index.html', integrityRe)); -} diff --git a/tests/legacy-cli/e2e/tests/build/ts-paths.ts b/tests/legacy-cli/e2e/tests/build/ts-paths.ts index 10510a9ee6a6..8af73d148817 100644 --- a/tests/legacy-cli/e2e/tests/build/ts-paths.ts +++ b/tests/legacy-cli/e2e/tests/build/ts-paths.ts @@ -1,21 +1,14 @@ -import { stripIndents } from 'common-tags'; import { appendToFile, createDir, replaceInFile, rimraf, writeMultipleFiles } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateTsConfig } from '../../utils/project'; export default async function () { - await updateTsConfig(json => { + await updateTsConfig((json) => { json['compilerOptions']['baseUrl'] = './src'; json['compilerOptions']['paths'] = { - '@shared': [ - 'app/shared', - ], - '@shared/*': [ - 'app/shared/*', - ], - '@root/*': [ - './*', - ], + '@shared': ['app/shared'], + '@shared/*': ['app/shared/*'], + '@root/*': ['./*'], }; }); @@ -29,14 +22,13 @@ export default async function () { await replaceInFile('src/app/app.module.ts', './app.component', '@root/app/app.component'); await ng('build', '--configuration=development'); - await updateTsConfig(json => { - json['compilerOptions']['paths']['*'] = [ - '*', - 'app/shared/*', - ]; + await updateTsConfig((json) => { + json['compilerOptions']['paths']['*'] = ['*', 'app/shared/*']; }); - await appendToFile('src/app/app.component.ts', stripIndents` + await appendToFile( + 'src/app/app.component.ts', + ` import { meaning } from 'app/shared/meaning'; import { meaning as meaning2 } from '@shared'; import { meaning as meaning3 } from '@shared/meaning'; @@ -50,7 +42,8 @@ export default async function () { console.log(meaning3) console.log(meaning4) console.log(meaning5) - `); + `, + ); await ng('build', '--configuration=development'); diff --git a/tests/legacy-cli/e2e/tests/build/vendor-chunk.ts b/tests/legacy-cli/e2e/tests/build/vendor-chunk.ts index 02c82a86d7e2..2460c13db9bb 100644 --- a/tests/legacy-cli/e2e/tests/build/vendor-chunk.ts +++ b/tests/legacy-cli/e2e/tests/build/vendor-chunk.ts @@ -2,7 +2,6 @@ import { expectFileToExist } from '../../utils/fs'; import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; - export default async function () { await ng('build', '--configuration=development'); await expectFileToExist('dist/test-project/vendor.js'); diff --git a/tests/legacy-cli/e2e/tests/build/worker.ts b/tests/legacy-cli/e2e/tests/build/worker.ts index 69e03193aec7..a8dc02e0b4f9 100644 --- a/tests/legacy-cli/e2e/tests/build/worker.ts +++ b/tests/legacy-cli/e2e/tests/build/worker.ts @@ -1,27 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { join } from 'path'; + +import { readdir } from 'fs/promises'; import { expectFileToExist, expectFileToMatch, replaceInFile, writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; export default async function () { - const workerPath = join('src', 'app', 'app.worker.ts'); - const snippetPath = join('src', 'app', 'app.component.ts'); + const workerPath = 'src/app/app.worker.ts'; + const snippetPath = 'src/app/app.component.ts'; const projectTsConfig = 'tsconfig.json'; const workerTsConfig = 'tsconfig.worker.json'; - // Enable Differential loading to run both size checks - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - await ng('generate', 'web-worker', 'app'); await expectFileToExist(workerPath); await expectFileToExist(projectTsConfig); @@ -29,23 +23,22 @@ export default async function () { await expectFileToMatch(snippetPath, `new Worker(new URL('./app.worker', import.meta.url)`); await ng('build', '--configuration=development'); - await expectFileToExist('dist/test-project/src_app_app_worker_ts-es5.js'); - await expectFileToMatch('dist/test-project/main-es5.js', 'src_app_app_worker_ts'); - await expectFileToExist('dist/test-project/src_app_app_worker_ts-es2017.js'); - await expectFileToMatch('dist/test-project/main-es2017.js', 'src_app_app_worker_ts'); + await expectFileToExist('dist/test-project/src_app_app_worker_ts.js'); + await expectFileToMatch('dist/test-project/main.js', 'src_app_app_worker_ts'); await ng('build', '--output-hashing=none'); - const chunkId = '137'; - await expectFileToExist(`dist/test-project/${chunkId}-es5.js`); - await expectFileToMatch('dist/test-project/main-es5.js', chunkId); - await expectFileToExist(`dist/test-project/${chunkId}-es2017.js`); - await expectFileToMatch('dist/test-project/main-es2017.js', chunkId); + + const chunkId = await getWorkerChunkId(); + await expectFileToExist(`dist/test-project/${chunkId}.js`); + await expectFileToMatch('dist/test-project/main.js', chunkId); // console.warn has to be used because chrome only captures warnings and errors by default // https://github.com/angular/protractor/issues/2207 await replaceInFile('src/app/app.component.ts', 'console.log', 'console.warn'); - await writeFile('e2e/app.e2e-spec.ts', ` + await writeFile( + 'e2e/app.e2e-spec.ts', + ` import { AppPage } from './app.po'; import { browser, logging } from 'protractor'; describe('worker bundle', () => { @@ -57,7 +50,19 @@ export default async function () { expect(logs[0].message).toContain('page got message: worker response to hello'); }); }); - `); + `, + ); await ng('e2e'); } + +async function getWorkerChunkId(): Promise { + const files = await readdir('dist/test-project'); + const fileName = files.find((f) => /^\d{3}\.js$/.test(f)); + + if (!fileName) { + throw new Error('Cannot determine worker chunk Id.'); + } + + return fileName.substring(0, 3); +} diff --git a/tests/legacy-cli/e2e/tests/commands/add/add-material.ts b/tests/legacy-cli/e2e/tests/commands/add/add-material.ts index 39496611be33..724b521519a1 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/add-material.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/add-material.ts @@ -1,24 +1,32 @@ +import { assertIsError } from '../../../utils/utils'; import { expectFileToMatch, rimraf } from '../../../utils/fs'; import { uninstallPackage } from '../../../utils/packages'; import { ng } from '../../../utils/process'; import { isPrereleaseCli } from '../../../utils/project'; - export default async function () { // forcibly remove in case another test doesn't clean itself up await rimraf('node_modules/@angular/material'); - const tag = await isPrereleaseCli() ? '@next' : ''; + const tag = (await isPrereleaseCli()) ? '@next' : ''; try { await ng('add', `@angular/material${tag}`, '--unknown', '--skip-confirmation'); } catch (error) { - if (!(error.message && error.message.includes(`Unknown option: '--unknown'`))) { + assertIsError(error); + if (!error.message.includes(`Unknown option: '--unknown'`)) { throw error; } } - await ng('add', `@angular/material${tag}`, '--theme', 'custom', '--verbose', '--skip-confirmation'); + await ng( + 'add', + `@angular/material${tag}`, + '--theme', + 'custom', + '--verbose', + '--skip-confirmation', + ); await expectFileToMatch('package.json', /@angular\/material/); // Clean up existing cdk package diff --git a/tests/legacy-cli/e2e/tests/commands/add/add-pwa.ts b/tests/legacy-cli/e2e/tests/commands/add/add-pwa.ts index 45672d13a94d..d2115e2cfef2 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/add-pwa.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/add-pwa.ts @@ -1,6 +1,11 @@ import { join } from 'path'; +import { getGlobalVariable } from '../../../utils/env'; import { expectFileToExist, readFile, rimraf } from '../../../utils/fs'; +import { installWorkspacePackages } from '../../../utils/packages'; import { ng } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +const snapshots = require('../../../ng-snapshot/package.json'); export default async function () { // forcibly remove in case another test doesn't clean itself up @@ -9,10 +14,59 @@ export default async function () { await expectFileToExist(join(process.cwd(), 'src/manifest.webmanifest')); // Angular PWA doesn't install as a dependency - const { dependencies, devDependencies } = JSON.parse(await readFile(join(process.cwd(), 'package.json'))); - const hasPWADep = Object.keys({ ...dependencies, ...devDependencies }) - .some(d => d === '@angular/pwa'); + const { dependencies, devDependencies } = JSON.parse( + await readFile(join(process.cwd(), 'package.json')), + ); + const hasPWADep = Object.keys({ ...dependencies, ...devDependencies }).some( + (d) => d === '@angular/pwa', + ); if (hasPWADep) { throw new Error(`Expected 'package.json' not to contain a dependency on '@angular/pwa'.`); } + + const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; + if (isSnapshotBuild) { + let needInstall = false; + await updateJsonFile('package.json', (packageJson) => { + const dependencies = packageJson['dependencies']; + // Iterate over all of the packages to update them to the snapshot version. + for (const [name, version] of Object.entries(snapshots.dependencies)) { + if (name in dependencies && dependencies[name] !== version) { + dependencies[name] = version; + needInstall = true; + } + } + }); + + if (needInstall) { + await installWorkspacePackages(); + } + } + + // It should generate a SW configuration file (`ngsw.json`). + const workspaceJson = JSON.parse(await readFile('angular.json')); + const outputPath = workspaceJson.projects['test-project'].architect.build.options.outputPath; + const ngswPath = join(process.cwd(), outputPath, 'ngsw.json'); + + await ng('build'); + await expectFileToExist(ngswPath); + + // It should correctly generate assetGroups and include at least one URL in each group. + const ngswJson = JSON.parse(await readFile(ngswPath)); + // @ts-ignore + const assetGroups: any[] = ngswJson.assetGroups.map(({ name, urls }) => ({ + name, + urlCount: urls.length, + })); + const emptyAssetGroups = assetGroups.filter(({ urlCount }) => urlCount === 0); + + if (assetGroups.length === 0) { + throw new Error("Expected 'ngsw.json' to contain at least one asset-group."); + } + if (emptyAssetGroups.length > 0) { + throw new Error( + 'Expected all asset-groups to contain at least one URL, but the following groups are empty: ' + + emptyAssetGroups.map(({ name }) => name).join(', '), + ); + } } diff --git a/tests/legacy-cli/e2e/tests/commands/add/add-version.ts b/tests/legacy-cli/e2e/tests/commands/add/add-version.ts index bdbf9c96bd64..02d63eb66e0b 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/add-version.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/add-version.ts @@ -1,7 +1,6 @@ import { expectFileToExist, expectFileToMatch, rimraf } from '../../../utils/fs'; import { ng } from '../../../utils/process'; - export default async function () { await ng('add', '@angular-devkit-tests/ng-add-simple@^1.0.0', '--skip-confirmation'); await expectFileToMatch('package.json', /\/ng-add-simple.*\^1\.0\.0/); diff --git a/tests/legacy-cli/e2e/tests/commands/add/add.ts b/tests/legacy-cli/e2e/tests/commands/add/add.ts index bf3ae5e3d0b5..6d9827013dc4 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/add.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/add.ts @@ -1,7 +1,6 @@ import { expectFileToExist, expectFileToMatch, rimraf } from '../../../utils/fs'; import { ng } from '../../../utils/process'; - export default async function () { await ng('add', '@angular-devkit-tests/ng-add-simple', '--skip-confirmation'); await expectFileToMatch('package.json', /@angular-devkit-tests\/ng-add-simple/); diff --git a/tests/legacy-cli/e2e/tests/commands/add/base.ts b/tests/legacy-cli/e2e/tests/commands/add/base.ts index b22583909076..ba5978633225 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/base.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/base.ts @@ -3,7 +3,6 @@ import { expectFileToExist, rimraf, symlinkFile } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; - export default async function () { await symlinkFile(assetDir('add-collection'), `./node_modules/add-collection`, 'dir'); @@ -13,7 +12,7 @@ export default async function () { await ng('add', 'add-collection', '--name=blah'); await expectFileToExist('blah'); - await expectToFail(() => ng('add', 'add-collection')); // File already exists. + await expectToFail(() => ng('add', 'add-collection')); // File already exists. // Cleanup the package await rimraf('node_modules/add-collection'); diff --git a/tests/legacy-cli/e2e/tests/commands/add/dir.ts b/tests/legacy-cli/e2e/tests/commands/add/dir.ts index 695c9369a43b..f5fadc486b3d 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/dir.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/dir.ts @@ -2,7 +2,6 @@ import { assetDir } from '../../../utils/assets'; import { expectFileToExist } from '../../../utils/fs'; import { ng } from '../../../utils/process'; - export default async function () { await ng('add', assetDir('add-collection'), '--name=blah', '--skip-confirmation'); await expectFileToExist('blah'); diff --git a/tests/legacy-cli/e2e/tests/commands/add/file.ts b/tests/legacy-cli/e2e/tests/commands/add/file.ts index d05abdb9e0f3..6096dac6f666 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/file.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/file.ts @@ -2,7 +2,6 @@ import { assetDir } from '../../../utils/assets'; import { expectFileToExist } from '../../../utils/fs'; import { ng } from '../../../utils/process'; - export default async function () { await ng('add', assetDir('add-collection.tgz'), '--name=blah', '--skip-confirmation'); await expectFileToExist('blah'); diff --git a/tests/legacy-cli/e2e/tests/commands/add/npm-env-vars.ts b/tests/legacy-cli/e2e/tests/commands/add/npm-env-vars.ts new file mode 100644 index 000000000000..d35a7e0846d6 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/add/npm-env-vars.ts @@ -0,0 +1,26 @@ +import { expectFileNotToExist, expectFileToExist } from '../../../utils/fs'; +import { git, ng } from '../../../utils/process'; +import { + createNpmConfigForAuthentication, + setNpmEnvVarsForAuthentication, +} from '../../../utils/registry'; + +export default async function () { + // Yarn also supports NPM_CONFIG env variables. + // https://classic.yarnpkg.com/en/docs/envvars/ + + const command = ['add', '@angular/pwa', '--skip-confirmation']; + + // Environment variables only + await expectFileNotToExist('src/manifest.webmanifest'); + setNpmEnvVarsForAuthentication(); + await ng(...command); + await expectFileToExist('src/manifest.webmanifest'); + await git('clean', '-dxf'); + + // Mix of config file and env vars works + await expectFileNotToExist('src/manifest.webmanifest'); + await createNpmConfigForAuthentication(false, true); + await ng(...command); + await expectFileToExist('src/manifest.webmanifest'); +} diff --git a/tests/legacy-cli/e2e/tests/commands/add/peer.ts b/tests/legacy-cli/e2e/tests/commands/add/peer.ts index 2f5147df0b03..ff45127ff182 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/peer.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/peer.ts @@ -4,17 +4,25 @@ import { ng } from '../../../utils/process'; const warning = 'Adding the package may not succeed.'; export default async function () { - const { stderr: bad } = await ng('add', assetDir('add-collection-peer-bad'), '--skip-confirmation'); + const { stderr: bad } = await ng( + 'add', + assetDir('add-collection-peer-bad'), + '--skip-confirmation', + ); if (!bad.includes(warning)) { throw new Error('peer warning not shown on bad package'); } - const { stderr: base } = await ng('add', assetDir('add-collection'), '--skip-confirmation'); + const { stderr: base } = await ng('add', assetDir('add-collection'), '--skip-confirmation'); if (base.includes(warning)) { throw new Error('peer warning shown on base package'); } - const { stderr: good } = await ng('add', assetDir('add-collection-peer-good'), '--skip-confirmation'); + const { stderr: good } = await ng( + 'add', + assetDir('add-collection-peer-good'), + '--skip-confirmation', + ); if (good.includes(warning)) { throw new Error('peer warning shown on good package'); } diff --git a/tests/legacy-cli/e2e/tests/commands/add/registry-option.ts b/tests/legacy-cli/e2e/tests/commands/add/registry-option.ts index 8a9e31dcb4d6..13d54e6c2f50 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/registry-option.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/registry-option.ts @@ -1,25 +1,16 @@ import { getGlobalVariable } from '../../../utils/env'; -import { expectFileToExist, writeMultipleFiles } from '../../../utils/fs'; +import { expectFileToExist } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; export default async function () { const testRegistry = getGlobalVariable('package-registry'); - // Setup an invalid registry - await writeMultipleFiles({ - '.npmrc': 'registry=http://127.0.0.1:9999', - }); - // The environment variable has priority over the .npmrc - const originalRegistryVariable = process.env['NPM_CONFIG_REGISTRY']; - process.env['NPM_CONFIG_REGISTRY'] = undefined; + // Set an invalid registry + process.env['NPM_CONFIG_REGISTRY'] = 'http://127.0.0.1:9999/'; - try { - await expectToFail(() => ng('add', '@angular/pwa', '--skip-confirmation')); + await expectToFail(() => ng('add', '@angular/pwa', '--skip-confirmation')); - await ng('add', `--registry=${testRegistry}`, '@angular/pwa', '--skip-confirmation'); - await expectFileToExist('src/manifest.webmanifest'); - } finally { - process.env['NPM_CONFIG_REGISTRY'] = originalRegistryVariable; - } + await ng('add', `--registry=${testRegistry}`, '@angular/pwa', '--skip-confirmation'); + await expectFileToExist('src/manifest.webmanifest'); } diff --git a/tests/legacy-cli/e2e/tests/commands/add/secure-registry.ts b/tests/legacy-cli/e2e/tests/commands/add/secure-registry.ts new file mode 100644 index 000000000000..69da8d5845b9 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/add/secure-registry.ts @@ -0,0 +1,32 @@ +import { expectFileNotToExist, expectFileToExist } from '../../../utils/fs'; +import { git, ng } from '../../../utils/process'; +import { createNpmConfigForAuthentication } from '../../../utils/registry'; +import { expectToFail } from '../../../utils/utils'; + +export default async function () { + // The environment variable has priority over the .npmrc + delete process.env['NPM_CONFIG_REGISTRY']; + + const command = ['add', '@angular/pwa', '--skip-confirmation']; + await expectFileNotToExist('src/manifest.webmanifest'); + + // Works with unscoped registry authentication details + await createNpmConfigForAuthentication(false); + await ng(...command); + await expectFileToExist('src/manifest.webmanifest'); + await git('clean', '-dxf'); + + // Works with scoped registry authentication details + await expectFileNotToExist('src/manifest.webmanifest'); + + await createNpmConfigForAuthentication(true); + await ng(...command); + await expectFileToExist('src/manifest.webmanifest'); + + // Invalid authentication token + await createNpmConfigForAuthentication(false, true); + await expectToFail(() => ng(...command)); + + await createNpmConfigForAuthentication(true, true); + await expectToFail(() => ng(...command)); +} diff --git a/tests/legacy-cli/e2e/tests/commands/add/version-specifier.ts b/tests/legacy-cli/e2e/tests/commands/add/version-specifier.ts index f7c8cdd9f820..426f8d5b61e5 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/version-specifier.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/version-specifier.ts @@ -1,16 +1,23 @@ +import { appendFile } from 'fs/promises'; import { expectFileToMatch, rimraf } from '../../../utils/fs'; -import { uninstallPackage } from '../../../utils/packages'; +import { getActivePackageManager, uninstallPackage } from '../../../utils/packages'; import { ng } from '../../../utils/process'; import { isPrereleaseCli } from '../../../utils/project'; - export default async function () { // forcibly remove in case another test doesn't clean itself up. await rimraf('node_modules/@angular/localize'); - const tag = await isPrereleaseCli() ? '@next' : ''; + // If using npm, enable the force option to allow testing the output behavior of the + // `ng add` command itself and not the behavior of npm which may otherwise fail depending + // on the npm version in use and the version specifier supplied in each test. + if (getActivePackageManager() === 'npm') { + appendFile('.npmrc', '\nforce=true\n'); + } + + const tag = (await isPrereleaseCli()) ? '@next' : ''; - await ng('add', `@angular/localize${tag}`, '--skip-confirmation'); + await ng('add', `@angular/localize${tag}`, '--skip-confirmation'); await expectFileToMatch('package.json', /@angular\/localize/); const output1 = await ng('add', '@angular/localize', '--skip-confirmation'); @@ -23,12 +30,13 @@ export default async function () { throw new Error('Installation should not have been skipped'); } - const output3 = await ng('add', '@angular/localize@10.0.0', '--skip-confirmation'); + // v12.2.0 has a package.json engine field that supports Node.js v16+ + const output3 = await ng('add', '@angular/localize@12.2.0', '--skip-confirmation'); if (output3.stdout.includes('Skipping installation: Package already installed')) { throw new Error('Installation should not have been skipped'); } - const output4 = await ng('add', '@angular/localize@10', '--skip-confirmation'); + const output4 = await ng('add', '@angular/localize@12', '--skip-confirmation'); if (!output4.stdout.includes('Skipping installation: Package already installed')) { throw new Error('Installation was not skipped'); } diff --git a/tests/legacy-cli/e2e/tests/commands/add/yarn-env-vars.ts b/tests/legacy-cli/e2e/tests/commands/add/yarn-env-vars.ts new file mode 100644 index 000000000000..67ffdbdc9b09 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/add/yarn-env-vars.ts @@ -0,0 +1,29 @@ +import { expectFileNotToExist, expectFileToExist } from '../../../utils/fs'; +import { getActivePackageManager } from '../../../utils/packages'; +import { git, ng } from '../../../utils/process'; +import { + createNpmConfigForAuthentication, + setNpmEnvVarsForAuthentication, +} from '../../../utils/registry'; + +export default async function () { + // Yarn specific test that tests YARN_ env variables. + // https://classic.yarnpkg.com/en/docs/envvars/ + if (getActivePackageManager() !== 'yarn') { + return; + } + const command = ['add', '@angular/pwa', '--skip-confirmation']; + + // Environment variables only + await expectFileNotToExist('src/manifest.webmanifest'); + setNpmEnvVarsForAuthentication(false, true); + await ng(...command); + await expectFileToExist('src/manifest.webmanifest'); + await git('clean', '-dxf'); + + // Mix of config file and env vars works + await expectFileNotToExist('src/manifest.webmanifest'); + await createNpmConfigForAuthentication(false, true); + await ng(...command); + await expectFileToExist('src/manifest.webmanifest'); +} diff --git a/tests/legacy-cli/e2e/tests/commands/additional-properties.ts b/tests/legacy-cli/e2e/tests/commands/additional-properties.ts index b9a477c7cff6..a53006853fca 100644 --- a/tests/legacy-cli/e2e/tests/commands/additional-properties.ts +++ b/tests/legacy-cli/e2e/tests/commands/additional-properties.ts @@ -2,16 +2,19 @@ import { createDir, rimraf, writeMultipleFiles } from '../../utils/fs'; import { execAndWaitForOutputToMatch } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -export default async function() { +export default async function () { await createDir('example-builder'); await writeMultipleFiles({ 'example-builder/package.json': '{ "builders": "./builders.json" }', - 'example-builder/schema.json': '{ "$schema": "http://json-schema.org/draft-07/schema", "type": "object", "additionalProperties": true }', - 'example-builder/builders.json': '{ "$schema": "@angular-devkit/architect/src/builders-schema.json", "builders": { "example": { "implementation": "./example", "schema": "./schema.json" } } }', - 'example-builder/example.js': 'module.exports.default = require("@angular-devkit/architect").createBuilder((options) => { console.log(options); return { success: true }; });', + 'example-builder/schema.json': + '{ "$schema": "http://json-schema.org/draft-07/schema", "type": "object", "additionalProperties": true }', + 'example-builder/builders.json': + '{ "$schema": "@angular-devkit/architect/src/builders-schema.json", "builders": { "example": { "implementation": "./example", "schema": "./schema.json" } } }', + 'example-builder/example.js': + 'module.exports.default = require("@angular-devkit/architect").createBuilder((options) => { console.log(options); return { success: true }; });', }); - await updateJsonFile('angular.json', json => { + await updateJsonFile('angular.json', (json) => { const appArchitect = json.projects['test-project'].architect; appArchitect.example = { builder: './example-builder:example', @@ -21,7 +24,7 @@ export default async function() { await execAndWaitForOutputToMatch( 'ng', ['run', 'test-project:example', '--additional', 'property'], - /'{ '--': \[ '--additional', 'property' \] }'/, + /Unknown argument: additional/, ); await rimraf('example-builder'); diff --git a/tests/legacy-cli/e2e/tests/commands/analytics/analytics-enable-disable.ts b/tests/legacy-cli/e2e/tests/commands/analytics/analytics-enable-disable.ts new file mode 100644 index 000000000000..94bd8f95edb2 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/analytics/analytics-enable-disable.ts @@ -0,0 +1,11 @@ +import assert from 'node:assert'; +import { readFile } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; + +export default async function () { + await ng('analytics', 'enable'); + assert.ok(JSON.parse(await readFile('angular.json')).cli.analytics); + + await ng('analytics', 'disable'); + assert.strictEqual(JSON.parse(await readFile('angular.json')).cli.analytics, false); +} diff --git a/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts b/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts new file mode 100644 index 000000000000..68d15db2358e --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts @@ -0,0 +1,27 @@ +import { execAndWaitForOutputToMatch } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +export default async function () { + // Should be disabled by default. + await configureTest(undefined /** analytics */); + await execAndWaitForOutputToMatch('ng', ['analytics', 'info'], /Effective status: disabled/, { + NG_FORCE_TTY: '0', // Disable prompts + }); + + await configureTest('1dba0835-38a3-4957-bf34-9974e2df0df3' /** analytics */); + await execAndWaitForOutputToMatch('ng', ['analytics', 'info'], /Effective status: enabled/, { + NG_FORCE_TTY: '0', // Disable prompts + }); + + await configureTest(false /** analytics */); + await execAndWaitForOutputToMatch('ng', ['analytics', 'info'], /Effective status: disabled/, { + NG_FORCE_TTY: '0', // Disable prompts + }); +} + +async function configureTest(analytics: false | string | undefined): Promise { + await updateJsonFile('angular.json', (config) => { + config.cli ??= {}; + config.cli.analytics = analytics; + }); +} diff --git a/tests/legacy-cli/e2e/tests/commands/analytics/ask-analytics-command.ts b/tests/legacy-cli/e2e/tests/commands/analytics/ask-analytics-command.ts new file mode 100644 index 000000000000..1bfff89b448c --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/analytics/ask-analytics-command.ts @@ -0,0 +1,53 @@ +import { execWithEnv } from '../../../utils/process'; +import { mockHome } from '../../../utils/utils'; + +const ANALYTICS_PROMPT = /Would you like to share pseudonymous usage data/; + +export default async function () { + // CLI should prompt for analytics permissions. + await mockHome(async () => { + const { stdout } = await execWithEnv( + 'ng', + ['version'], + { + ...process.env, + NG_FORCE_TTY: '1', + NG_FORCE_AUTOCOMPLETE: 'false', + }, + 'y' /* stdin */, + ); + + if (!ANALYTICS_PROMPT.test(stdout)) { + throw new Error('CLI did not prompt for analytics permission.'); + } + }); + + // CLI should skip analytics prompt with `NG_CLI_ANALYTICS=false`. + await mockHome(async () => { + const { stdout } = await execWithEnv('ng', ['version'], { + ...process.env, + NG_FORCE_TTY: '1', + NG_CLI_ANALYTICS: 'false', + NG_FORCE_AUTOCOMPLETE: 'false', + }); + + if (ANALYTICS_PROMPT.test(stdout)) { + throw new Error('CLI prompted for analytics permission when it should be forced off.'); + } + }); + + // CLI should skip analytics prompt during `ng update`. + await mockHome(async () => { + const { stdout } = await execWithEnv('ng', ['update', '--help'], { + ...process.env, + NG_FORCE_TTY: '1', + NG_FORCE_AUTOCOMPLETE: 'false', + }); + + if (ANALYTICS_PROMPT.test(stdout)) { + throw new Error( + 'CLI prompted for analytics permission during an update where it should not' + ' have.', + ); + } + }); +} diff --git a/tests/legacy-cli/e2e/tests/commands/build/build-outdir.ts b/tests/legacy-cli/e2e/tests/commands/build/build-outdir.ts deleted file mode 100644 index 6cfc60afa39a..000000000000 --- a/tests/legacy-cli/e2e/tests/commands/build/build-outdir.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {ng} from '../../../utils/process'; -import {updateJsonFile} from '../../../utils/project'; -import {expectToFail} from '../../../utils/utils'; - -export default function() { - // TODO(architect): This isn't working correctly in devkit/build-angular, due to module resolution. - return; - - return Promise.resolve() - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.outputPath = './'; - })) - .then(() => expectToFail(() => ng('build', '--configuration=development'))) - .then(() => expectToFail(() => ng('serve'))); -} diff --git a/tests/legacy-cli/e2e/tests/commands/builder-not-found.ts b/tests/legacy-cli/e2e/tests/commands/builder-not-found.ts new file mode 100644 index 000000000000..934ff2c16666 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/builder-not-found.ts @@ -0,0 +1,45 @@ +import { moveFile } from '../../utils/fs'; +import { getActivePackageManager, installPackage, uninstallPackage } from '../../utils/packages'; +import { execAndWaitForOutputToMatch, ng } from '../../utils/process'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + try { + await uninstallPackage('@angular-devkit/build-angular'); + + await expectToFail(() => ng('build')); + await execAndWaitForOutputToMatch( + 'ng', + ['build'], + /Could not find the '@angular-devkit\/build-angular:browser' builder's node package\./, + ); + await expectToFail(() => + execAndWaitForOutputToMatch( + 'ng', + ['build'], + new RegExp( + `Node packages may not be installed\. Try installing with '${getActivePackageManager()} install'\.`, + ), + ), + ); + + await moveFile('node_modules', 'temp_node_modules'); + + await expectToFail(() => ng('build')); + await execAndWaitForOutputToMatch( + 'ng', + ['build'], + /Could not find the '@angular-devkit\/build-angular:browser' builder's node package\./, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['build'], + new RegExp( + `Node packages may not be installed\. Try installing with '${getActivePackageManager()} install'\.`, + ), + ); + } finally { + await moveFile('temp_node_modules', 'node_modules'); + await installPackage('@angular-devkit/build-angular'); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts b/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts new file mode 100644 index 000000000000..77da67a09a4b --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts @@ -0,0 +1,21 @@ +import { join } from 'path'; +import { expectFileToExist } from '../../utils/fs'; +import { ng } from '../../utils/process'; + +export default async function () { + await ng('generate', 'app', 'second-app', '--skip-install'); + await ng('generate', 'app', 'third-app', '--skip-install'); + const startCwd = process.cwd(); + + try { + // When no project is provided it should favor the project that is located in the current working directory. + process.chdir(join(startCwd, 'projects/second-app')); + await ng('build', '--configuration=development'); + + process.chdir(startCwd); + await expectFileToExist('dist/second-app'); + } finally { + // restore path + process.chdir(startCwd); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/cache/cache-clean.ts b/tests/legacy-cli/e2e/tests/commands/cache/cache-clean.ts new file mode 100644 index 000000000000..004c5a069dfc --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/cache/cache-clean.ts @@ -0,0 +1,11 @@ +import { createDir, expectFileNotToExist, expectFileToExist } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; + +export default async function () { + const cachePath = '.angular/cache'; + await createDir(cachePath); + await expectFileToExist(cachePath); + + await ng('cache', 'clean'); + await expectFileNotToExist(cachePath); +} diff --git a/tests/legacy-cli/e2e/tests/commands/cache/cache-enable-disable.ts b/tests/legacy-cli/e2e/tests/commands/cache/cache-enable-disable.ts new file mode 100644 index 000000000000..bb81dd2d80b9 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/cache/cache-enable-disable.ts @@ -0,0 +1,14 @@ +import { readFile } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; + +export default async function () { + await ng('cache', 'enable'); + if (JSON.parse(await readFile('angular.json')).cli.cache.enabled !== true) { + throw new Error(`Expected 'cli.cache.enable' to be true.`); + } + + await ng('cache', 'disable'); + if (JSON.parse(await readFile('angular.json')).cli.cache.enabled !== false) { + throw new Error(`Expected 'cli.cache.enable' to be false.`); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/cache/cache-info.ts b/tests/legacy-cli/e2e/tests/commands/cache/cache-info.ts new file mode 100644 index 000000000000..46ae15aef796 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/cache/cache-info.ts @@ -0,0 +1,81 @@ +import { execAndWaitForOutputToMatch } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; + +export default async function () { + const originalCIValue = process.env['CI']; + + try { + // Should be enabled by default for local builds. + await configureTest('0' /** envCI */); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: enabled/, + ); + + // Should be disabled by default for CI builds. + await configureTest('1' /** envCI */, { enabled: true }); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: disabled/, + ); + + // Should be enabled by when environment is local and env is not CI. + await configureTest('0' /** envCI */, { environment: 'local' }); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: enabled/, + ); + + // Should be disabled by when environment is local and env is CI. + await configureTest('1' /** envCI */, { environment: 'local' }); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: disabled/, + ); + + // Effective status should be enabled when 'environment' is set to 'all' or 'ci'. + await configureTest('1' /** envCI */, { environment: 'all' }); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: enabled/, + ); + + // Effective status should be enabled when 'environment' is set to 'ci' and run is in ci + await configureTest('1' /** envCI */, { environment: 'ci' }); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: enabled/, + ); + + // Effective status should be disabled when 'enabled' is set to false + await configureTest('1' /** envCI */, { environment: 'all', enabled: false }); + await execAndWaitForOutputToMatch( + 'ng', + ['cache', 'info'], + /Effective status on current machine: disabled/, + ); + } finally { + process.env['CI'] = originalCIValue; + } +} + +async function configureTest( + envCI: '1' | '0', + cacheOptions?: { + environment?: 'ci' | 'local' | 'all'; + enabled?: boolean; + }, +): Promise { + process.env['CI'] = envCI; + + await updateJsonFile('angular.json', (config) => { + config.cli ??= {}; + config.cli.cache = cacheOptions; + }); +} diff --git a/tests/legacy-cli/e2e/tests/commands/completion/completion-prompt.ts b/tests/legacy-cli/e2e/tests/commands/completion/completion-prompt.ts new file mode 100644 index 000000000000..1e233d891e32 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/completion/completion-prompt.ts @@ -0,0 +1,449 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { env } from 'process'; +import { getGlobalVariable } from '../../../utils/env'; +import { mockHome } from '../../../utils/utils'; + +import { + execAndCaptureError, + execAndWaitForOutputToMatch, + execWithEnv, + silentNpm, +} from '../../../utils/process'; + +const AUTOCOMPLETION_PROMPT = /Would you like to enable autocompletion\?/; +const DEFAULT_ENV = Object.freeze({ + ...env, + // Shell should be mocked for each test that cares about it. + SHELL: '/bin/bash', + // Even if the actual test process is run on CI, we're testing user flows which aren't on CI. + CI: undefined, + // Tests run on CI technically don't have a TTY, but the autocompletion prompt requires it, so we + // force a TTY by default. + NG_FORCE_TTY: '1', + // Analytics wants to prompt for a first command as well, but we don't care about that here. + NG_CLI_ANALYTICS: 'false', +}); + +const testRegistry = getGlobalVariable('package-registry'); + +export default async function () { + // Windows Cmd and Powershell do not support autocompletion. Run a different set of tests to + // confirm autocompletion skips the prompt appropriately. + if (process.platform === 'win32') { + await windowsTests(); + return; + } + + // Sets up autocompletion after user accepts a prompt from any command. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, `# Other content...`); + + const { stdout } = await execWithEnv( + 'ng', + ['version'], + { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }, + 'y' /* stdin: accept prompt */, + ); + + if (!AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error('CLI execution did not prompt for autocompletion setup when it should have.'); + } + + const bashrcContents = await fs.readFile(bashrc, 'utf-8'); + if (!bashrcContents.includes('source <(ng completion script)')) { + throw new Error( + 'Autocompletion was *not* added to `~/.bashrc` after accepting the setup prompt.', + ); + } + + if (!stdout.includes('Appended `source <(ng completion script)`')) { + throw new Error('CLI did not print that it successfully set up autocompletion.'); + } + }); + + // Does nothing if the user rejects the autocompletion prompt. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, `# Other content...`); + + const { stdout } = await execWithEnv( + 'ng', + ['version'], + { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }, + 'n' /* stdin: reject prompt */, + ); + + if (!AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error('CLI execution did not prompt for autocompletion setup when it should have.'); + } + + const bashrcContents = await fs.readFile(bashrc, 'utf-8'); + if (bashrcContents.includes('ng completion')) { + throw new Error( + 'Autocompletion was incorrectly added to `~/.bashrc` after refusing the setup prompt.', + ); + } + + if (stdout.includes('Appended `source <(ng completion script)`')) { + throw new Error( + "CLI printed that it successfully set up autocompletion when it actually didn't.", + ); + } + + if (!stdout.includes("Ok, you won't be prompted again.")) { + throw new Error('CLI did not inform the user they will not be prompted again.'); + } + }); + + // Does *not* prompt if the user already accepted (even if they delete the completion config). + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, '# Other commands...'); + + const { stdout: stdout1 } = await execWithEnv( + 'ng', + ['version'], + { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }, + 'y' /* stdin: accept prompt */, + ); + + if (!AUTOCOMPLETION_PROMPT.test(stdout1)) { + throw new Error('First execution did not prompt for autocompletion setup.'); + } + + const bashrcContents1 = await fs.readFile(bashrc, 'utf-8'); + if (!bashrcContents1.includes('source <(ng completion script)')) { + throw new Error( + '`~/.bashrc` file was not updated after the user accepted the autocompletion' + + ` prompt. Contents:\n${bashrcContents1}`, + ); + } + + // User modifies their configuration and removes `ng completion`. + await fs.writeFile(bashrc, '# Some new commands...'); + + const { stdout: stdout2 } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout2)) { + throw new Error( + 'Subsequent execution after rejecting autocompletion setup prompted again' + + ' when it should not have.', + ); + } + + const bashrcContents2 = await fs.readFile(bashrc, 'utf-8'); + if (bashrcContents2 !== '# Some new commands...') { + throw new Error( + '`~/.bashrc` file was incorrectly modified when using a modified `~/.bashrc`' + + ` after previously accepting the autocompletion prompt. Contents:\n${bashrcContents2}`, + ); + } + }); + + // Does *not* prompt if the user already rejected. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, '# Other commands...'); + + const { stdout: stdout1 } = await execWithEnv( + 'ng', + ['version'], + { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }, + 'n' /* stdin: reject prompt */, + ); + + if (!AUTOCOMPLETION_PROMPT.test(stdout1)) { + throw new Error('First execution did not prompt for autocompletion setup.'); + } + + const { stdout: stdout2 } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout2)) { + throw new Error( + 'Subsequent execution after rejecting autocompletion setup prompted again' + + ' when it should not have.', + ); + } + + const bashrcContents = await fs.readFile(bashrc, 'utf-8'); + if (bashrcContents !== '# Other commands...') { + throw new Error( + '`~/.bashrc` file was incorrectly modified when the user never accepted the' + + ` autocompletion prompt. Contents:\n${bashrcContents}`, + ); + } + }); + + // Prompts user again on subsequent execution after accepting prompt but failing to setup. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, '# Other commands...'); + + // Make `~/.bashrc` readonly. This is enough for the CLI to verify that the file exists and + // `ng completion` is not in it, but will fail when actually trying to modify the file. + await fs.chmod(bashrc, 0o444); + + const err = await execAndCaptureError( + 'ng', + ['version'], + { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }, + 'y' /* stdin: accept prompt */, + ); + + if (!err.message.includes('Failed to append autocompletion setup')) { + throw new Error( + `Failed first execution did not print the expected error message. Actual:\n${err.message}`, + ); + } + + // User corrects file permissions between executions. + await fs.chmod(bashrc, 0o777); + + const { stdout: stdout2 } = await execWithEnv( + 'ng', + ['version'], + { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }, + 'y' /* stdin: accept prompt */, + ); + + if (!AUTOCOMPLETION_PROMPT.test(stdout2)) { + throw new Error( + 'Subsequent execution after failed autocompletion setup did not prompt again when it should' + + ' have.', + ); + } + + const bashrcContents = await fs.readFile(bashrc, 'utf-8'); + if (!bashrcContents.includes('ng completion script')) { + throw new Error( + '`~/.bashrc` file does not include `ng completion` after the user never accepted the' + + ` autocompletion prompt a second time. Contents:\n${bashrcContents}`, + ); + } + }); + + // Does *not* prompt for `ng update` commands. + await mockHome(async (home) => { + // Use `ng update --help` so it's actually a no-op and we don't need to setup a project. + const { stdout } = await execWithEnv('ng', ['update', '--help'], { + ...DEFAULT_ENV, + HOME: home, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error('`ng update` command incorrectly prompted for autocompletion setup.'); + } + }); + + // Does *not* prompt for `ng completion` commands. + await mockHome(async (home) => { + const { stdout } = await execWithEnv('ng', ['completion'], { + ...DEFAULT_ENV, + HOME: home, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error('`ng completion` command incorrectly prompted for autocompletion setup.'); + } + }); + + // Does *not* prompt user for CI executions. + { + const { stdout } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + CI: 'true', + NG_FORCE_TTY: undefined, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error('CI execution prompted for autocompletion setup but should not have.'); + } + } + + // Does *not* prompt user for non-TTY executions. + { + const { stdout } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + NG_FORCE_TTY: 'false', + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error('Non-TTY execution prompted for autocompletion setup but should not have.'); + } + } + + // Does *not* prompt user for executions without a `$HOME`. + { + const { stdout } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + HOME: undefined, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error( + 'Execution without a `$HOME` value prompted for autocompletion setup but' + + ' should not have.', + ); + } + } + + // Does *not* prompt user for executions without a `$SHELL`. + { + const { stdout } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + SHELL: undefined, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error( + 'Execution without a `$SHELL` value prompted for autocompletion setup but' + + ' should not have.', + ); + } + } + + // Does *not* prompt user for executions from unknown shells. + { + const { stdout } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + SHELL: '/usr/bin/unknown', + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error( + 'Execution with an unknown `$SHELL` value prompted for autocompletion setup' + + ' but should not have.', + ); + } + } + + // Does *not* prompt user when an RC file already uses `ng completion`. + await mockHome(async (home) => { + await fs.writeFile( + path.join(home, '.bashrc'), + ` +# Some stuff... + +source <(ng completion script) + +# Some other stuff... + `.trim(), + ); + + const { stdout } = await execWithEnv('ng', ['version'], { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error( + "Execution with an existing `ng completion` line in the user's RC file" + + ' prompted for autocompletion setup but should not have.', + ); + } + }); + + // Prompts when a global CLI install is present on the system. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, `# Other content...`); + + await execAndWaitForOutputToMatch('ng', ['version'], AUTOCOMPLETION_PROMPT, { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + }); + }); + + // Does *not* prompt when a global CLI install is missing from the system. + await mockHome(async (home) => { + try { + // Temporarily uninstall the global CLI binary from the system. + await silentNpm(['uninstall', '--global', '@angular/cli', `--registry=${testRegistry}`]); + + // Setup a fake project directory with a local install of the CLI. + const projectDir = path.join(home, 'project'); + await fs.mkdir(projectDir); + await silentNpm(['init', '-y', `--registry=${testRegistry}`], { cwd: projectDir }); + await silentNpm(['install', '@angular/cli', `--registry=${testRegistry}`], { + cwd: projectDir, + }); + + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, `# Other content...`); + + const localCliDir = path.join(projectDir, 'node_modules', '.bin'); + const localCliBinary = path.join(localCliDir, 'ng'); + const pathDirs = process.env['PATH']!.split(':'); + const pathEnvVar = [...pathDirs, localCliDir].join(':'); + const { stdout } = await execWithEnv(localCliBinary, ['version'], { + ...DEFAULT_ENV, + SHELL: '/bin/bash', + HOME: home, + PATH: pathEnvVar, + }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error( + 'Execution without a global CLI install prompted for autocompletion setup but should' + + ' not have.', + ); + } + } finally { + // Reinstall global CLI for remainder of the tests. + await silentNpm(['install', '--global', '@angular/cli', `--registry=${testRegistry}`]); + } + }); +} + +async function windowsTests(): Promise { + // Should *not* prompt on Windows, autocompletion isn't supported. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, `# Other content...`); + + const { stdout } = await execWithEnv('ng', ['version'], { ...env }); + + if (AUTOCOMPLETION_PROMPT.test(stdout)) { + throw new Error( + 'Execution prompted to set up autocompletion on Windows despite not actually being' + + ' supported.', + ); + } + }); +} diff --git a/tests/legacy-cli/e2e/tests/commands/completion/completion-script.ts b/tests/legacy-cli/e2e/tests/commands/completion/completion-script.ts new file mode 100644 index 000000000000..1b940056d30e --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/completion/completion-script.ts @@ -0,0 +1,70 @@ +import { exec, execAndWaitForOutputToMatch } from '../../../utils/process'; + +export default async function () { + // ng build + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'b', ''], + /test-project/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'build', ''], + /test-project/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'build', '--a'], + /--aot/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'build', '--configuration'], + /production/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'b', '--configuration'], + /production/, + ); + + // ng run + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'run', ''], + /test-project\\:build\\:development/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'run', ''], + /test-project\\:build/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'run', ''], + /test-project\\:test/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'run', 'test-project:build'], + /test-project\\:build\\:development/, + ); + await execAndWaitForOutputToMatch( + 'ng', + ['--get-yargs-completions', 'ng', 'run', 'test-project:'], + /test-project\\:test/, + ); + + const { stdout: noServeStdout } = await exec( + 'ng', + '--get-yargs-completions', + 'ng', + 'run', + 'test-project:build', + ); + if (noServeStdout.includes(':serve')) { + throw new Error( + `':serve' should not have been listed as a completion option.\nSTDOUT:\n${noServeStdout}`, + ); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/completion/completion.ts b/tests/legacy-cli/e2e/tests/commands/completion/completion.ts new file mode 100644 index 000000000000..496620b5cf52 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/completion/completion.ts @@ -0,0 +1,383 @@ +import { promises as fs } from 'fs'; +import * as path from 'path'; +import { getGlobalVariable } from '../../../utils/env'; +import { mockHome } from '../../../utils/utils'; +import { + execAndCaptureError, + execAndWaitForOutputToMatch, + execWithEnv, + silentNpm, +} from '../../../utils/process'; + +const testRegistry = getGlobalVariable('package-registry'); + +export default async function () { + // Windows Cmd and Powershell do not support autocompletion. Run a different set of tests to + // confirm autocompletion fails gracefully. + if (process.platform === 'win32') { + await windowsTests(); + + return; + } + + // Generates new `.bashrc` file. + await mockHome(async (home) => { + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/bin/bash', + }, + ); + + const rcContents = await fs.readFile(path.join(home, '.bashrc'), 'utf-8'); + const expected = ` +# Load Angular CLI autocompletion. +source <(ng completion script) + `.trim(); + if (!rcContents.includes(expected)) { + throw new Error(`~/.bashrc does not contain autocompletion script. Contents:\n${rcContents}`); + } + }); + + // Generates new `.zshrc` file. + await mockHome(async (home) => { + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }, + ); + + const rcContents = await fs.readFile(path.join(home, '.zshrc'), 'utf-8'); + const expected = ` +# Load Angular CLI autocompletion. +source <(ng completion script) + `.trim(); + if (!rcContents.includes(expected)) { + throw new Error(`~/.zshrc does not contain autocompletion script. Contents:\n${rcContents}`); + } + }); + + // Appends to existing `.bashrc` file. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, '# Other commands...'); + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/bin/bash', + }, + ); + + const rcContents = await fs.readFile(bashrc, 'utf-8'); + const expected = `# Other commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (rcContents !== expected) { + throw new Error(`~/.bashrc does not match expectation. Contents:\n${rcContents}`); + } + }); + + // Appends to existing `.bash_profile` file. + await mockHome(async (home) => { + const bashProfile = path.join(home, '.bash_profile'); + await fs.writeFile(bashProfile, '# Other commands...'); + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/bin/bash', + }, + ); + + const rcContents = await fs.readFile(bashProfile, 'utf-8'); + const expected = `# Other commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (rcContents !== expected) { + throw new Error(`~/.bash_profile does not match expectation. Contents:\n${rcContents}`); + } + }); + + // Appends to existing `.profile` file (using Bash). + await mockHome(async (home) => { + const profile = path.join(home, '.profile'); + await fs.writeFile(profile, '# Other commands...'); + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/bin/bash', + }, + ); + + const rcContents = await fs.readFile(profile, 'utf-8'); + const expected = `# Other commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (rcContents !== expected) { + throw new Error(`~/.profile does not match expectation. Contents:\n${rcContents}`); + } + }); + + // Bash shell prefers `.bashrc`. + await mockHome(async (home) => { + const bashrc = path.join(home, '.bashrc'); + await fs.writeFile(bashrc, '# `.bashrc` commands...'); + const bashProfile = path.join(home, '.bash_profile'); + await fs.writeFile(bashProfile, '# `.bash_profile` commands...'); + const profile = path.join(home, '.profile'); + await fs.writeFile(profile, '# `.profile` commands...'); + + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/bin/bash', + }, + ); + + const bashrcContents = await fs.readFile(bashrc, 'utf-8'); + const bashrcExpected = `# \`.bashrc\` commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (bashrcContents !== bashrcExpected) { + throw new Error(`~/.bashrc does not match expectation. Contents:\n${bashrcContents}`); + } + const bashProfileContents = await fs.readFile(bashProfile, 'utf-8'); + if (bashProfileContents !== '# `.bash_profile` commands...') { + throw new Error( + `~/.bash_profile does not match expectation. Contents:\n${bashProfileContents}`, + ); + } + const profileContents = await fs.readFile(profile, 'utf-8'); + if (profileContents !== '# `.profile` commands...') { + throw new Error(`~/.profile does not match expectation. Contents:\n${profileContents}`); + } + }); + + // Appends to existing `.zshrc` file. + await mockHome(async (home) => { + const zshrc = path.join(home, '.zshrc'); + await fs.writeFile(zshrc, '# Other commands...'); + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }, + ); + + const rcContents = await fs.readFile(zshrc, 'utf-8'); + const expected = `# Other commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (rcContents !== expected) { + throw new Error(`~/.zshrc does not match expectation. Contents:\n${rcContents}`); + } + }); + + // Appends to existing `.zsh_profile` file. + await mockHome(async (home) => { + const zshProfile = path.join(home, '.zsh_profile'); + await fs.writeFile(zshProfile, '# Other commands...'); + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }, + ); + + const rcContents = await fs.readFile(zshProfile, 'utf-8'); + const expected = `# Other commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (rcContents !== expected) { + throw new Error(`~/.zsh_profile does not match expectation. Contents:\n${rcContents}`); + } + }); + + // Appends to existing `.profile` file (using Zsh). + await mockHome(async (home) => { + const profile = path.join(home, '.profile'); + await fs.writeFile(profile, '# Other commands...'); + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }, + ); + + const rcContents = await fs.readFile(profile, 'utf-8'); + const expected = `# Other commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (rcContents !== expected) { + throw new Error(`~/.profile does not match expectation. Contents:\n${rcContents}`); + } + }); + + // Zsh prefers `.zshrc`. + await mockHome(async (home) => { + const zshrc = path.join(home, '.zshrc'); + await fs.writeFile(zshrc, '# `.zshrc` commands...'); + const zshProfile = path.join(home, '.zsh_profile'); + await fs.writeFile(zshProfile, '# `.zsh_profile` commands...'); + const profile = path.join(home, '.profile'); + await fs.writeFile(profile, '# `.profile` commands...'); + + await execAndWaitForOutputToMatch( + 'ng', + ['completion'], + /Appended `source <\(ng completion script\)`/, + { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }, + ); + + const zshrcContents = await fs.readFile(zshrc, 'utf-8'); + const zshrcExpected = `# \`.zshrc\` commands... + +# Load Angular CLI autocompletion. +source <(ng completion script) +`; + if (zshrcContents !== zshrcExpected) { + throw new Error(`~/.zshrc does not match expectation. Contents:\n${zshrcContents}`); + } + + const zshProfileContents = await fs.readFile(zshProfile, 'utf-8'); + if (zshProfileContents !== '# `.zsh_profile` commands...') { + throw new Error( + `~/.zsh_profile does not match expectation. Contents:\n${zshProfileContents}`, + ); + } + const profileContents = await fs.readFile(profile, 'utf-8'); + if (profileContents !== '# `.profile` commands...') { + throw new Error(`~/.profile does not match expectation. Contents:\n${profileContents}`); + } + }); + + // Fails for no `$HOME` directory. + { + const err = await execAndCaptureError('ng', ['completion'], { + ...process.env, + SHELL: '/bin/bash', + HOME: undefined, + }); + if (!err.message.includes('`$HOME` environment variable not set.')) { + throw new Error(`Expected unset \`$HOME\` error message, but got:\n\n${err.message}`); + } + } + + // Fails for no `$SHELL`. + await mockHome(async (home) => { + const err = await execAndCaptureError('ng', ['completion'], { + ...process.env, + SHELL: undefined, + }); + if (!err.message.includes('`$SHELL` environment variable not set.')) { + throw new Error(`Expected unset \`$SHELL\` error message, but got:\n\n${err.message}`); + } + }); + + // Fails for unknown `$SHELL`. + await mockHome(async (home) => { + const err = await execAndCaptureError('ng', ['completion'], { + ...process.env, + SHELL: '/usr/bin/unknown', + }); + if (!err.message.includes('Unknown `$SHELL` environment variable')) { + throw new Error(`Expected unknown \`$SHELL\` error message, but got:\n\n${err.message}`); + } + }); + + // Does *not* warn when a global CLI install is present on the system. + await mockHome(async (home) => { + const { stdout } = await execWithEnv('ng', ['completion'], { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }); + + if (stdout.includes('there does not seem to be a global install of the Angular CLI')) { + throw new Error(`CLI warned about missing global install, but one should exist.`); + } + }); + + // Warns when a global CLI install is *not* present on the system. + await mockHome(async (home) => { + try { + // Temporarily uninstall the global CLI binary from the system. + await silentNpm(['uninstall', '--global', '@angular/cli', `--registry=${testRegistry}`]); + + // Setup a fake project directory with a local install of the CLI. + const projectDir = path.join(home, 'project'); + await fs.mkdir(projectDir); + await silentNpm(['init', '-y', `--registry=${testRegistry}`], { cwd: projectDir }); + await silentNpm(['install', '@angular/cli', `--registry=${testRegistry}`], { + cwd: projectDir, + }); + + // Invoke the local CLI binary. + const localCliBinary = path.join(projectDir, 'node_modules', '.bin', 'ng'); + const { stdout } = await execWithEnv(localCliBinary, ['completion'], { + ...process.env, + 'SHELL': '/usr/bin/zsh', + }); + + if (stdout.includes('there does not seem to be a global install of the Angular CLI')) { + throw new Error(`CLI warned about missing global install, but one should exist.`); + } + } finally { + // Reinstall global CLI for remainder of the tests. + await silentNpm(['install', '--global', '@angular/cli', `--registry=${testRegistry}`]); + } + }); +} + +async function windowsTests(): Promise { + // Should fail with a clear error message. + const err = await execAndCaptureError('ng', ['completion']); + if (!err.message.includes("Cmd and Powershell don't support command autocompletion")) { + throw new Error( + `Expected Windows autocompletion to fail with custom error, but got:\n\n${err.message}`, + ); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-get.ts b/tests/legacy-cli/e2e/tests/commands/config/config-get.ts index 4786838afefc..0f33c75feaee 100644 --- a/tests/legacy-cli/e2e/tests/commands/config/config-get.ts +++ b/tests/legacy-cli/e2e/tests/commands/config/config-get.ts @@ -1,35 +1,44 @@ import { ng } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; +export default async function () { + await expectToFail(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle')); + await ng('config', 'schematics.@schematics/angular.component.inlineStyle', 'false'); + const { stdout } = await ng('config', 'schematics.@schematics/angular.component.inlineStyle'); + if (!stdout.match(/false\n?/)) { + throw new Error(`Expected "false", received "${JSON.stringify(stdout)}".`); + } -export default function() { - return Promise.resolve() - .then(() => expectToFail(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle'))) - .then(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle', 'false')) - .then(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle')) - .then(({ stdout }) => { - if (!stdout.match(/false\n?/)) { - throw new Error(`Expected "false", received "${JSON.stringify(stdout)}".`); - } - }) - .then(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle', 'true')) - .then(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle')) - .then(({ stdout }) => { - if (!stdout.match(/true\n?/)) { - throw new Error(`Expected "true", received "${JSON.stringify(stdout)}".`); - } - }) - .then(() => ng('config', 'schematics.@schematics/angular.component.inlineStyle', 'false')) - .then(() => ng('config', `projects.test-project.architect.build.options.assets[0]`)) - .then(({ stdout }) => { - if (!stdout.includes('src/favicon.ico')) { - throw new Error(`Expected "src/favicon.ico", received "${JSON.stringify(stdout)}".`); - } - }) - .then(() => ng('config', `projects["test-project"].architect.build.options.assets[0]`)) - .then(({ stdout }) => { - if (!stdout.includes('src/favicon.ico')) { - throw new Error(`Expected "src/favicon.ico", received "${JSON.stringify(stdout)}".`); - } - }); + await ng('config', 'schematics.@schematics/angular.component.inlineStyle', 'true'); + const { stdout: stdout1 } = await ng( + 'config', + 'schematics.@schematics/angular.component.inlineStyle', + ); + if (!stdout1.match(/true\n?/)) { + throw new Error(`Expected "true", received "${JSON.stringify(stdout)}".`); + } + + await ng('config', 'schematics.@schematics/angular.component.inlineStyle', 'false'); + const { stdout: stdout2 } = await ng( + 'config', + `projects.test-project.architect.build.options.assets[0]`, + ); + if (!stdout2.includes('src/favicon.ico')) { + throw new Error(`Expected "src/favicon.ico", received "${JSON.stringify(stdout)}".`); + } + + const { stdout: stdout3 } = await ng( + 'config', + `projects["test-project"].architect.build.options.assets[0]`, + ); + + if (!stdout3.includes('src/favicon.ico')) { + throw new Error(`Expected "src/favicon.ico", received "${JSON.stringify(stdout)}".`); + } + + // should print all config when no positional args are provided. + const { stdout: stdout4 } = await ng('config'); + if (!stdout4.includes('$schema')) { + throw new Error(`Expected to contain "$schema", received "${JSON.stringify(stdout)}".`); + } } diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-global-validation.ts b/tests/legacy-cli/e2e/tests/commands/config/config-global-validation.ts new file mode 100644 index 000000000000..96fe3383f9a9 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/config/config-global-validation.ts @@ -0,0 +1,51 @@ +import { homedir } from 'os'; +import * as path from 'path'; +import { deleteFile, expectFileToExist } from '../../../utils/fs'; +import { ng, silentNg } from '../../../utils/process'; +import { expectToFail } from '../../../utils/utils'; + +export default async function () { + let ngError: Error; + + ngError = await expectToFail(() => silentNg('config', 'cli.completion.prompted', 'true')); + + if ( + !ngError.message.includes('Data path "/cli" must NOT have additional properties(completion).') + ) { + throw new Error('Should have failed with must NOT have additional properties(completion).'); + } + + ngError = await expectToFail(() => + silentNg('config', '--global', 'cli.completion.invalid', 'true'), + ); + + if ( + !ngError.message.includes( + 'Data path "/cli/completion" must NOT have additional properties(invalid).', + ) + ) { + throw new Error('Should have failed with must NOT have additional properties(invalid).'); + } + + ngError = await expectToFail(() => silentNg('config', '--global', 'cli.cache.enabled', 'true')); + + if (!ngError.message.includes('Data path "/cli" must NOT have additional properties(cache).')) { + throw new Error('Should have failed with must NOT have additional properties(cache).'); + } + + ngError = await expectToFail(() => silentNg('config', 'cli.completion.prompted')); + + if (!ngError.message.includes('Value cannot be found.')) { + throw new Error('Should have failed with Value cannot be found.'); + } + + await ng('config', '--global', 'cli.completion.prompted', 'true'); + const { stdout } = await silentNg('config', '--global', 'cli.completion.prompted'); + + if (!stdout.includes('true')) { + throw new Error(`Expected "true", received "${JSON.stringify(stdout)}".`); + } + + await expectFileToExist(path.join(homedir(), '.angular-config.json')); + await deleteFile(path.join(homedir(), '.angular-config.json')); +} diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-global.ts b/tests/legacy-cli/e2e/tests/commands/config/config-global.ts index 8327836fc500..193fa3ee3829 100644 --- a/tests/legacy-cli/e2e/tests/commands/config/config-global.ts +++ b/tests/legacy-cli/e2e/tests/commands/config/config-global.ts @@ -4,13 +4,10 @@ import { deleteFile, expectFileToExist } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; - -export default async function() { - await expectToFail(() => ng( - 'config', - '--global', - 'schematics.@schematics/angular.component.inlineStyle', - )); +export default async function () { + await expectToFail(() => + ng('config', '--global', 'schematics.@schematics/angular.component.inlineStyle'), + ); await ng('config', '--global', 'schematics.@schematics/angular.component.inlineStyle', 'false'); let output = await ng( diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-set-enum-check.ts b/tests/legacy-cli/e2e/tests/commands/config/config-set-enum-check.ts index 694f90f6020a..571dc74cdd14 100644 --- a/tests/legacy-cli/e2e/tests/commands/config/config-set-enum-check.ts +++ b/tests/legacy-cli/e2e/tests/commands/config/config-set-enum-check.ts @@ -1,20 +1,15 @@ import { ng } from '../../../utils/process'; -export default async function() { +export default async function () { + // These tests require schema querying capabilities + // .then(() => expectToFail( + // () => ng('config', 'schematics.@schematics/angular.component.aaa', 'bbb')), + // ) + // .then(() => expectToFail(() => ng( + // 'config', + // 'schematics.@schematics/angular.component.viewEncapsulation', + // 'bbb', + // ))) - // These tests require schema querying capabilities - // .then(() => expectToFail( - // () => ng('config', 'schematics.@schematics/angular.component.aaa', 'bbb')), - // ) - // .then(() => expectToFail(() => ng( - // 'config', - // 'schematics.@schematics/angular.component.viewEncapsulation', - // 'bbb', - // ))) - - await ng( - 'config', - 'schematics.@schematics/angular.component.viewEncapsulation', - 'Emulated', - ); + await ng('config', 'schematics.@schematics/angular.component.viewEncapsulation', 'Emulated'); } diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-set-prefix.ts b/tests/legacy-cli/e2e/tests/commands/config/config-set-prefix.ts index 904050649129..09c3afea9f7f 100644 --- a/tests/legacy-cli/e2e/tests/commands/config/config-set-prefix.ts +++ b/tests/legacy-cli/e2e/tests/commands/config/config-set-prefix.ts @@ -1,10 +1,10 @@ import { ng } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; -export default function() { +export default function () { return Promise.resolve() .then(() => expectToFail(() => ng('config', 'schematics.@schematics/angular.component.prefix'))) - .then(() => ng('config', 'schematics.@schematics/angular.component.prefix' , 'new-prefix')) + .then(() => ng('config', 'schematics.@schematics/angular.component.prefix', 'new-prefix')) .then(() => ng('config', 'schematics.@schematics/angular.component.prefix')) .then(({ stdout }) => { if (!stdout.match(/new-prefix/)) { diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-set-serve-port.ts b/tests/legacy-cli/e2e/tests/commands/config/config-set-serve-port.ts index 7557c6f534c9..125d21d7e66f 100644 --- a/tests/legacy-cli/e2e/tests/commands/config/config-set-serve-port.ts +++ b/tests/legacy-cli/e2e/tests/commands/config/config-set-serve-port.ts @@ -1,7 +1,7 @@ import { expectFileToMatch } from '../../../utils/fs'; import { ng } from '../../../utils/process'; -export default function() { +export default function () { return Promise.resolve() .then(() => ng('config', 'projects.test-project.architect.serve.options.port', '1234')) .then(() => expectFileToMatch('angular.json', /"port": 1234/)); diff --git a/tests/legacy-cli/e2e/tests/commands/config/config-set.ts b/tests/legacy-cli/e2e/tests/commands/config/config-set.ts index cabf4c1373a6..689be8c72194 100644 --- a/tests/legacy-cli/e2e/tests/commands/config/config-set.ts +++ b/tests/legacy-cli/e2e/tests/commands/config/config-set.ts @@ -1,21 +1,42 @@ -import { ng } from '../../../utils/process'; +import { ng, silentNg } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; -export default function() { - return Promise.resolve() - .then(() => expectToFail(() => ng('config', 'cli.warnings.zzzz'))) - .then(() => ng('config', 'cli.warnings.versionMismatch' , 'false')) - .then(() => ng('config', 'cli.warnings.versionMismatch')) - .then(({ stdout }) => { - if (!stdout.match(/false/)) { - throw new Error(`Expected "false", received "${JSON.stringify(stdout)}".`); - } - }) - .then(() => ng('config', 'cli.packageManager' , 'yarn')) - .then(() => ng('config', 'cli.packageManager')) - .then(({ stdout }) => { - if (!stdout.match(/yarn/)) { - throw new Error(`Expected "yarn", received "${JSON.stringify(stdout)}".`); - } - }); +export default async function () { + let ngError: Error; + + ngError = await expectToFail(() => silentNg('config', 'cli.warnings.zzzz', 'true')); + if ( + !ngError.message.includes( + 'Data path "/cli/warnings" must NOT have additional properties(zzzz).', + ) + ) { + throw new Error('Should have failed with must NOT have additional properties(zzzz).'); + } + + ngError = await expectToFail(() => silentNg('config', 'cli.warnings.zzzz')); + if (!ngError.message.includes('Value cannot be found.')) { + throw new Error('Should have failed with Value cannot be found.'); + } + + await ng('config', 'cli.warnings.versionMismatch', 'false'); + const { stdout } = await ng('config', 'cli.warnings.versionMismatch'); + if (!stdout.includes('false')) { + throw new Error(`Expected "false", received "${JSON.stringify(stdout)}".`); + } + + await ng('config', 'cli.packageManager', 'yarn'); + const { stdout: stdout2 } = await ng('config', 'cli.packageManager'); + if (!stdout2.includes('yarn')) { + throw new Error(`Expected "yarn", received "${JSON.stringify(stdout2)}".`); + } + + await ng('config', 'schematics', '{"@schematics/angular:component":{"style": "scss"}}'); + const { stdout: stdout3 } = await ng('config', 'schematics.@schematics/angular:component.style'); + if (!stdout3.includes('scss')) { + throw new Error(`Expected "scss", received "${JSON.stringify(stdout3)}".`); + } + + await ng('config', 'schematics'); + await ng('config', 'schematics', 'undefined'); + await expectToFail(() => ng('config', 'schematics')); } diff --git a/tests/legacy-cli/e2e/tests/commands/e2e/e2e-and-serve.ts b/tests/legacy-cli/e2e/tests/commands/e2e/e2e-and-serve.ts new file mode 100644 index 000000000000..6333c05be279 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/e2e/e2e-and-serve.ts @@ -0,0 +1,12 @@ +import { killAllProcesses, silentNg } from '../../../utils/process'; +import { ngServe } from '../../../utils/project'; + +export default async function () { + try { + // Should run side-by-side with `ng serve` + await ngServe(); + await silentNg('e2e'); + } finally { + killAllProcesses(); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/e2e/multiple-specs.ts b/tests/legacy-cli/e2e/tests/commands/e2e/multiple-specs.ts new file mode 100644 index 000000000000..c7da20adf900 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/e2e/multiple-specs.ts @@ -0,0 +1,17 @@ +import { silentNg } from '../../../utils/process'; +import { moveFile, copyFile } from '../../../utils/fs'; + +export default async function () { + // Should accept different multiple spec files + await moveFile('./e2e/src/app.e2e-spec.ts', './e2e/src/renamed-app.e2e-spec.ts'); + await copyFile('./e2e/src/renamed-app.e2e-spec.ts', './e2e/src/another-app.e2e-spec.ts'); + + await silentNg( + 'e2e', + 'test-project', + '--specs', + './e2e/renamed-app.e2e-spec.ts', + '--specs', + './e2e/another-app.e2e-spec.ts', + ); +} diff --git a/tests/legacy-cli/e2e/tests/commands/e2e/protractor-config.ts b/tests/legacy-cli/e2e/tests/commands/e2e/protractor-config.ts new file mode 100644 index 000000000000..52e9494e4062 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/e2e/protractor-config.ts @@ -0,0 +1,8 @@ +import { moveFile } from '../../../utils/fs'; +import { silentNg } from '../../../utils/process'; + +export default async function () { + // Should accept different config file + await moveFile('./e2e/protractor.conf.js', './e2e/renamed-protractor.conf.js'); + await silentNg('e2e', 'test-project', '--protractor-config=e2e/renamed-protractor.conf.js'); +} diff --git a/tests/legacy-cli/e2e/tests/commands/e2e/suite.ts b/tests/legacy-cli/e2e/tests/commands/e2e/suite.ts new file mode 100644 index 000000000000..519ed63a71bb --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/e2e/suite.ts @@ -0,0 +1,16 @@ +import { silentNg } from '../../../utils/process'; +import { replaceInFile } from '../../../utils/fs'; + +export default async function () { + // Suites block need to be added in the protractor.conf.js file to test suites + await replaceInFile( + 'e2e/protractor.conf.js', + `allScriptsTimeout: 11000,`, + `allScriptsTimeout: 11000, + suites: { + app: './e2e/src/app.e2e-spec.ts' + }, + `, + ); + await silentNg('e2e', 'test-project', '--suite=app'); +} diff --git a/tests/legacy-cli/e2e/tests/commands/help/help-hidden.ts b/tests/legacy-cli/e2e/tests/commands/help/help-hidden.ts index 5f88a787257c..bf616039600b 100644 --- a/tests/legacy-cli/e2e/tests/commands/help/help-hidden.ts +++ b/tests/legacy-cli/e2e/tests/commands/help/help-hidden.ts @@ -1,26 +1,15 @@ -import { oneLine } from 'common-tags'; - import { silentNg } from '../../../utils/process'; +export default async function () { + const { stdout: stdoutNew } = await silentNg('--help'); + if (/(easter-egg)|(ng make-this-awesome)|(ng init)/.test(stdoutNew)) { + throw new Error( + 'Expected to not match "(easter-egg)|(ng make-this-awesome)|(ng init)" in help output.', + ); + } -export default function() { - return Promise.resolve() - .then(() => silentNg('--help')) - .then(({ stdout }) => { - if (stdout.match(/(easter-egg)|(ng make-this-awesome)|(ng init)/)) { - throw new Error(oneLine` - Expected to not match "(easter-egg)|(ng make-this-awesome)|(ng init)" - in help output. - `); - } - }) - .then(() => silentNg('--help', 'new')) - .then(({ stdout }) => { - if (stdout.match(/--link-cli/)) { - throw new Error(oneLine` - Expected to not match "--link-cli" - in help output. - `); - } - }) + const { stdout: ngGenerate } = await silentNg('--help', 'generate', 'component'); + if (ngGenerate.includes('--path')) { + throw new Error('Expected to not match "--path" in help output.'); + } } diff --git a/tests/legacy-cli/e2e/tests/commands/help/help-json.ts b/tests/legacy-cli/e2e/tests/commands/help/help-json.ts index 898adfbe5bc6..7b3e1c74e6b4 100644 --- a/tests/legacy-cli/e2e/tests/commands/help/help-json.ts +++ b/tests/legacy-cli/e2e/tests/commands/help/help-json.ts @@ -1,19 +1,72 @@ import { silentNg } from '../../../utils/process'; +export default async function () { + // This test is use as a sanity check. + const addHelpOutputSnapshot = JSON.stringify({ + 'name': 'config', + 'command': 'ng config [json-path] [value]', + 'shortDescription': + 'Retrieves or sets Angular configuration values in the angular.json file for the workspace.', + 'longDescriptionRelativePath': '@angular/cli/src/commands/config/long-description.md', + 'longDescription': + 'A workspace has a single CLI configuration file, `angular.json`, at the top level.\nThe `projects` object contains a configuration object for each project in the workspace.\n\nYou can edit the configuration directly in a code editor,\nor indirectly on the command line using this command.\n\nThe configurable property names match command option names,\nexcept that in the configuration file, all names must use camelCase,\nwhile on the command line options can be given dash-case.\n\nFor further details, see [Workspace Configuration](guide/workspace-config).\n\nFor configuration of CLI usage analytics, see [ng analytics](cli/analytics).\n', + 'options': [ + { + 'name': 'global', + 'type': 'boolean', + 'aliases': ['g'], + 'default': false, + 'description': "Access the global configuration in the caller's home directory.", + }, + { + 'name': 'help', + 'type': 'boolean', + 'description': 'Shows a help message for this command in the console.', + }, + { + 'name': 'json-path', + 'type': 'string', + 'description': + 'The configuration key to set or query, in JSON path format. For example: "a[3].foo.bar[2]". If no new value is provided, returns the current value of this key.', + 'positional': 0, + }, + { + 'name': 'value', + 'type': 'string', + 'description': 'If provided, a new value for the given configuration key.', + 'positional': 1, + }, + ], + }); -export default async function() { - const commands = require('@angular/cli/commands.json'); - for (const commandName of Object.keys(commands)) { - const { stdout } = await silentNg(commandName, '--help=json'); + const { stdout } = await silentNg('config', '--help', '--json-help'); + const output = JSON.stringify(JSON.parse(stdout.trim())); - if (stdout.trim()) { - JSON.parse(stdout, (key, value) => { - if (key === 'name' && /[A-Z]/.test(value)) { - throw new Error(`Option named '${value}' is not kebab case.`); - } - }); - } else { - console.warn(`No JSON output for command [${commandName}].`); - } + if (output !== addHelpOutputSnapshot) { + throw new Error( + `ng config JSON help output didn\'t match snapshot.\n\nExpected "${output}" to be "${addHelpOutputSnapshot}".`, + ); + } + + const { stdout: stdout2 } = await silentNg('--help', '--json-help'); + try { + JSON.parse(stdout2.trim()); + } catch (error) { + throw new Error( + `'ng --help ---json-help' failed to return JSON.\n${ + error instanceof Error ? error.message : error + }`, + ); + } + + const { stdout: stdout3 } = await silentNg('generate', '--help', '--json-help'); + try { + JSON.parse(stdout3.trim()); + } catch (error) { + throw new Error( + `'ng generate --help ---json-help' failed to return JSON.\n${ + error instanceof Error ? error.message : error + }`, + ); } } diff --git a/tests/legacy-cli/e2e/tests/commands/help/help-option-command.ts b/tests/legacy-cli/e2e/tests/commands/help/help-option-command.ts deleted file mode 100644 index c6782765b839..000000000000 --- a/tests/legacy-cli/e2e/tests/commands/help/help-option-command.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {silentNg} from '../../../utils/process'; - - -export default function() { - return Promise.resolve() - .then(() => silentNg('--help', 'build')); -} diff --git a/tests/legacy-cli/e2e/tests/commands/help/help-option.ts b/tests/legacy-cli/e2e/tests/commands/help/help-option.ts deleted file mode 100644 index 03b96b5758d9..000000000000 --- a/tests/legacy-cli/e2e/tests/commands/help/help-option.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {silentNg} from '../../../utils/process'; - - -export default function() { - return Promise.resolve() - .then(() => silentNg('--help')) - .then(() => process.chdir('/')) - .then(() => silentNg('--help')); -} diff --git a/tests/legacy-cli/e2e/tests/commands/help/help.ts b/tests/legacy-cli/e2e/tests/commands/help/help.ts deleted file mode 100644 index f326f6a81ff8..000000000000 --- a/tests/legacy-cli/e2e/tests/commands/help/help.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {silentNg} from '../../../utils/process'; - - -export default function() { - return Promise.resolve() - .then(() => silentNg('help')) - .then(() => process.chdir('/')) - .then(() => silentNg('help')); -} diff --git a/tests/legacy-cli/e2e/tests/commands/ng-new-collection.ts b/tests/legacy-cli/e2e/tests/commands/ng-new-collection.ts new file mode 100644 index 000000000000..e55c75ee69b4 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/ng-new-collection.ts @@ -0,0 +1,19 @@ +import { execAndWaitForOutputToMatch } from '../../utils/process'; + +export default async function () { + const currentDirectory = process.cwd(); + + try { + process.chdir('..'); + + // The below is a way to validate that the `--collection` option is being considered. + await execAndWaitForOutputToMatch( + 'ng', + ['new', '--collection', 'invalid-schematic'], + /Collection "invalid-schematic" cannot be resolved/, + ); + } finally { + // Change directory back + process.chdir(currentDirectory); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts new file mode 100644 index 000000000000..40ccce6640da --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts @@ -0,0 +1,39 @@ +import { join } from 'path'; +import { execAndWaitForOutputToMatch, ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + const errorMessage = + 'Cannot determine project for command.\n' + + 'This is a multi-project workspace and more than one project supports this command.'; + + // Delete root project + await updateJsonFile('angular.json', (workspaceJson) => { + delete workspaceJson.projects['test-project']; + }); + + await ng('generate', 'app', 'second-app', '--skip-install'); + await ng('generate', 'app', 'third-app', '--skip-install'); + + const startCwd = process.cwd(); + + try { + const { message } = await expectToFail(() => ng('build')); + if (!message.includes(errorMessage)) { + throw new Error(`Expected build to fail with: '${errorMessage}'.`); + } + + // Help should still work + execAndWaitForOutputToMatch('ng', ['build', '--help'], /--configuration/); + + // Yargs allows positional args to be passed as flags. Verify that in this case the project can be determined. + await ng('build', '--project=third-app', '--configuration=development'); + + process.chdir(join(startCwd, 'projects/second-app')); + await ng('build', '--configuration=development'); + } finally { + // Restore path + process.chdir(startCwd); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/run-configuration-option.ts b/tests/legacy-cli/e2e/tests/commands/run-configuration-option.ts new file mode 100644 index 000000000000..827d8dea6fc9 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/run-configuration-option.ts @@ -0,0 +1,26 @@ +import { silentNg } from '../../utils/process'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + const errorMatch = `Provide the configuration as part of the target 'ng run test-project:build:production`; + + { + const { message } = await expectToFail(() => + silentNg('run', 'test-project:build:development', '--configuration=production'), + ); + + if (!message.includes(errorMatch)) { + throw new Error(`Expected error to include '${errorMatch}' but didn't.\n\n${message}`); + } + } + + { + const { message } = await expectToFail(() => + silentNg('run', 'test-project:build', '--configuration=production'), + ); + + if (!message.includes(errorMatch)) { + throw new Error(`Expected error to include '${errorMatch}' but didn't.\n\n${message}`); + } + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/serve/preflight-request.ts b/tests/legacy-cli/e2e/tests/commands/serve/preflight-request.ts new file mode 100644 index 000000000000..f12402e31199 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/serve/preflight-request.ts @@ -0,0 +1,15 @@ +import fetch from 'node-fetch'; +import { ngServe } from '../../../utils/project'; + +export default async function () { + const port = await ngServe(); + const { size, status } = await fetch(`http://localhost:${port}/main.js`, { method: 'OPTIONS' }); + + if (size !== 0) { + throw new Error(`Expected "size" to be "0" but got "${size}".`); + } + + if (status !== 204) { + throw new Error(`Expected "status" to be "204" but got "${status}".`); + } +} diff --git a/tests/legacy-cli/e2e/tests/commands/serve/reload-shims.ts b/tests/legacy-cli/e2e/tests/commands/serve/reload-shims.ts index d597ac1e39fc..1d1c8a51d6b6 100644 --- a/tests/legacy-cli/e2e/tests/commands/serve/reload-shims.ts +++ b/tests/legacy-cli/e2e/tests/commands/serve/reload-shims.ts @@ -1,7 +1,7 @@ import { prependToFile, writeFile } from '../../../utils/fs'; import { execAndWaitForOutputToMatch, killAllProcesses } from '../../../utils/process'; -export default async function() { +export default async function () { // Simulate a JS library using a Node.js specific module await writeFile('src/node-usage.js', `const path = require('path');\n`); await prependToFile('src/main.ts', `import './node-usage';\n`); @@ -20,6 +20,6 @@ export default async function() { /Module not found: Error: Can't resolve 'path'/, ); } finally { - killAllProcesses(); + await killAllProcesses(); } } diff --git a/tests/legacy-cli/e2e/tests/commands/serve/serve-path.ts b/tests/legacy-cli/e2e/tests/commands/serve/serve-path.ts index 2075b1acd3c0..d5783b788363 100644 --- a/tests/legacy-cli/e2e/tests/commands/serve/serve-path.ts +++ b/tests/legacy-cli/e2e/tests/commands/serve/serve-path.ts @@ -1,31 +1,23 @@ -import { request } from '../../../utils/http'; +import * as assert from 'assert'; +import fetch from 'node-fetch'; import { killAllProcesses } from '../../../utils/process'; import { ngServe } from '../../../utils/project'; -export default function () { +export default async function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. + const port = await ngServe('--serve-path', 'test/'); + return Promise.resolve() - .then(() => ngServe('--serve-path', 'test/')) - .then(() => request('http://localhost:4200/test')) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } + .then(() => fetch(`http://localhost:${port}/test`, { headers: { 'Accept': 'text/html' } })) + .then(async (response) => { + assert.strictEqual(response.status, 200); + assert.match(await response.text(), /<\/app-root>/); }) - .then(() => request('http://localhost:4200/test/abc')) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } + .then(() => fetch(`http://localhost:${port}/test/abc`, { headers: { 'Accept': 'text/html' } })) + .then(async (response) => { + assert.strictEqual(response.status, 200); + assert.match(await response.text(), /<\/app-root>/); }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - // .then(() => ngServe('--base-href', 'test/')) - // .then(() => request('http://localhost:4200/test')) - // .then(body => { - // if (!body.match(/<\/app-root>/)) { - // throw new Error('Response does not match expected value.'); - // } - // }) - // .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }); + .finally(() => killAllProcesses()); } diff --git a/tests/legacy-cli/e2e/tests/commands/unknown-configuration.ts b/tests/legacy-cli/e2e/tests/commands/unknown-configuration.ts index 98257ee86e70..7b5bbc106702 100644 --- a/tests/legacy-cli/e2e/tests/commands/unknown-configuration.ts +++ b/tests/legacy-cli/e2e/tests/commands/unknown-configuration.ts @@ -1,12 +1,17 @@ -import { ng } from "../../utils/process"; +import { ng } from '../../utils/process'; export default async function () { try { await ng('build', '--configuration', 'invalid'); throw new Error('should have failed.'); } catch (error) { - if (!error.message.includes(`Configuration 'invalid' is not set in the workspace`)) { + if ( + !( + error instanceof Error && + error.message.includes(`Configuration 'invalid' is not set in the workspace`) + ) + ) { throw error; } } -}; +} diff --git a/tests/legacy-cli/e2e/tests/commands/unknown-option.ts b/tests/legacy-cli/e2e/tests/commands/unknown-option.ts index 220d74bc1646..f0f4cde0693f 100644 --- a/tests/legacy-cli/e2e/tests/commands/unknown-option.ts +++ b/tests/legacy-cli/e2e/tests/commands/unknown-option.ts @@ -1,27 +1,17 @@ import { execAndWaitForOutputToMatch, ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; -export default async function() { +export default async function () { await expectToFail(() => ng('build', '--notanoption')); await execAndWaitForOutputToMatch( 'ng', - [ 'build', '--notanoption' ], - /Unknown option: '--notanoption'/, + ['build', '--notanoption'], + /Unknown argument: notanoption/, ); - await expectToFail(() => execAndWaitForOutputToMatch( - 'ng', - [ 'build', '--notanoption' ], - /should NOT have additional properties\(notanoption\)./, - )); - - const ngGenerateArgs = [ 'generate', 'component', 'component-name', '--notanoption' ]; + const ngGenerateArgs = ['generate', 'component', 'component-name', '--notanoption']; await expectToFail(() => ng(...ngGenerateArgs)); - await execAndWaitForOutputToMatch( - 'ng', - ngGenerateArgs, - /Unknown option: '--notanoption'/, - ); + await execAndWaitForOutputToMatch('ng', ngGenerateArgs, /Unknown argument: notanoption/); } diff --git a/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts b/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts index 44c92b2c347e..615e08426c2b 100644 --- a/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts @@ -2,10 +2,9 @@ import { expectFileToMatch } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { useCIChrome } from '../../../utils/project'; - -export default function() { +export default function () { return ng('generate', 'application', 'app2') .then(() => expectFileToMatch('angular.json', /\"app2\":/)) - .then(() => useCIChrome('projects/app2')) + .then(() => useCIChrome('app2', 'projects/app2')) .then(() => ng('test', 'app2', '--watch=false')); } diff --git a/tests/legacy-cli/e2e/tests/generate/class.ts b/tests/legacy-cli/e2e/tests/generate/class.ts index aa3ba25b5899..2363de96132c 100644 --- a/tests/legacy-cli/e2e/tests/generate/class.ts +++ b/tests/legacy-cli/e2e/tests/generate/class.ts @@ -1,16 +1,17 @@ -import {join} from 'path'; -import {ng} from '../../utils/process'; -import {expectFileToExist} from '../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../utils/process'; +import { expectFileToExist } from '../../utils/fs'; - -export default function() { +export default function () { const projectDir = join('src', 'app'); - return ng('generate', 'class', 'test-class') - .then(() => expectFileToExist(projectDir)) - .then(() => expectFileToExist(join(projectDir, 'test-class.ts'))) - .then(() => expectFileToExist(join(projectDir, 'test-class.spec.ts'))) + return ( + ng('generate', 'class', 'test-class') + .then(() => expectFileToExist(projectDir)) + .then(() => expectFileToExist(join(projectDir, 'test-class.ts'))) + .then(() => expectFileToExist(join(projectDir, 'test-class.spec.ts'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-basic.ts b/tests/legacy-cli/e2e/tests/generate/component/component-basic.ts index 7c39f144d00d..66b226282027 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-basic.ts @@ -1,22 +1,22 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist, expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist, expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const projectDir = join('src', 'app'); const componentDir = join(projectDir, 'test-component'); - const importCheck = - `import { TestComponentComponent } from './test-component/test-component.component';`; - return ng('generate', 'component', 'test-component') - .then(() => expectFileToExist(componentDir)) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.html'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) - .then(() => expectFileToMatch(join(projectDir, 'app.module.ts'), importCheck)) + const importCheck = `import { TestComponentComponent } from './test-component/test-component.component';`; + return ( + ng('generate', 'component', 'test-component') + .then(() => expectFileToExist(componentDir)) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.html'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) + .then(() => expectFileToMatch(join(projectDir, 'app.module.ts'), importCheck)) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-duplicate.ts b/tests/legacy-cli/e2e/tests/generate/component/component-duplicate.ts index 7c8e4074bb07..c00e573df793 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-duplicate.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-duplicate.ts @@ -1,4 +1,3 @@ -import { oneLine } from 'common-tags'; import { appendToFile } from '../../../utils/fs'; import { ng } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; @@ -7,12 +6,11 @@ export default function () { return ng('generate', 'component', 'test-component') .then((output) => { if (!output.stdout.match(/UPDATE src[\\|\/]app[\\|\/]app.module.ts/)) { - throw new Error(oneLine` - Expected to match - "UPDATE src/app.module.ts" - in ${output.stdout}.`); + throw new Error(`Expected to match "UPDATE src/app.module.ts" in ${output.stdout}.`); } }) - .then(() => appendToFile('src/app/test-component/test-component.component.ts', '\n// new content')) + .then(() => + appendToFile('src/app/test-component/test-component.component.ts', '\n// new content'), + ) .then(() => expectToFail(() => ng('generate', 'component', 'test-component'))); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-flag-case.ts b/tests/legacy-cli/e2e/tests/generate/component/component-flag-case.ts deleted file mode 100644 index a759ae01832a..000000000000 --- a/tests/legacy-cli/e2e/tests/generate/component/component-flag-case.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; - - -export default function() { - // TODO:BREAKING CHANGE... NO LONGER SUPPORTED - return Promise.resolve(); - const compDir = join('projects', 'test-project', 'src', 'test'); - - return Promise.resolve() - .then(() => ng('generate', 'component', 'test', - '--change-detection', 'onpush', - '--view-encapsulation', 'emulated')) - .then(() => expectFileToMatch(join(compDir, 'test.component.ts'), - /changeDetection: ChangeDetectionStrategy.OnPush/)) - .then(() => expectFileToMatch(join(compDir, 'test.component.ts'), - /encapsulation: ViewEncapsulation.Emulated/)); -} diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-flat.ts b/tests/legacy-cli/e2e/tests/generate/component/component-flat.ts index 448a6aef4df3..a17a5c24bd9d 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-flat.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-flat.ts @@ -1,24 +1,27 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; -import {updateJsonFile} from '../../../utils/project'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist } from '../../../utils/fs'; +import { updateJsonFile } from '../../../utils/project'; - -export default function() { +export default function () { const appDir = join('src', 'app'); - return Promise.resolve() - .then(() => updateJsonFile('angular.json', configJson => { - configJson.projects['test-project'].schematics = { - '@schematics/angular:component': { flat: true } - }; - })) - .then(() => ng('generate', 'component', 'test-component')) - .then(() => expectFileToExist(appDir)) - .then(() => expectFileToExist(join(appDir, 'test-component.component.ts'))) - .then(() => expectFileToExist(join(appDir, 'test-component.component.spec.ts'))) - .then(() => expectFileToExist(join(appDir, 'test-component.component.html'))) - .then(() => expectFileToExist(join(appDir, 'test-component.component.css'))) + return ( + Promise.resolve() + .then(() => + updateJsonFile('angular.json', (configJson) => { + configJson.projects['test-project'].schematics = { + '@schematics/angular:component': { flat: true }, + }; + }), + ) + .then(() => ng('generate', 'component', 'test-component')) + .then(() => expectFileToExist(appDir)) + .then(() => expectFileToExist(join(appDir, 'test-component.component.ts'))) + .then(() => expectFileToExist(join(appDir, 'test-component.component.spec.ts'))) + .then(() => expectFileToExist(join(appDir, 'test-component.component.html'))) + .then(() => expectFileToExist(join(appDir, 'test-component.component.css'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-inline-template.ts b/tests/legacy-cli/e2e/tests/generate/component/component-inline-template.ts index f76409820a1a..930226520004 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-inline-template.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-inline-template.ts @@ -1,26 +1,31 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; -import {updateJsonFile} from '../../../utils/project'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist } from '../../../utils/fs'; +import { updateJsonFile } from '../../../utils/project'; import { expectToFail } from '../../../utils/utils'; - // tslint:disable:max-line-length -export default function() { +export default function () { const componentDir = join('src', 'app', 'test-component'); - return Promise.resolve() - .then(() => updateJsonFile('angular.json', configJson => { - configJson.projects['test-project'].schematics = { - '@schematics/angular:component': { inlineTemplate: true } - }; - })) - .then(() => ng('generate', 'component', 'test-component')) - .then(() => expectFileToExist(componentDir)) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) - .then(() => expectToFail(() => expectFileToExist(join(componentDir, 'test-component.component.html')))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) + return ( + Promise.resolve() + .then(() => + updateJsonFile('angular.json', (configJson) => { + configJson.projects['test-project'].schematics = { + '@schematics/angular:component': { inlineTemplate: true }, + }; + }), + ) + .then(() => ng('generate', 'component', 'test-component')) + .then(() => expectFileToExist(componentDir)) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) + .then(() => + expectToFail(() => expectFileToExist(join(componentDir, 'test-component.component.html'))), + ) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-module-2.ts b/tests/legacy-cli/e2e/tests/generate/component/component-module-2.ts index c5af6fad4d0a..18c95587c35d 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-module-2.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-module-2.ts @@ -1,20 +1,26 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const root = process.cwd(); - const modulePath = join(root, 'src', 'app', - 'admin', 'module', 'module.module.ts'); + const modulePath = join(root, 'src', 'app', 'admin', 'module', 'module.module.ts'); - return Promise.resolve() - .then(() => ng('generate', 'module', 'admin/module')) - .then(() => ng('generate', 'component', 'other/test-component', '--module', 'admin/module')) - .then(() => expectFileToMatch(modulePath, - new RegExp(/import { TestComponentComponent } /.source + - /from '..\/..\/other\/test-component\/test-component.component'/.source))) + return ( + Promise.resolve() + .then(() => ng('generate', 'module', 'admin/module')) + .then(() => ng('generate', 'component', 'other/test-component', '--module', 'admin/module')) + .then(() => + expectFileToMatch( + modulePath, + new RegExp( + /import { TestComponentComponent } /.source + + /from '..\/..\/other\/test-component\/test-component.component'/.source, + ), + ), + ) - // Try to run the unit tests. - .then(() => ng('build', '--configuration=development')); + // Try to run the unit tests. + .then(() => ng('build', '--configuration=development')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-module-export.ts b/tests/legacy-cli/e2e/tests/generate/component/component-module-export.ts index 4c2b2e89d8c5..64340915a095 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-module-export.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-module-export.ts @@ -1,14 +1,17 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const modulePath = join('src', 'app', 'app.module.ts'); - return ng('generate', 'component', 'test-component', '--export') - .then(() => expectFileToMatch(modulePath, /exports: \[\r?\n(\s*) TestComponentComponent\r?\n\1\]/)) + return ( + ng('generate', 'component', 'test-component', '--export') + .then(() => + expectFileToMatch(modulePath, /exports: \[\r?\n(\s*) TestComponentComponent\r?\n\1\]/), + ) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-module-fail.ts b/tests/legacy-cli/e2e/tests/generate/component/component-module-fail.ts index f4efd1eb8ff6..fe116fcac3d2 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-module-fail.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-module-fail.ts @@ -1,9 +1,10 @@ -import {ng} from '../../../utils/process'; -import {expectToFail} from '../../../utils/utils'; +import { ng } from '../../../utils/process'; +import { expectToFail } from '../../../utils/utils'; - -export default function() { - return Promise.resolve() - .then(() => expectToFail(() => - ng('generate', 'component', 'test-component', '--module', 'app.moduleXXX.ts'))); +export default function () { + return Promise.resolve().then(() => + expectToFail(() => + ng('generate', 'component', 'test-component', '--module', 'app.moduleXXX.ts'), + ), + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-module.ts b/tests/legacy-cli/e2e/tests/generate/component/component-module.ts index 9a77eaadea48..46f7d29d11ba 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-module.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-module.ts @@ -1,23 +1,32 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const root = process.cwd(); // projects/ test-project/ src/ app.module.ts const modulePath = join('src', 'app', 'app.module.ts'); - return ng('generate', 'component', 'test-component', '--module', 'app.module.ts') - .then(() => expectFileToMatch(modulePath, - /import { TestComponentComponent } from '.\/test-component\/test-component.component'/)) + return ( + ng('generate', 'component', 'test-component', '--module', 'app.module.ts') + .then(() => + expectFileToMatch( + modulePath, + /import { TestComponentComponent } from '.\/test-component\/test-component.component'/, + ), + ) - .then(() => process.chdir(join(root, 'src', 'app'))) - .then(() => ng('generate', 'component', 'test-component2', '--module', 'app.module.ts')) - .then(() => process.chdir('../..')) - .then(() => expectFileToMatch(modulePath, - /import { TestComponent2Component } from '.\/test-component2\/test-component2.component'/)) + .then(() => process.chdir(join(root, 'src', 'app'))) + .then(() => ng('generate', 'component', 'test-component2', '--module', 'app.module.ts')) + .then(() => process.chdir('../..')) + .then(() => + expectFileToMatch( + modulePath, + /import { TestComponent2Component } from '.\/test-component2\/test-component2.component'/, + ), + ) - // Try to run the unit tests. - .then(() => ng('build', '--configuration=development')); + // Try to run the unit tests. + .then(() => ng('build', '--configuration=development')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-not-flat.ts b/tests/legacy-cli/e2e/tests/generate/component/component-not-flat.ts index d25936fb6e4f..575fafab99dd 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-not-flat.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-not-flat.ts @@ -1,25 +1,28 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; -import {updateJsonFile} from '../../../utils/project'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist } from '../../../utils/fs'; +import { updateJsonFile } from '../../../utils/project'; - -export default function() { +export default function () { const componentDir = join('src', 'app', 'test-component'); - return Promise.resolve() - .then(() => updateJsonFile('angular.json', configJson => { - configJson.projects['test-project'].schematics = { - '@schematics/angular:component': { flat: false } - }; - })) - .then(() => ng('generate', 'component', 'test-component')) - .then(() => expectFileToExist(componentDir)) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.html'))) - .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) + return ( + Promise.resolve() + .then(() => + updateJsonFile('angular.json', (configJson) => { + configJson.projects['test-project'].schematics = { + '@schematics/angular:component': { flat: false }, + }; + }), + ) + .then(() => ng('generate', 'component', 'test-component')) + .then(() => expectFileToExist(componentDir)) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.spec.ts'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.html'))) + .then(() => expectFileToExist(join(componentDir, 'test-component.component.css'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-path-case.ts b/tests/legacy-cli/e2e/tests/generate/component/component-path-case.ts index f76c09abe1fc..e143c9c56427 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-path-case.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-path-case.ts @@ -11,7 +11,7 @@ export default async function () { try { // Generate a component - await ng('generate', 'component', `${upperDirs}/test-component`) + await ng('generate', 'component', `${upperDirs}/test-component`); // Ensure component is created in the correct location relative to the workspace root await expectFileToExist(join(componentDirectory, 'test-component.component.ts')); diff --git a/tests/legacy-cli/e2e/tests/generate/component/component-prefix.ts b/tests/legacy-cli/e2e/tests/generate/component/component-prefix.ts index f63dece7672a..16ad3c0a025e 100644 --- a/tests/legacy-cli/e2e/tests/generate/component/component-prefix.ts +++ b/tests/legacy-cli/e2e/tests/generate/component/component-prefix.ts @@ -1,26 +1,29 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; import { updateJsonFile } from '../../../utils/project'; - -export default function() { +export default function () { const testCompDir = join('src', 'app', 'test-component'); const aliasCompDir = join('src', 'app', 'alias'); - return Promise.resolve() - .then(() => updateJsonFile('angular.json', configJson => { - configJson.projects['test-project'].schematics = { - '@schematics/angular:component': { prefix: 'pre' } - }; - })) - .then(() => ng('generate', 'component', 'test-component')) - .then(() => expectFileToMatch(join(testCompDir, 'test-component.component.ts'), - /selector: 'pre-/)) - .then(() => ng('g', 'c', 'alias')) - .then(() => expectFileToMatch(join(aliasCompDir, 'alias.component.ts'), - /selector: 'pre-/)) + return ( + Promise.resolve() + .then(() => + updateJsonFile('angular.json', (configJson) => { + configJson.projects['test-project'].schematics = { + '@schematics/angular:component': { prefix: 'pre' }, + }; + }), + ) + .then(() => ng('generate', 'component', 'test-component')) + .then(() => + expectFileToMatch(join(testCompDir, 'test-component.component.ts'), /selector: 'pre-/), + ) + .then(() => ng('g', 'c', 'alias')) + .then(() => expectFileToMatch(join(aliasCompDir, 'alias.component.ts'), /selector: 'pre-/)) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/directive/directive-basic.ts b/tests/legacy-cli/e2e/tests/generate/directive/directive-basic.ts index b76308f26da3..31cbbe2225eb 100644 --- a/tests/legacy-cli/e2e/tests/generate/directive/directive-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/directive/directive-basic.ts @@ -1,14 +1,15 @@ -import {ng} from '../../../utils/process'; -import {join} from 'path'; -import {expectFileToExist} from '../../../utils/fs'; +import { ng } from '../../../utils/process'; +import { join } from 'path'; +import { expectFileToExist } from '../../../utils/fs'; - -export default function() { +export default function () { const directiveDir = join('src', 'app'); - return ng('generate', 'directive', 'test-directive') - .then(() => expectFileToExist(join(directiveDir, 'test-directive.directive.ts'))) - .then(() => expectFileToExist(join(directiveDir, 'test-directive.directive.spec.ts'))) + return ( + ng('generate', 'directive', 'test-directive') + .then(() => expectFileToExist(join(directiveDir, 'test-directive.directive.ts'))) + .then(() => expectFileToExist(join(directiveDir, 'test-directive.directive.spec.ts'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/directive/directive-module-export.ts b/tests/legacy-cli/e2e/tests/generate/directive/directive-module-export.ts index 376b5cb762f5..6a0c7f7a8aa6 100644 --- a/tests/legacy-cli/e2e/tests/generate/directive/directive-module-export.ts +++ b/tests/legacy-cli/e2e/tests/generate/directive/directive-module-export.ts @@ -1,14 +1,17 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const modulePath = join('src', 'app', 'app.module.ts'); - return ng('generate', 'directive', 'test-directive', '--export') - .then(() => expectFileToMatch(modulePath, /exports: \[\r?\n(\s*) TestDirectiveDirective\r?\n\1\]/)) + return ( + ng('generate', 'directive', 'test-directive', '--export') + .then(() => + expectFileToMatch(modulePath, /exports: \[\r?\n(\s*) TestDirectiveDirective\r?\n\1\]/), + ) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/directive/directive-module-fail.ts b/tests/legacy-cli/e2e/tests/generate/directive/directive-module-fail.ts index 05ff8d98f1c9..c32d8a28df97 100644 --- a/tests/legacy-cli/e2e/tests/generate/directive/directive-module-fail.ts +++ b/tests/legacy-cli/e2e/tests/generate/directive/directive-module-fail.ts @@ -1,8 +1,10 @@ -import {ng} from '../../../utils/process'; -import {expectToFail} from '../../../utils/utils'; +import { ng } from '../../../utils/process'; +import { expectToFail } from '../../../utils/utils'; -export default function() { - return Promise.resolve() - .then(() => expectToFail(() => - ng('generate', 'directive', 'test-directive', '--module', 'app.moduleXXX.ts'))); +export default function () { + return Promise.resolve().then(() => + expectToFail(() => + ng('generate', 'directive', 'test-directive', '--module', 'app.moduleXXX.ts'), + ), + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/directive/directive-module.ts b/tests/legacy-cli/e2e/tests/generate/directive/directive-module.ts index fb0e4f99c3db..25bc0f701faa 100644 --- a/tests/legacy-cli/e2e/tests/generate/directive/directive-module.ts +++ b/tests/legacy-cli/e2e/tests/generate/directive/directive-module.ts @@ -1,21 +1,30 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const modulePath = join('src', 'app', 'app.module.ts'); - return ng('generate', 'directive', 'test-directive', '--module', 'app.module.ts') - .then(() => expectFileToMatch(modulePath, - /import { TestDirectiveDirective } from '.\/test-directive.directive'/)) + return ( + ng('generate', 'directive', 'test-directive', '--module', 'app.module.ts') + .then(() => + expectFileToMatch( + modulePath, + /import { TestDirectiveDirective } from '.\/test-directive.directive'/, + ), + ) - .then(() => process.chdir(join('src', 'app'))) - .then(() => ng('generate', 'directive', 'test-directive2', '--module', 'app.module.ts')) - .then(() => process.chdir('../..')) - .then(() => expectFileToMatch(modulePath, - /import { TestDirective2Directive } from '.\/test-directive2.directive'/)) + .then(() => process.chdir(join('src', 'app'))) + .then(() => ng('generate', 'directive', 'test-directive2', '--module', 'app.module.ts')) + .then(() => process.chdir('../..')) + .then(() => + expectFileToMatch( + modulePath, + /import { TestDirective2Directive } from '.\/test-directive2.directive'/, + ), + ) - // Try to run the unit tests. - .then(() => ng('build', '--configuration=development')); + // Try to run the unit tests. + .then(() => ng('build', '--configuration=development')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/directive/directive-prefix.ts b/tests/legacy-cli/e2e/tests/generate/directive/directive-prefix.ts index 7667915c7af3..1d6c272fe9e2 100644 --- a/tests/legacy-cli/e2e/tests/generate/directive/directive-prefix.ts +++ b/tests/legacy-cli/e2e/tests/generate/directive/directive-prefix.ts @@ -1,40 +1,51 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; import { updateJsonFile, useCIChrome, useCIDefaults } from '../../../utils/project'; - -export default function() { +export default function () { const directiveDir = join('src', 'app'); - return Promise.resolve() - .then(() => updateJsonFile('angular.json', configJson => { - configJson.schematics = { - '@schematics/angular:directive': { prefix: 'preW' } - }; - })) - .then(() => ng('generate', 'directive', 'test2-directive')) - .then(() => expectFileToMatch(join(directiveDir, 'test2-directive.directive.ts'), - /selector: '\[preW/)) - .then(() => ng('generate', 'application', 'app-two', '--skip-install')) - .then(() => useCIDefaults('app-two')) - .then(() => useCIChrome('./projects/app-two')) - .then(() => updateJsonFile('angular.json', configJson => { - configJson.projects['test-project'].schematics = { - '@schematics/angular:directive': { prefix: 'preP' } - }; - })) - .then(() => process.chdir('projects/app-two')) - .then(() => ng('generate', 'directive', '--skip-import', 'test3-directive')) - .then(() => process.chdir('../..')) - .then(() => expectFileToMatch(join('projects', 'app-two', 'test3-directive.directive.ts'), - /selector: '\[preW/)) - .then(() => process.chdir('src/app')) - .then(() => ng('generate', 'directive', 'test-directive')) - .then(() => process.chdir('../..')) - .then(() => expectFileToMatch(join(directiveDir, 'test-directive.directive.ts'), - /selector: '\[preP/)) + return ( + Promise.resolve() + .then(() => + updateJsonFile('angular.json', (configJson) => { + configJson.schematics = { + '@schematics/angular:directive': { prefix: 'preW' }, + }; + }), + ) + .then(() => ng('generate', 'directive', 'test2-directive')) + .then(() => + expectFileToMatch(join(directiveDir, 'test2-directive.directive.ts'), /selector: '\[preW/), + ) + .then(() => ng('generate', 'application', 'app-two', '--skip-install')) + .then(() => useCIDefaults('app-two')) + .then(() => useCIChrome('app-two', './projects/app-two')) + .then(() => + updateJsonFile('angular.json', (configJson) => { + configJson.projects['test-project'].schematics = { + '@schematics/angular:directive': { prefix: 'preP' }, + }; + }), + ) + .then(() => process.chdir('projects/app-two')) + .then(() => ng('generate', 'directive', '--skip-import', 'test3-directive')) + .then(() => process.chdir('../..')) + .then(() => + expectFileToMatch( + join('projects', 'app-two', 'test3-directive.directive.ts'), + /selector: '\[preW/, + ), + ) + .then(() => process.chdir('src/app')) + .then(() => ng('generate', 'directive', 'test-directive')) + .then(() => process.chdir('../..')) + .then(() => + expectFileToMatch(join(directiveDir, 'test-directive.directive.ts'), /selector: '\[preP/), + ) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/generate-error.ts b/tests/legacy-cli/e2e/tests/generate/generate-error.ts index 08d85cc02b43..cee6d7998239 100644 --- a/tests/legacy-cli/e2e/tests/generate/generate-error.ts +++ b/tests/legacy-cli/e2e/tests/generate/generate-error.ts @@ -1,8 +1,9 @@ -import {ng} from '../../utils/process'; -import {deleteFile} from '../../utils/fs'; -import {expectToFail} from '../../utils/utils'; +import { ng } from '../../utils/process'; +import { deleteFile } from '../../utils/fs'; +import { expectToFail } from '../../utils/utils'; -export default function() { - return deleteFile('angular.json') - .then(() => expectToFail(() => ng('generate', 'class', 'hello'))); +export default function () { + return deleteFile('angular.json').then(() => + expectToFail(() => ng('generate', 'class', 'hello')), + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/generate-name-check.ts b/tests/legacy-cli/e2e/tests/generate/generate-name-check.ts index a42a5cd6ca9a..ad542ba3cb17 100644 --- a/tests/legacy-cli/e2e/tests/generate/generate-name-check.ts +++ b/tests/legacy-cli/e2e/tests/generate/generate-name-check.ts @@ -1,24 +1,27 @@ -import {join} from 'path'; -import {ng} from '../../utils/process'; -import {expectFileToExist} from '../../utils/fs'; -import {updateJsonFile} from '../../utils/project'; +import { join } from 'path'; +import { ng } from '../../utils/process'; +import { expectFileToExist } from '../../utils/fs'; +import { updateJsonFile } from '../../utils/project'; - -export default function() { +export default function () { const compDir = join('src', 'app', 'test-component'); - return Promise.resolve() - .then(() => updateJsonFile('package.json', configJson => { - delete configJson.name; - return configJson; - })) - .then(() => ng('generate', 'component', 'test-component')) - .then(() => expectFileToExist(compDir)) - .then(() => expectFileToExist(join(compDir, 'test-component.component.ts'))) - .then(() => expectFileToExist(join(compDir, 'test-component.component.spec.ts'))) - .then(() => expectFileToExist(join(compDir, 'test-component.component.html'))) - .then(() => expectFileToExist(join(compDir, 'test-component.component.css'))) + return ( + Promise.resolve() + .then(() => + updateJsonFile('package.json', (configJson) => { + delete configJson.name; + return configJson; + }), + ) + .then(() => ng('generate', 'component', 'test-component')) + .then(() => expectFileToExist(compDir)) + .then(() => expectFileToExist(join(compDir, 'test-component.component.ts'))) + .then(() => expectFileToExist(join(compDir, 'test-component.component.spec.ts'))) + .then(() => expectFileToExist(join(compDir, 'test-component.component.html'))) + .then(() => expectFileToExist(join(compDir, 'test-component.component.css'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/generate-name-error.ts b/tests/legacy-cli/e2e/tests/generate/generate-name-error.ts index 2fb0583ca514..02660e690a0b 100644 --- a/tests/legacy-cli/e2e/tests/generate/generate-name-error.ts +++ b/tests/legacy-cli/e2e/tests/generate/generate-name-error.ts @@ -1,9 +1,8 @@ -import {ng} from '../../utils/process'; -import {expectToFail} from '../../utils/utils'; +import { ng } from '../../utils/process'; +import { expectToFail } from '../../utils/utils'; - -export default function() { - return Promise.resolve() - .then(() => expectToFail(() => - ng('generate', 'component', '1my-component'))); +export default function () { + return Promise.resolve().then(() => + expectToFail(() => ng('generate', 'component', '1my-component')), + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/guard/guard-basic.ts b/tests/legacy-cli/e2e/tests/generate/guard/guard-basic.ts index 7148fb0b5bf5..737035952d7c 100644 --- a/tests/legacy-cli/e2e/tests/generate/guard/guard-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/guard/guard-basic.ts @@ -1,9 +1,8 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist, expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist, expectFileToMatch } from '../../../utils/fs'; - -export default async function() { +export default async function () { // Does not create a sub directory. const guardDir = join('src', 'app'); diff --git a/tests/legacy-cli/e2e/tests/generate/guard/guard-implements.ts b/tests/legacy-cli/e2e/tests/generate/guard/guard-implements.ts index 5dcf2a32d18c..7a16e02b8619 100644 --- a/tests/legacy-cli/e2e/tests/generate/guard/guard-implements.ts +++ b/tests/legacy-cli/e2e/tests/generate/guard/guard-implements.ts @@ -1,9 +1,8 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist,expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist, expectFileToMatch } from '../../../utils/fs'; - -export default async function() { +export default async function () { // Does not create a sub directory. const guardDir = join('src', 'app'); diff --git a/tests/legacy-cli/e2e/tests/generate/guard/guard-multiple-implements.ts b/tests/legacy-cli/e2e/tests/generate/guard/guard-multiple-implements.ts index a91bfd0ddde5..3ec06be59ccc 100644 --- a/tests/legacy-cli/e2e/tests/generate/guard/guard-multiple-implements.ts +++ b/tests/legacy-cli/e2e/tests/generate/guard/guard-multiple-implements.ts @@ -1,16 +1,18 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist,expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist, expectFileToMatch } from '../../../utils/fs'; - -export default async function() { +export default async function () { // Does not create a sub directory. const guardDir = join('src', 'app'); await ng('generate', 'guard', 'load', '--implements=CanLoad', '--implements=CanDeactivate'); await expectFileToExist(guardDir); await expectFileToExist(join(guardDir, 'load.guard.ts')); - await expectFileToMatch(join(guardDir, 'load.guard.ts'), /implements CanLoad, CanDeactivate/); + await expectFileToMatch( + join(guardDir, 'load.guard.ts'), + /implements CanLoad, CanDeactivate/, + ); await expectFileToExist(join(guardDir, 'load.guard.spec.ts')); await ng('test', '--watch=false'); } diff --git a/tests/legacy-cli/e2e/tests/generate/help-output-no-duplicates.ts b/tests/legacy-cli/e2e/tests/generate/help-output-no-duplicates.ts new file mode 100644 index 000000000000..9a8cf8ca7fcc --- /dev/null +++ b/tests/legacy-cli/e2e/tests/generate/help-output-no-duplicates.ts @@ -0,0 +1,17 @@ +import { ng } from '../../utils/process'; + +export default async function () { + // Verify that there are no duplicate options + const { stdout } = await ng('generate', 'component', '--help'); + const firstIndex = stdout.indexOf('--prefix'); + + if (firstIndex < 0) { + console.log(stdout); + throw new Error('--prefix was not part of the help output.'); + } + + if (firstIndex !== stdout.lastIndexOf('--prefix')) { + console.log(stdout); + throw new Error('--prefix first and last index were different. Possible duplicate output!'); + } +} diff --git a/tests/legacy-cli/e2e/tests/generate/help-output.ts b/tests/legacy-cli/e2e/tests/generate/help-output.ts index 22b0e8a397e0..54c5b4772365 100644 --- a/tests/legacy-cli/e2e/tests/generate/help-output.ts +++ b/tests/legacy-cli/e2e/tests/generate/help-output.ts @@ -1,21 +1,22 @@ -import {join} from 'path'; -import {ng, ProcessOutput} from '../../utils/process'; -import {writeMultipleFiles, createDir} from '../../utils/fs'; +import { join } from 'path'; +import { ng, ProcessOutput } from '../../utils/process'; +import { writeMultipleFiles, createDir } from '../../utils/fs'; import { updateJsonFile } from '../../utils/project'; - -export default function() { +export default function () { // setup temp collection const genRoot = join('node_modules/fake-schematics/'); - return Promise.resolve() - .then(() => createDir(genRoot)) - .then(() => writeMultipleFiles({ - [join(genRoot, 'package.json')]: ` + return ( + Promise.resolve() + .then(() => createDir(genRoot)) + .then(() => + writeMultipleFiles({ + [join(genRoot, 'package.json')]: ` { "schematics": "./collection.json" }`, - [join(genRoot, 'collection.json')]: ` + [join(genRoot, 'collection.json')]: ` { "schematics": { "fake": { @@ -25,11 +26,12 @@ export default function() { }, } }`, - [join(genRoot, 'fake-schema.json')]: ` + [join(genRoot, 'fake-schema.json')]: ` { "$id": "FakeSchema", "title": "Fake Schema", "type": "object", + "required": ["a"], "properties": { "b": { "type": "string", @@ -59,47 +61,50 @@ export default function() { "type": "string", "description": "optB" } - }, - "required": [] + } }`, - [join(genRoot, 'fake.js')]: ` + [join(genRoot, 'fake.js')]: ` function def(options) { return (host, context) => { return host; }; } exports.default = def; - `}, - )) - .then(() => ng('generate', 'fake-schematics:fake', '--help')) - .then(({stdout}) => { - if (!/ng generate fake-schematics:fake
\[options\]/.test(stdout)) { - throw new Error('Help signature is wrong (1).'); - } - if (!/opt-a[\s\S]*opt-b[\s\S]*opt-c/.test(stdout)) { - throw new Error('Help signature options are incorrect.'); - } - }) - // set up default collection. - .then(() => updateJsonFile('angular.json', json => { - json.cli = json.cli || {} as any; - json.cli.defaultCollection = 'fake-schematics'; - })) - .then(() => ng('generate', 'fake', '--help')) - // verify same output - .then(({stdout}) => { - if (!/ng generate fake \[options\]/.test(stdout)) { - throw new Error('Help signature is wrong (2).'); - } - if (!/opt-a[\s\S]*opt-b[\s\S]*opt-c/.test(stdout)) { - throw new Error('Help signature options are incorrect.'); - } - }) + `, + }), + ) + .then(() => ng('generate', 'fake-schematics:fake', '--help')) + .then(({ stdout }) => { + if (!/ng generate fake-schematics:fake \[b\]/.test(stdout)) { + throw new Error('Help signature is wrong (1).'); + } + if (!/opt-a[\s\S]*opt-b[\s\S]*opt-c/.test(stdout)) { + throw new Error('Help signature options are incorrect.'); + } + }) + // set up default collection. + .then(() => + updateJsonFile('angular.json', (json) => { + json.cli = json.cli || ({} as any); + json.cli.schematicCollections = ['fake-schematics']; + }), + ) + .then(() => ng('generate', 'fake', '--help')) + // verify same output + .then(({ stdout }) => { + if (!/ng generate fake \[b\]/.test(stdout)) { + throw new Error('Help signature is wrong (2).'); + } + if (!/opt-a[\s\S]*opt-b[\s\S]*opt-c/.test(stdout)) { + throw new Error('Help signature options are incorrect.'); + } + }) - // should print all the available schematics in a collection - // when a collection has more than 1 schematic - .then(() => writeMultipleFiles({ - [join(genRoot, 'collection.json')]: ` + // should print all the available schematics in a collection + // when a collection has more than 1 schematic + .then(() => + writeMultipleFiles({ + [join(genRoot, 'collection.json')]: ` { "schematics": { "fake": { @@ -114,13 +119,13 @@ export default function() { }, } }`, - })) - .then(() => ng('generate', '--help')) - .then(({stdout}) => { - if (!/Collection \"fake-schematics\" \(default\):[\s\S]*fake[\s\S]*fake-two/.test(stdout)) { - throw new Error( - `Help result is wrong, it didn't contain all the schematics.`); - } - }); - + }), + ) + .then(() => ng('generate', '--help')) + .then(({ stdout }) => { + if (!/fake[\s\S]*fake-two/.test(stdout)) { + throw new Error(`Help result is wrong, it didn't contain all the schematics.`); + } + }) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/install-allow-scripts.ts b/tests/legacy-cli/e2e/tests/generate/install-allow-scripts.ts new file mode 100644 index 000000000000..f41a49935d80 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/generate/install-allow-scripts.ts @@ -0,0 +1,31 @@ +import { copyAssets } from '../../utils/assets'; +import { expectFileNotToExist, expectFileToExist, rimraf } from '../../utils/fs'; +import { ng } from '../../utils/process'; + +export default async function () { + // Copy test schematic into test project to ensure schematic dependencies are available + await copyAssets('schematic-allow-scripts', 'schematic-allow-scripts'); + + // By default should not run the postinstall from the added package.json in the schematic + await ng('generate', './schematic-allow-scripts:test'); + await expectFileToExist('install-test/package.json'); + await expectFileNotToExist('install-test/post-script-ran'); + + // Cleanup for next test case + await rimraf('install-test'); + + // Should run the postinstall if the allowScripts task option is enabled + // For testing purposes, this schematic exposes the task option via a schematic option + await ng('generate', './schematic-allow-scripts:test', '--allow-scripts'); + await expectFileToExist('install-test/package.json'); + await expectFileToExist('install-test/post-script-ran'); + + // Cleanup for next test case + await rimraf('install-test'); + + // Package manager configuration should take priority + // The `ignoreScripts` schematic option sets the value of the `ignore-scripts` option in a test project `.npmrc` + await ng('generate', './schematic-allow-scripts:test', '--allow-scripts', '--ignore-scripts'); + await expectFileToExist('install-test/package.json'); + await expectFileNotToExist('install-test/post-script-ran'); +} diff --git a/tests/legacy-cli/e2e/tests/generate/interceptor/interceptor-basic.ts b/tests/legacy-cli/e2e/tests/generate/interceptor/interceptor-basic.ts index 19e27eeed5dc..632e8e86367b 100755 --- a/tests/legacy-cli/e2e/tests/generate/interceptor/interceptor-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/interceptor/interceptor-basic.ts @@ -1,17 +1,18 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist } from '../../../utils/fs'; - -export default function() { +export default function () { // Does not create a sub directory. const interceptorDir = join('src', 'app'); - return ng('generate', 'interceptor', 'test-interceptor') - .then(() => expectFileToExist(interceptorDir)) - .then(() => expectFileToExist(join(interceptorDir, 'test-interceptor.interceptor.ts'))) - .then(() => expectFileToExist(join(interceptorDir, 'test-interceptor.interceptor.spec.ts'))) + return ( + ng('generate', 'interceptor', 'test-interceptor') + .then(() => expectFileToExist(interceptorDir)) + .then(() => expectFileToExist(join(interceptorDir, 'test-interceptor.interceptor.ts'))) + .then(() => expectFileToExist(join(interceptorDir, 'test-interceptor.interceptor.spec.ts'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/interface.ts b/tests/legacy-cli/e2e/tests/generate/interface.ts index e74f2570f4f1..2a28ca524617 100644 --- a/tests/legacy-cli/e2e/tests/generate/interface.ts +++ b/tests/legacy-cli/e2e/tests/generate/interface.ts @@ -1,15 +1,16 @@ -import {join} from 'path'; -import {ng} from '../../utils/process'; -import {expectFileToExist} from '../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../utils/process'; +import { expectFileToExist } from '../../utils/fs'; - -export default function() { +export default function () { const interfaceDir = join('src', 'app'); - return ng('generate', 'interface', 'test-interface', 'model') - .then(() => expectFileToExist(interfaceDir)) - .then(() => expectFileToExist(join(interfaceDir, 'test-interface.model.ts'))) + return ( + ng('generate', 'interface', 'test-interface', 'model') + .then(() => expectFileToExist(interfaceDir)) + .then(() => expectFileToExist(join(interfaceDir, 'test-interface.model.ts'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ivy-full.ts b/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ivy-full.ts deleted file mode 100644 index b523bbc67bb1..000000000000 --- a/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ivy-full.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { writeFile } from '../../../utils/fs'; -import { ng } from '../../../utils/process'; -import { updateJsonFile } from '../../../utils/project'; - -export default async function () { - await ng('generate', 'library', 'my-lib'); - - await writeFile('./src/app/app.module.ts', ` - import { BrowserModule } from '@angular/platform-browser'; - import { NgModule } from '@angular/core'; - import { MyLibModule } from 'my-lib'; - - import { AppComponent } from './app.component'; - - @NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - MyLibModule, - ], - providers: [], - bootstrap: [AppComponent] - }) - export class AppModule { } - `); - - await writeFile('./src/app/app.component.ts', ` - import { Component } from '@angular/core'; - import { MyLibService } from 'my-lib'; - - @Component({ - selector: 'app-root', - template: '' - }) - export class AppComponent { - title = 'test-project'; - - constructor(myLibService: MyLibService) { - console.log(myLibService); - } - } - `); - - await writeFile('e2e/src/app.e2e-spec.ts', ` - import { browser, logging, element, by } from 'protractor'; - import { AppPage } from './app.po'; - - describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display text from library component', async () => { - await page.navigateTo(); - expect(await element(by.css('lib-my-lib p')).getText()).toEqual('my-lib works!'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - })); - }); - }); - `); - - // Build library in full mode (development) - await ng('build', 'my-lib', '--configuration=development'); - - // AOT linking - await runTests(); - - // JIT linking - await updateJsonFile('angular.json', config => { - const build = config.projects['test-project'].architect.build; - build.options.aot = false; - build.configurations.production.buildOptimizer = false; - }); - - await runTests(); -} - -async function runTests(): Promise { - // Check that the tests succeeds both with named project, unnamed (should test app), and prod. - await ng('e2e'); - await ng('e2e', 'test-project', '--devServerTarget=test-project:serve:production'); -} diff --git a/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ivy-partial.ts b/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ivy-partial.ts deleted file mode 100644 index 076ceb65cd80..000000000000 --- a/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ivy-partial.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { writeFile } from '../../../utils/fs'; -import { ng } from '../../../utils/process'; -import { updateJsonFile } from '../../../utils/project'; - -export default async function () { - await ng('generate', 'library', 'my-lib'); - - await writeFile('./src/app/app.module.ts', ` - import { BrowserModule } from '@angular/platform-browser'; - import { NgModule } from '@angular/core'; - import { MyLibModule } from 'my-lib'; - - import { AppComponent } from './app.component'; - - @NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - MyLibModule, - ], - providers: [], - bootstrap: [AppComponent] - }) - export class AppModule { } - `); - - await writeFile('./src/app/app.component.ts', ` - import { Component } from '@angular/core'; - import { MyLibService } from 'my-lib'; - - @Component({ - selector: 'app-root', - template: '' - }) - export class AppComponent { - title = 'test-project'; - - constructor(myLibService: MyLibService) { - console.log(myLibService); - } - } - `); - - await writeFile('e2e/src/app.e2e-spec.ts', ` - import { browser, logging, element, by } from 'protractor'; - import { AppPage } from './app.po'; - - describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display text from library component', async () => { - await page.navigateTo(); - expect(await element(by.css('lib-my-lib p')).getText()).toEqual('my-lib works!'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - })); - }); - }); - `); - - // Build library in partial mode (production) - await ng('build', 'my-lib', '--configuration=production'); - - // AOT linking - await runTests(); - - // JIT linking - await updateJsonFile('angular.json', config => { - const build = config.projects['test-project'].architect.build; - build.options.aot = false; - build.configurations.production.buildOptimizer = false; - }); - - await runTests(); -} - -async function runTests(): Promise { - // Check that the tests succeeds both with named project, unnamed (should test app), and prod. - await ng('e2e'); - await ng('e2e', 'test-project', '--devServerTarget=test-project:serve:production'); -} diff --git a/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ve.ts b/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ve.ts deleted file mode 100644 index 34c4114fdf8f..000000000000 --- a/tests/legacy-cli/e2e/tests/generate/library/library-consumption-ve.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { writeFile } from '../../../utils/fs'; -import { ng } from '../../../utils/process'; -import { updateJsonFile } from '../../../utils/project'; - -export default async function () { - await ng('generate', 'library', 'my-lib'); - - await updateJsonFile('projects/my-lib/tsconfig.lib.prod.json', config => { - const { angularCompilerOptions = {} } = config; - angularCompilerOptions.enableIvy = false; - angularCompilerOptions.skipTemplateCodegen = true; - angularCompilerOptions.strictMetadataEmit = true; - config.angularCompilerOptions = angularCompilerOptions; - }); - - await writeFile('./src/app/app.module.ts', ` - import { BrowserModule } from '@angular/platform-browser'; - import { NgModule } from '@angular/core'; - import { MyLibModule } from 'my-lib'; - - import { AppComponent } from './app.component'; - - @NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - MyLibModule, - ], - providers: [], - bootstrap: [AppComponent] - }) - export class AppModule { } - `); - - await writeFile('./src/app/app.component.ts', ` - import { Component } from '@angular/core'; - import { MyLibService } from 'my-lib'; - - @Component({ - selector: 'app-root', - template: '' - }) - export class AppComponent { - title = 'test-project'; - - constructor(myLibService: MyLibService) { - console.log(myLibService); - } - } - `); - - await writeFile('e2e/src/app.e2e-spec.ts', ` - import { browser, logging, element, by } from 'protractor'; - import { AppPage } from './app.po'; - - describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display text from library component', async () => { - await page.navigateTo(); - expect(await element(by.css('lib-my-lib p')).getText()).toEqual('my-lib works!'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - })); - }); - }); - `); - - // Build library in VE mode (production) - await ng('build', 'my-lib', '--configuration=production'); - - // AOT linking - await runTests(); - - // JIT linking - await updateJsonFile('angular.json', config => { - const build = config.projects['test-project'].architect.build; - build.options.aot = false; - build.configurations.production.buildOptimizer = false; - }); - - await runTests(); -} - -async function runTests(): Promise { - // Check that the tests succeeds both with named project, unnamed (should test app), and prod. - await ng('e2e'); - await ng('e2e', 'test-project', '--devServerTarget=test-project:serve:production'); -} diff --git a/tests/legacy-cli/e2e/tests/generate/module/module-basic.ts b/tests/legacy-cli/e2e/tests/generate/module/module-basic.ts index b78c3c22a112..2326f2f282f3 100644 --- a/tests/legacy-cli/e2e/tests/generate/module/module-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/module/module-basic.ts @@ -1,19 +1,20 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist, expectFileToMatch} from '../../../utils/fs'; -import {expectToFail} from '../../../utils/utils'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist, expectFileToMatch } from '../../../utils/fs'; +import { expectToFail } from '../../../utils/utils'; - -export default function() { +export default function () { const moduleDir = join('src', 'app', 'test'); - return ng('generate', 'module', 'test') - .then(() => expectFileToExist(moduleDir)) - .then(() => expectFileToExist(join(moduleDir, 'test.module.ts'))) - .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test-routing.module.ts')))) - .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test.spec.ts')))) - .then(() => expectFileToMatch(join(moduleDir, 'test.module.ts'), 'TestModule')) + return ( + ng('generate', 'module', 'test') + .then(() => expectFileToExist(moduleDir)) + .then(() => expectFileToExist(join(moduleDir, 'test.module.ts'))) + .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test-routing.module.ts')))) + .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test.spec.ts')))) + .then(() => expectFileToMatch(join(moduleDir, 'test.module.ts'), 'TestModule')) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/module/module-import.ts b/tests/legacy-cli/e2e/tests/generate/module/module-import.ts index 1e00993bc858..987d6d074257 100644 --- a/tests/legacy-cli/e2e/tests/generate/module/module-import.ts +++ b/tests/legacy-cli/e2e/tests/generate/module/module-import.ts @@ -13,42 +13,62 @@ export default function () { .then(() => ng('generate', 'module', 'sub/deep')) .then(() => ng('generate', 'module', 'test1', '--module', 'app.module.ts')) - .then(() => expectFileToMatch(modulePath, - /import { Test1Module } from '.\/test1\/test1.module'/)) + .then(() => + expectFileToMatch(modulePath, /import { Test1Module } from '.\/test1\/test1.module'/), + ) .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test1Module(.|\s)*\]/m)) .then(() => ng('generate', 'module', 'test2', '--module', 'app.module')) - .then(() => expectFileToMatch(modulePath, - /import { Test2Module } from '.\/test2\/test2.module'/)) + .then(() => + expectFileToMatch(modulePath, /import { Test2Module } from '.\/test2\/test2.module'/), + ) .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test2Module(.|\s)*\]/m)) .then(() => ng('generate', 'module', 'test3', '--module', 'app')) - .then(() => expectFileToMatch(modulePath, - /import { Test3Module } from '.\/test3\/test3.module'/)) + .then(() => + expectFileToMatch(modulePath, /import { Test3Module } from '.\/test3\/test3.module'/), + ) .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test3Module(.|\s)*\]/m)) .then(() => ng('generate', 'module', 'test4', '--routing', '--module', 'app')) .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test4Module(.|\s)*\]/m)) - .then(() => expectFileToMatch(join('src', 'app', 'test4', 'test4.module.ts'), - /import { Test4RoutingModule } from '.\/test4-routing.module'/)) - .then(() => expectFileToMatch(join('src', 'app', 'test4', 'test4.module.ts'), - /imports: \[(.|\s)*Test4RoutingModule(.|\s)*\]/m)) + .then(() => + expectFileToMatch( + join('src', 'app', 'test4', 'test4.module.ts'), + /import { Test4RoutingModule } from '.\/test4-routing.module'/, + ), + ) + .then(() => + expectFileToMatch( + join('src', 'app', 'test4', 'test4.module.ts'), + /imports: \[(.|\s)*Test4RoutingModule(.|\s)*\]/m, + ), + ) .then(() => ng('generate', 'module', 'test5', '--module', 'sub')) - .then(() => expectFileToMatch(subModulePath, - /import { Test5Module } from '..\/test5\/test5.module'/)) + .then(() => + expectFileToMatch(subModulePath, /import { Test5Module } from '..\/test5\/test5.module'/), + ) .then(() => expectFileToMatch(subModulePath, /imports: \[(.|\s)*Test5Module(.|\s)*\]/m)) - .then(() => ng('generate', 'module', 'test6', '--module', join('sub', 'deep')) - .then(() => expectFileToMatch(deepSubModulePath, - /import { Test6Module } from '..\/..\/test6\/test6.module'/)) - .then(() => expectFileToMatch(deepSubModulePath, /imports: \[(.|\s)*Test6Module(.|\s)*\]/m))); - - // E2E_DISABLE: temporarily disable pending investigation - // .then(() => process.chdir(join(root, 'src', 'app'))) - // .then(() => ng('generate', 'module', 'test7', '--module', 'app.module.ts')) - // .then(() => process.chdir('..')) - // .then(() => expectFileToMatch(modulePath, - // /import { Test7Module } from '.\/test7\/test7.module'/)) - // .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test7Module(.|\s)*\]/m)); + .then(() => + ng('generate', 'module', 'test6', '--module', join('sub', 'deep')) + .then(() => + expectFileToMatch( + deepSubModulePath, + /import { Test6Module } from '..\/..\/test6\/test6.module'/, + ), + ) + .then(() => + expectFileToMatch(deepSubModulePath, /imports: \[(.|\s)*Test6Module(.|\s)*\]/m), + ), + ); + + // E2E_DISABLE: temporarily disable pending investigation + // .then(() => process.chdir(join(root, 'src', 'app'))) + // .then(() => ng('generate', 'module', 'test7', '--module', 'app.module.ts')) + // .then(() => process.chdir('..')) + // .then(() => expectFileToMatch(modulePath, + // /import { Test7Module } from '.\/test7\/test7.module'/)) + // .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test7Module(.|\s)*\]/m)); } diff --git a/tests/legacy-cli/e2e/tests/generate/module/module-routing-child-folder.ts b/tests/legacy-cli/e2e/tests/generate/module/module-routing-child-folder.ts index 240588a8967d..0d7b431aa4c7 100644 --- a/tests/legacy-cli/e2e/tests/generate/module/module-routing-child-folder.ts +++ b/tests/legacy-cli/e2e/tests/generate/module/module-routing-child-folder.ts @@ -3,23 +3,21 @@ import { ng } from '../../../utils/process'; import { expectFileToExist } from '../../../utils/fs'; import { expectToFail } from '../../../utils/utils'; - export default function () { const root = process.cwd(); const testPath = join(root, 'src', 'app'); process.chdir(testPath); - return Promise.resolve() - .then(() => - ng('generate', 'module', 'sub-dir/child', '--routing') - .then(() => expectFileToExist(join(testPath, 'sub-dir/child'))) - .then(() => expectFileToExist(join(testPath, 'sub-dir/child', 'child.module.ts'))) - .then(() => expectFileToExist(join(testPath, 'sub-dir/child', 'child-routing.module.ts'))) - .then(() => expectToFail(() => - expectFileToExist(join(testPath, 'sub-dir/child', 'child.spec.ts')) - )) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')) - ); + return Promise.resolve().then(() => + ng('generate', 'module', 'sub-dir/child', '--routing') + .then(() => expectFileToExist(join(testPath, 'sub-dir/child'))) + .then(() => expectFileToExist(join(testPath, 'sub-dir/child', 'child.module.ts'))) + .then(() => expectFileToExist(join(testPath, 'sub-dir/child', 'child-routing.module.ts'))) + .then(() => + expectToFail(() => expectFileToExist(join(testPath, 'sub-dir/child', 'child.spec.ts'))), + ) + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')), + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/module/module-routing.ts b/tests/legacy-cli/e2e/tests/generate/module/module-routing.ts index cf6208b0a84c..dbeaf843ec51 100644 --- a/tests/legacy-cli/e2e/tests/generate/module/module-routing.ts +++ b/tests/legacy-cli/e2e/tests/generate/module/module-routing.ts @@ -1,17 +1,18 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; -import {expectToFail} from '../../../utils/utils'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist } from '../../../utils/fs'; +import { expectToFail } from '../../../utils/utils'; - -export default function() { +export default function () { const moduleDir = join('src', 'app', 'test'); - return ng('generate', 'module', 'test', '--routing') - .then(() => expectFileToExist(moduleDir)) - .then(() => expectFileToExist(join(moduleDir, 'test.module.ts'))) - .then(() => expectFileToExist(join(moduleDir, 'test-routing.module.ts'))) - .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test.spec.ts')))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + return ( + ng('generate', 'module', 'test', '--routing') + .then(() => expectFileToExist(moduleDir)) + .then(() => expectFileToExist(join(moduleDir, 'test.module.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-routing.module.ts'))) + .then(() => expectToFail(() => expectFileToExist(join(moduleDir, 'test.spec.ts')))) + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-basic.ts b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-basic.ts index 0480c13cd2ff..d752aa38b533 100644 --- a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-basic.ts @@ -1,17 +1,19 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; +import { expectFileToExist } from '../../../utils/fs'; -export default function() { +export default function () { // Create the pipe in the same directory. const pipeDir = join('src', 'app'); - return ng('generate', 'pipe', 'test-pipe') - .then(() => expectFileToExist(pipeDir)) - .then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.ts'))) - .then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.spec.ts'))) + return ( + ng('generate', 'pipe', 'test-pipe') + .then(() => expectFileToExist(pipeDir)) + .then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.ts'))) + .then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.spec.ts'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-export.ts b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-export.ts index 4773b2b5e231..7f6f1bda0c06 100644 --- a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-export.ts +++ b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-export.ts @@ -1,14 +1,15 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const modulePath = join('src', 'app', 'app.module.ts'); - return ng('generate', 'pipe', 'test-pipe', '--export') - .then(() => expectFileToMatch(modulePath, /exports: \[\r?\n(\s*) TestPipePipe\r?\n\1\]/)) + return ( + ng('generate', 'pipe', 'test-pipe', '--export') + .then(() => expectFileToMatch(modulePath, /exports: \[\r?\n(\s*) TestPipePipe\r?\n\1\]/)) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-fail.ts b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-fail.ts index e18d5e73d038..14939a8002a5 100644 --- a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-fail.ts +++ b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-fail.ts @@ -1,9 +1,8 @@ -import {ng} from '../../../utils/process'; -import {expectToFail} from '../../../utils/utils'; +import { ng } from '../../../utils/process'; +import { expectToFail } from '../../../utils/utils'; - -export default function() { - return Promise.resolve() - .then(() => expectToFail(() => - ng('generate', 'pipe', 'test-pipe', '--module', 'app.moduleXXX.ts'))); +export default function () { + return Promise.resolve().then(() => + expectToFail(() => ng('generate', 'pipe', 'test-pipe', '--module', 'app.moduleXXX.ts')), + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module.ts b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module.ts index 274e0079d16f..79d8c542d8a2 100644 --- a/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module.ts +++ b/tests/legacy-cli/e2e/tests/generate/pipe/pipe-module.ts @@ -1,21 +1,22 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToMatch} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToMatch } from '../../../utils/fs'; - -export default function() { +export default function () { const modulePath = join('src', 'app', 'app.module.ts'); - return ng('generate', 'pipe', 'test-pipe', '--module', 'app.module.ts') - .then(() => expectFileToMatch(modulePath, - /import { TestPipePipe } from '.\/test-pipe.pipe'/)) + return ( + ng('generate', 'pipe', 'test-pipe', '--module', 'app.module.ts') + .then(() => expectFileToMatch(modulePath, /import { TestPipePipe } from '.\/test-pipe.pipe'/)) - .then(() => process.chdir(join('src', 'app'))) - .then(() => ng('generate', 'pipe', 'test-pipe2', '--module', 'app.module.ts')) - .then(() => process.chdir('../..')) - .then(() => expectFileToMatch(modulePath, - /import { TestPipe2Pipe } from '.\/test-pipe2.pipe'/)) + .then(() => process.chdir(join('src', 'app'))) + .then(() => ng('generate', 'pipe', 'test-pipe2', '--module', 'app.module.ts')) + .then(() => process.chdir('../..')) + .then(() => + expectFileToMatch(modulePath, /import { TestPipe2Pipe } from '.\/test-pipe2.pipe'/), + ) - // Try to run the unit tests. - .then(() => ng('build', '--configuration=development')); + // Try to run the unit tests. + .then(() => ng('build', '--configuration=development')) + ); } diff --git a/tests/legacy-cli/e2e/tests/generate/schematic-defaults.ts b/tests/legacy-cli/e2e/tests/generate/schematic-defaults.ts new file mode 100644 index 000000000000..7e015a0d6638 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/generate/schematic-defaults.ts @@ -0,0 +1,40 @@ +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + await updateJsonFile('angular.json', (config) => { + config.projects['test-project'].schematics = { + '@schematics/angular:component': { + style: 'scss', + }, + }; + }); + + // Generate component in application to verify that it's minimal + const { stdout } = await ng('generate', 'component', 'foo'); + if (!stdout.includes('foo.component.scss')) { + throw new Error('Expected "foo.component.scss" to exist.'); + } + + // Generate another project with different settings + await ng('generate', 'application', 'test-project-two', '--no-minimal'); + + await updateJsonFile('angular.json', (config) => { + config.projects['test-project-two'].schematics = { + '@schematics/angular:component': { + style: 'less', + }, + }; + }); + + const { stdout: stdout2 } = await ng( + 'generate', + 'component', + 'foo', + '--project', + 'test-project-two', + ); + if (!stdout2.includes('foo.component.less')) { + throw new Error('Expected "foo.component.less" to exist.'); + } +} diff --git a/tests/legacy-cli/e2e/tests/generate/schematics-collections.ts b/tests/legacy-cli/e2e/tests/generate/schematics-collections.ts new file mode 100644 index 000000000000..e7fd1847b81d --- /dev/null +++ b/tests/legacy-cli/e2e/tests/generate/schematics-collections.ts @@ -0,0 +1,95 @@ +import { join } from 'path'; +import { ng } from '../../utils/process'; +import { writeMultipleFiles, createDir, expectFileToExist } from '../../utils/fs'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + // setup temp collection + const genRoot = join('node_modules/fake-schematics/'); + const fakeComponentSchematicDesc = 'Fake component schematic'; + + await createDir(genRoot); + await writeMultipleFiles({ + [join(genRoot, 'package.json')]: JSON.stringify({ + 'schematics': './collection.json', + }), + [join(genRoot, 'collection.json')]: JSON.stringify({ + 'schematics': { + 'fake': { + 'description': 'Fake schematic', + 'schema': './fake-schema.json', + 'factory': './fake', + }, + 'component': { + 'description': fakeComponentSchematicDesc, + 'schema': './fake-schema.json', + 'factory': './fake-component', + }, + }, + }), + [join(genRoot, 'fake-schema.json')]: JSON.stringify({ + '$id': 'FakeSchema', + 'title': 'Fake Schema', + 'type': 'object', + }), + [join(genRoot, 'fake.js')]: ` + exports.default = function (options) { + return (host, context) => { + console.log('fake schematic run.'); + }; + } + `, + [join(genRoot, 'fake-component.js')]: ` + exports.default = function (options) { + return (host, context) => { + console.log('fake component schematic run.'); + }; + } + `, + }); + + await updateJsonFile('angular.json', (json) => { + json.cli ??= {}; + json.cli.schematicCollections = ['fake-schematics', '@schematics/angular']; + }); + + // should display schematics for all schematics + const { stdout: stdout1 } = await ng('generate', '--help'); + if (!stdout1.includes('ng generate component')) { + throw new Error(`Didn't show schematics of '@schematics/angular'.`); + } + + if (!stdout1.includes('ng generate fake')) { + throw new Error(`Didn't show schematics of 'fake-schematics'.`); + } + + // check registration order. Both schematics contain a component schematic verify that the first one wins. + if (!stdout1.includes(fakeComponentSchematicDesc)) { + throw new Error(`Didn't show fake component description.`); + } + + // Verify execution based on ordering + const { stdout: stdout2 } = await ng('generate', 'component'); + if (!stdout2.includes('fake component schematic run')) { + throw new Error(`stdout didn't contain 'fake component schematic run'.`); + } + + await updateJsonFile('angular.json', (json) => { + json.cli ??= {}; + json.cli.schematicCollections = ['@schematics/angular', 'fake-schematics']; + }); + + const { stdout: stdout3 } = await ng('generate', '--help'); + if (!stdout3.includes('ng generate component [name]')) { + throw new Error(`Didn't show component description from @schematics/angular.`); + } + if (stdout3.includes(fakeComponentSchematicDesc)) { + throw new Error(`Shown fake component description, when it shouldn't.`); + } + + // Verify execution based on ordering + const projectDir = join('src', 'app'); + const componentDir = join(projectDir, 'test-component'); + await ng('generate', 'component', 'test-component'); + await expectFileToExist(componentDir); +} diff --git a/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts b/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts index 3ebd58fc977c..1906f3ba82ca 100644 --- a/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/service/service-basic.ts @@ -1,17 +1,18 @@ -import {join} from 'path'; -import {ng} from '../../../utils/process'; -import {expectFileToExist} from '../../../utils/fs'; +import { join } from 'path'; +import { ng } from '../../../utils/process'; +import { expectFileToExist } from '../../../utils/fs'; - -export default function() { +export default function () { // Does not create a sub directory. const serviceDir = join('src', 'app'); - return ng('generate', 'service', 'test-service') - .then(() => expectFileToExist(serviceDir)) - .then(() => expectFileToExist(join(serviceDir, 'test-service.service.ts'))) - .then(() => expectFileToExist(join(serviceDir, 'test-service.service.spec.ts'))) + return ( + ng('generate', 'service', 'test-service') + .then(() => expectFileToExist(serviceDir)) + .then(() => expectFileToExist(join(serviceDir, 'test-service.service.ts'))) + .then(() => expectFileToExist(join(serviceDir, 'test-service.service.spec.ts'))) - // Try to run the unit tests. - .then(() => ng('test', '--watch=false')); + // Try to run the unit tests. + .then(() => ng('test', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/i18n/extract-ivy-disk-cache.ts b/tests/legacy-cli/e2e/tests/i18n/extract-ivy-disk-cache.ts new file mode 100644 index 000000000000..61a2f48e62f5 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/i18n/extract-ivy-disk-cache.ts @@ -0,0 +1,36 @@ +import { join } from 'path'; +import { getGlobalVariable } from '../../utils/env'; +import { expectFileToMatch, rimraf, writeFile } from '../../utils/fs'; +import { installPackage, uninstallPackage } from '../../utils/packages'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { readNgVersion } from '../../utils/version'; + +export default async function () { + // Enable disk cache + updateJsonFile('angular.json', (config) => { + config.cli ??= {}; + config.cli.cache = { environment: 'all' }; + }); + + // Setup an i18n enabled component + await ng('generate', 'component', 'i18n-test'); + await writeFile(join('src/app/i18n-test', 'i18n-test.component.html'), '

Hello world

'); + + // Install correct version + let localizeVersion = '@angular/localize@' + readNgVersion(); + if (getGlobalVariable('argv')['ng-snapshots']) { + localizeVersion = require('../../ng-snapshot/package.json').dependencies['@angular/localize']; + } + + await installPackage(localizeVersion); + + for (let i = 0; i < 2; i++) { + // Run the extraction twice and make sure the second time round works with cache. + await rimraf('messages.xlf'); + await ng('extract-i18n'); + await expectFileToMatch('messages.xlf', 'Hello world'); + } + + await uninstallPackage('@angular/localize'); +} diff --git a/tests/legacy-cli/e2e/tests/i18n/extract-ivy-libraries.ts b/tests/legacy-cli/e2e/tests/i18n/extract-ivy-libraries.ts index 442f64ea2d71..a4ffa601056d 100644 --- a/tests/legacy-cli/e2e/tests/i18n/extract-ivy-libraries.ts +++ b/tests/legacy-cli/e2e/tests/i18n/extract-ivy-libraries.ts @@ -4,7 +4,7 @@ import { installPackage, uninstallPackage } from '../../utils/packages'; import { ng } from '../../utils/process'; import { readNgVersion } from '../../utils/version'; -export default async function() { +export default async function () { // Setup a library await ng('generate', 'library', 'i18n-lib-test'); await replaceInFile( diff --git a/tests/legacy-cli/e2e/tests/i18n/extract-ivy.ts b/tests/legacy-cli/e2e/tests/i18n/extract-ivy.ts index a64eb2604e0f..0fba95b63245 100644 --- a/tests/legacy-cli/e2e/tests/i18n/extract-ivy.ts +++ b/tests/legacy-cli/e2e/tests/i18n/extract-ivy.ts @@ -1,23 +1,19 @@ import { join } from 'path'; import { getGlobalVariable } from '../../utils/env'; -import { writeFile } from '../../utils/fs'; +import { expectFileToMatch, writeFile } from '../../utils/fs'; import { installPackage, uninstallPackage } from '../../utils/packages'; import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; import { readNgVersion } from '../../utils/version'; -export default async function() { +export default async function () { // Setup an i18n enabled component await ng('generate', 'component', 'i18n-test'); - await writeFile( - join('src/app/i18n-test', 'i18n-test.component.html'), - '

Hello world

', - ); + await writeFile(join('src/app/i18n-test', 'i18n-test.component.html'), '

Hello world

'); // Should fail if `@angular/localize` is missing const { message: message1 } = await expectToFail(() => ng('extract-i18n')); - if (!message1.includes(`Ivy extraction requires the '@angular/localize' package.`)) { + if (!message1.includes(`i18n extraction requires the '@angular/localize' package.`)) { throw new Error('Expected localize package error message when missing'); } @@ -34,18 +30,7 @@ export default async function() { throw new Error('Expected no warnings to be shown'); } - // Disable Ivy - await updateJsonFile('tsconfig.json', config => { - const { angularCompilerOptions = {} } = config; - angularCompilerOptions.enableIvy = false; - config.angularCompilerOptions = angularCompilerOptions; - }); - - // Should show ivy disabled application warning with enableIvy false - const { stderr: message4 } = await ng('extract-i18n'); - if (!message4.includes(`Ivy extraction enabled but application is not Ivy enabled.`)) { - throw new Error('Expected ivy disabled application warning'); - } + await expectFileToMatch('messages.xlf', 'Hello world'); await uninstallPackage('@angular/localize'); } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell-service-worker.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell-service-worker.ts new file mode 100644 index 000000000000..3493148b6678 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell-service-worker.ts @@ -0,0 +1,90 @@ +import { getGlobalVariable } from '../../utils/env'; +import { appendToFile, createDir, expectFileToMatch, writeFile } from '../../utils/fs'; +import { installWorkspacePackages } from '../../utils/packages'; +import { silentNg } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { readNgVersion } from '../../utils/version'; + +const snapshots = require('../../ng-snapshot/package.json'); + +export default async function () { + const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; + + await updateJsonFile('package.json', (packageJson) => { + const dependencies = packageJson['dependencies']; + dependencies['@angular/localize'] = isSnapshotBuild + ? snapshots.dependencies['@angular/localize'] + : readNgVersion(); + }); + + await appendToFile('src/app/app.component.html', ''); + + // Add app-shell and service-worker + await silentNg('generate', 'app-shell'); + await silentNg('generate', 'service-worker'); + + if (isSnapshotBuild) { + await updateJsonFile('package.json', (packageJson) => { + const dependencies = packageJson['dependencies']; + dependencies['@angular/platform-server'] = snapshots.dependencies['@angular/platform-server']; + dependencies['@angular/service-worker'] = snapshots.dependencies['@angular/service-worker']; + dependencies['@angular/router'] = snapshots.dependencies['@angular/router']; + }); + } + + await installWorkspacePackages(); + + const browserBaseDir = 'dist/test-project/browser'; + + // Set configurations for each locale. + const langTranslations = [ + { lang: 'en-US', translation: 'Hello i18n!' }, + { lang: 'fr', translation: 'Bonjour i18n!' }, + ]; + + await updateJsonFile('angular.json', (workspaceJson) => { + const appProject = workspaceJson.projects['test-project']; + const appArchitect = appProject.architect; + const buildOptions = appArchitect['build'].options; + const serverOptions = appArchitect['server'].options; + + // Enable localization for all locales + buildOptions.localize = true; + buildOptions.outputHashing = 'none'; + serverOptions.localize = true; + serverOptions.outputHashing = 'none'; + + // Add locale definitions to the project + const i18n: Record = (appProject.i18n = { locales: {} }); + for (const { lang } of langTranslations) { + if (lang == 'en-US') { + i18n.sourceLocale = lang; + } else { + i18n.locales[lang] = `src/locale/messages.${lang}.xlf`; + } + } + }); + + await createDir('src/locale'); + + for (const { lang } of langTranslations) { + // dummy translation file. + await writeFile( + `src/locale/messages.${lang}.xlf`, + ` + + + + `, + ); + } + + // Build each locale and verify the SW output. + await silentNg('run', 'test-project:app-shell:development'); + for (const { lang } of langTranslations) { + await Promise.all([ + expectFileToMatch(`${browserBaseDir}/${lang}/ngsw.json`, `/${lang}/main.js`), + expectFileToMatch(`${browserBaseDir}/${lang}/ngsw.json`, `/${lang}/index.html`), + ]); + } +} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell.ts index c60aa1446d85..9dac69df0190 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-app-shell.ts @@ -2,7 +2,6 @@ import { getGlobalVariable } from '../../utils/env'; import { appendToFile, copyFile, - expectFileToExist, expectFileToMatch, replaceInFile, writeFile, @@ -15,13 +14,9 @@ import { readNgVersion } from '../../utils/version'; const snapshots = require('../../ng-snapshot/package.json'); export default async function () { - // TEMP: disable pending i18n updates - // TODO: when re-enabling, use setupI18nConfig and helpers like other i18n tests. - return; - const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; - await updateJsonFile('package.json', packageJson => { + await updateJsonFile('package.json', (packageJson) => { const dependencies = packageJson['dependencies']; dependencies['@angular/localize'] = isSnapshotBuild ? snapshots.dependencies['@angular/localize'] @@ -29,10 +24,10 @@ export default async function () { }); await appendToFile('src/app/app.component.html', ''); - await ng('generate', 'appShell', '--client-project', 'test-project'); + await ng('generate', 'app-shell', '--project', 'test-project'); if (isSnapshotBuild) { - await updateJsonFile('package.json', packageJson => { + await updateJsonFile('package.json', (packageJson) => { const dependencies = packageJson['dependencies']; dependencies['@angular/platform-server'] = snapshots.dependencies['@angular/platform-server']; dependencies['@angular/router'] = snapshots.dependencies['@angular/router']; @@ -49,7 +44,7 @@ export default async function () { { lang: 'fr', translation: 'Bonjour i18n!' }, ]; - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; const appArchitect = appProject.architect || appProject.targets; const buildOptions = appArchitect['build'].options; @@ -59,27 +54,12 @@ export default async function () { buildOptions.optimization = true; buildOptions.buildOptimizer = true; buildOptions.aot = true; - buildOptions.fileReplacements = [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ]; - - serverOptions.optimization = true; - serverOptions.fileReplacements = [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ]; // Enable localization for all locales buildOptions.localize = true; serverOptions.localize = true; // Add locale definitions to the project - // tslint:disable-next-line: no-any const i18n: Record = (appProject.i18n = { locales: {} }); for (const { lang } of langTranslations) { if (lang == 'en-US') { @@ -105,16 +85,12 @@ export default async function () { // Extract the translation messages and copy them for each language. await ng('extract-i18n', '--output-path=src/locale'); - await expectFileToExist('src/locale/messages.xlf'); await expectFileToMatch('src/locale/messages.xlf', `source-language="en-US"`); await expectFileToMatch('src/locale/messages.xlf', `An introduction header for this sample`); // Clean up app.component.html so that we can easily // find the translation text - await writeFile( - 'src/app/app.component.html', - '', - ); + await writeFile('src/app/app.component.html', ''); for (const { lang, translation } of langTranslations) { if (lang != 'en-US') { @@ -134,7 +110,6 @@ export default async function () { // Build each locale and verify the output. await ng('run', 'test-project:app-shell'); - for (const { lang, translation } of langTranslations) { await expectFileToMatch(`${browserBaseDir}/${lang}/index.html`, translation); } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref-absolute.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref-absolute.ts new file mode 100644 index 000000000000..a95bfda6f5bc --- /dev/null +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref-absolute.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { expectFileToMatch } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { externalServer, langTranslations, setupI18nConfig } from './setup'; + +const baseHrefs: { [l: string]: string } = { + 'en-US': '/en/', + fr: '/fr-FR/', + de: '', +}; + +export default async function () { + // Setup i18n tests and config. + await setupI18nConfig(); + + // Update angular.json + await updateJsonFile('angular.json', (workspaceJson) => { + const appProject = workspaceJson.projects['test-project']; + // tslint:disable-next-line: no-any + const i18n: Record = appProject.i18n; + + i18n.sourceLocale = { + baseHref: baseHrefs['en-US'], + }; + + i18n.locales['fr'] = { + translation: i18n.locales['fr'], + baseHref: baseHrefs['fr'], + }; + + i18n.locales['de'] = { + translation: i18n.locales['de'], + baseHref: baseHrefs['de'], + }; + }); + + // Test absolute base href. + await ng('build', '--base-href', 'http://www.domain.com/', '--configuration=development'); + for (const { lang, outputPath } of langTranslations) { + // Verify the HTML base HREF attribute is present + await expectFileToMatch( + `${outputPath}/index.html`, + `href="http://www.domain.com${baseHrefs[lang] || '/'}"`, + ); + } +} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref.ts index 6e4a581f0d12..e815f25dd022 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-basehref.ts @@ -1,27 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -import { externalServer, langTranslations, setupI18nConfig } from './setup'; - -const baseHrefs = { - 'en-US': '/en/', - fr: '/fr-FR/', - de: '', -}; +import { baseHrefs, externalServer, langTranslations, setupI18nConfig } from './setup'; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; // tslint:disable-next-line: no-any const i18n: Record = appProject.i18n; @@ -54,17 +49,18 @@ export default async function() { // Verify the HTML base HREF attribute is present await expectFileToMatch(`${outputPath}/index.html`, `href="https://github.com/$%7BbaseHrefs[lang]%20||'/'}"`); - // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); - // Execute Application E2E tests for a production build without dev server - const server = externalServer(outputPath, baseHrefs[lang] || '/'); + const { server, port, url } = await externalServer( + outputPath, + (baseHrefs[lang] as string) || '/', + ); try { await ng( 'e2e', + `--port=${port}`, `--configuration=${lang}`, - '--devServerTarget=', - `--baseUrl=http://localhost:4200${baseHrefs[lang] || '/'}`, + '--dev-server-target=', + `--base-url=${url}`, ); } finally { server.close(); @@ -72,7 +68,7 @@ export default async function() { } // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appArchitect = workspaceJson.projects['test-project'].architect; appArchitect['build'].options.baseHref = '/test/'; @@ -84,27 +80,21 @@ export default async function() { // Verify the HTML base HREF attribute is present await expectFileToMatch(`${outputPath}/index.html`, `href="https://github.com/test$%7BbaseHrefs[lang]%20||'/'}"`); - // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); - // Execute Application E2E tests for a production build without dev server - const server = externalServer(outputPath, '/test' + (baseHrefs[lang] || '/')); + const { server, port, url } = await externalServer( + outputPath, + '/test' + (baseHrefs[lang] || '/'), + ); try { await ng( 'e2e', + `--port=${port}`, `--configuration=${lang}`, - '--devServerTarget=', - `--baseUrl=http://localhost:4200/test${baseHrefs[lang] || '/'}`, + '--dev-server-target=', + `--base-url=${url}`, ); } finally { server.close(); } } - - // Test absolute base href. - await ng('build', '--base-href', 'http://www.domain.com/', '--configuration=development'); - for (const { lang, outputPath } of langTranslations) { - // Verify the HTML base HREF attribute is present - await expectFileToMatch(`${outputPath}/index.html`, `href="http://www.domain.com${baseHrefs[lang] || '/'}"`); - } } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-arb.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-arb.ts deleted file mode 100644 index 8461b5c1eec4..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-arb.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { executeTest } from './ivy-localize-dl-xliff2'; -import { setupI18nConfig } from './setup'; - -export default async function() { - // Setup i18n tests and config. - await setupI18nConfig('arb'); - - // Execute the tests - await executeTest(); -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-json.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-json.ts deleted file mode 100644 index 955ae0c70af7..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-json.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { executeTest } from './ivy-localize-dl-xliff2'; -import { setupI18nConfig } from './setup'; - -export default async function() { - // Setup i18n tests and config. - await setupI18nConfig('json'); - - // Execute the tests - await executeTest(); -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff1.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff1.ts deleted file mode 100644 index 33beb1cdd29b..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff1.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { executeTest } from './ivy-localize-dl-xliff2'; -import { setupI18nConfig } from './setup'; - -export default async function() { - // Setup i18n tests and config. - await setupI18nConfig('xlf'); - - // Execute the tests - await executeTest(); -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts deleted file mode 100644 index 87159d36a5f0..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xliff2.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { appendToFile, expectFileToMatch, replaceInFile } from '../../utils/fs'; -import { execAndWaitForOutputToMatch, killAllProcesses, ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; -import { baseDir, externalServer, langTranslations, setupI18nConfig } from './setup'; - -export default async function() { - // Setup i18n tests and config. - await setupI18nConfig('xlf2'); - - // Execute the tests - await executeTest(); -} - -export async function executeTest() { - // Ensure a DL build is used. - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - - await updateJsonFile('tsconfig.json', config => { - config.compilerOptions.target = 'es2017'; - if (!config.angularCompilerOptions) { - config.angularCompilerOptions = {}; - } - config.angularCompilerOptions.disableTypeScriptVersionCheck = true; - }); - - // Build each locale and verify the output. - await ng('build'); - for (const { lang, outputPath, translation } of langTranslations) { - await expectFileToMatch(`${outputPath}/main-es5.js`, translation.helloPartial); - await expectFileToMatch(`${outputPath}/main-es2017.js`, translation.helloPartial); - await expectToFail(() => expectFileToMatch(`${outputPath}/main-es5.js`, '$localize`')); - await expectToFail(() => expectFileToMatch(`${outputPath}/main-es2017.js`, '$localize`')); - - // Verify the locale ID is present - await expectFileToMatch(`${outputPath}/vendor-es5.js`, lang); - await expectFileToMatch(`${outputPath}/vendor-es2017.js`, lang); - - // Verify the HTML lang attribute is present - await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); - - // Verify the HTML base HREF attribute is present - await expectFileToMatch(`${outputPath}/index.html`, `href="https://github.com/$%7Blang%7D/"`); - - // Verify the locale data is registered using the global files - await expectFileToMatch(`${outputPath}/vendor-es5.js`, '.ng.common.locales'); - await expectFileToMatch(`${outputPath}/vendor-es2017.js`, '.ng.common.locales'); - - // Verify the locale data is browser compatible - await expectToFail(() => expectFileToMatch(`${outputPath}/vendor-es5.js`, /\bconst\b/)); - await expectFileToMatch(`${outputPath}/vendor-es2017.js`, /\bconst\b/); - - // Verify locale data comments are removed in production - await expectToFail(() => - expectFileToMatch(`${outputPath}/vendor-es5.js`, '// See angular/tools/gulp-tasks/cldr/extract.js'), - ); - await expectToFail(() => - expectFileToMatch(`${outputPath}/vendor-es2017.js`, '// See angular/tools/gulp-tasks/cldr/extract.js'), - ); - - // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); - - // Execute Application E2E tests for a production build without dev server - const server = externalServer(outputPath, `/${lang}/`); - try { - await ng( - 'e2e', - `--configuration=${lang}`, - '--devServerTarget=', - `--baseUrl=http://localhost:4200/${lang}/`, - ); - } finally { - server.close(); - } - } - - // Verify deprecated locale data registration is not present - await ng('build', '--configuration=fr', '--optimization=false', '--configuration=development'); - await expectToFail(() => expectFileToMatch(`${baseDir}/fr/main-es5.js`, 'registerLocaleData(')); - await expectToFail(() => - expectFileToMatch(`${baseDir}/fr/main-es2017.js`, 'registerLocaleData('), - ); - - // Verify missing translation behaviour. - await appendToFile('src/app/app.component.html', '

Other content

'); - await ng('build', '--i18n-missing-translation', 'ignore', '--configuration=development'); - await expectFileToMatch(`${baseDir}/fr/main-es5.js`, /Other content/); - await expectFileToMatch(`${baseDir}/fr/main-es2017.js`, /Other content/); - await expectToFail(() => ng('build', '--configuration=development')); - try { - await execAndWaitForOutputToMatch( - 'ng', - ['serve', '--configuration=fr', '--port=0'], - /No translation found for/, - ); - } finally { - killAllProcesses(); - } -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xmb.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xmb.ts deleted file mode 100644 index cfc116d43870..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl-xmb.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { executeTest } from './ivy-localize-dl-xliff2'; -import { setupI18nConfig } from './setup'; - -export default async function() { - // Setup i18n tests and config. - await setupI18nConfig('xmb'); - - // Execute the tests - await executeTest(); -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015-e2e.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015-e2e.ts new file mode 100644 index 000000000000..9a5943043ffd --- /dev/null +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015-e2e.ts @@ -0,0 +1,12 @@ +import { ng } from '../../utils/process'; +import { langTranslations, setupI18nConfig } from './setup'; + +export default async function () { + // Setup i18n tests and config. + await setupI18nConfig(); + + for (const { lang } of langTranslations) { + // Execute Application E2E tests with dev server + await ng('e2e', `--configuration=${lang}`, '--port=0'); + } +} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts index bbad96799cfb..3dbb1da3176d 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts @@ -1,38 +1,22 @@ -import { expectFileNotToExist, expectFileToMatch, readFile, writeFile } from '../../utils/fs'; +import { expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; import { externalServer, langTranslations, setupI18nConfig } from './setup'; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); - // Ensure a es2017 build is used. - await writeFile('.browserslistrc', 'Chrome 65'); - await updateJsonFile('tsconfig.json', config => { - config.compilerOptions.target = 'es2017'; - if (!config.angularCompilerOptions) { - config.angularCompilerOptions = {}; - } - config.angularCompilerOptions.disableTypeScriptVersionCheck = true; - }); + const { stderr } = await ng('build'); + if (/Locale data for .+ cannot be found/.test(stderr)) { + throw new Error( + `A warning for a locale not found was shown. This should not happen.\n\nSTDERR:\n${stderr}\n`, + ); + } - await ng('build', '--source-map'); for (const { lang, outputPath, translation } of langTranslations) { await expectFileToMatch(`${outputPath}/main.js`, translation.helloPartial); await expectToFail(() => expectFileToMatch(`${outputPath}/main.js`, '$localize`')); - await expectFileNotToExist(`${outputPath}/main-es5.js`); - - // Ensure sourcemap for modified file contains content - const mainSourceMap = JSON.parse(await readFile(`${outputPath}/main.js.map`)); - if ( - mainSourceMap.version !== 3 || - !Array.isArray(mainSourceMap.sources) || - typeof mainSourceMap.mappings !== 'string' - ) { - throw new Error('invalid localized sourcemap for main.js'); - } // Ensure locale is inlined (@angular/localize plugin inlines `$localize.locale` references) // The only reference in a new application is in @angular/core @@ -41,17 +25,15 @@ export default async function() { // Verify the HTML lang attribute is present await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); - // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); - // Execute Application E2E tests for a production build without dev server - const server = externalServer(outputPath, `/${lang}/`); + const { server, port, url } = await externalServer(outputPath, `/${lang}/`); try { await ng( 'e2e', + `--port=${port}`, `--configuration=${lang}`, - '--devServerTarget=', - `--baseUrl=http://localhost:4200/${lang}/`, + '--dev-server-target=', + `--base-url=${url}`, ); } finally { server.close(); diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts deleted file mode 100644 index 207e0c6a5e46..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expectFileNotToExist, expectFileToMatch, readFile } from '../../utils/fs'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; -import { externalServer, langTranslations, setupI18nConfig } from './setup'; - -export default async function() { - // Setup i18n tests and config. - await setupI18nConfig(); - - // Ensure a es5 build is used. - await updateJsonFile('tsconfig.json', config => { - config.compilerOptions.target = 'es5'; - if (!config.angularCompilerOptions) { - config.angularCompilerOptions = {}; - } - config.angularCompilerOptions.disableTypeScriptVersionCheck = true; - }); - - // Build each locale and verify the output. - await ng('build'); - for (const { lang, outputPath, translation } of langTranslations) { - await expectFileToMatch(`${outputPath}/main.js`, translation.helloPartial); - await expectToFail(() => expectFileToMatch(`${outputPath}/main.js`, '$localize`')); - await expectFileNotToExist(`${outputPath}/main-es2017.js`); - - // Ensure sourcemap for modified file contains content - const mainSourceMap = JSON.parse(await readFile(`${outputPath}/main.js.map`)); - if ( - mainSourceMap.version !== 3 || - !Array.isArray(mainSourceMap.sources) || - typeof mainSourceMap.mappings !== 'string' - ) { - throw new Error('invalid localized sourcemap for main.js'); - } - - // Ensure locale is inlined (@angular/localize plugin inlines `$localize.locale` references) - // The only reference in a new application is in @angular/core - await expectFileToMatch(`${outputPath}/vendor.js`, lang); - - // Verify the HTML lang attribute is present - await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); - - // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); - - // Execute Application E2E tests for a production build without dev server - const server = externalServer(outputPath, `/${lang}/`); - try { - await ng( - 'e2e', - `--configuration=${lang}`, - '--devServerTarget=', - `--baseUrl=http://localhost:4200/${lang}/`, - ); - } finally { - server.close(); - } - } -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-hashes.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-hashes.ts index 1f64ee4b6536..00d1dfcaa72c 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-hashes.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-hashes.ts @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import * as fs from 'fs'; import { appendToFile } from '../../utils/fs'; import { ng } from '../../utils/process'; @@ -12,7 +13,7 @@ import { langTranslations, setupI18nConfig } from './setup'; const OUTPUT_RE = /^(?(?:main|vendor|\d+))\.(?[a-z0-9]+)\.js$/i; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); @@ -22,7 +23,7 @@ export default async function() { for (const { lang, outputPath } of langTranslations) { for (const entry of fs.readdirSync(outputPath)) { const match = entry.match(OUTPUT_RE); - if (!match) { + if (!match?.groups) { continue; } @@ -43,7 +44,7 @@ export default async function() { for (const { lang, outputPath } of langTranslations) { for (const entry of fs.readdirSync(outputPath)) { const match = entry.match(OUTPUT_RE); - if (!match) { + if (!match?.groups) { continue; } @@ -52,7 +53,7 @@ export default async function() { if (!hash) { throw new Error('Unexpected output entry: ' + id); } - if (hash === match.groups.hash) { + if (hash === match.groups!.hash) { throw new Error('Hash value did not change for entry: ' + id); } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data-augment.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data-augment.ts index 3c0d8b59a39f..8c27229fc4cf 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data-augment.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data-augment.ts @@ -1,14 +1,20 @@ -import { expectFileToMatch, prependToFile, readFile, replaceInFile, writeFile } from '../../utils/fs'; +import { + expectFileToMatch, + prependToFile, + readFile, + replaceInFile, + writeFile, +} from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -import { externalServer, langTranslations, setupI18nConfig } from './setup'; +import { langTranslations, setupI18nConfig } from './setup'; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); // Update angular.json to only localize one locale - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; appProject.architect['build'].options.localize = ['fr']; }); @@ -19,7 +25,7 @@ export default async function() { // Augment the locale data and import into the main application file const localeData = await readFile('node_modules/@angular/common/locales/global/fr.js'); await writeFile('src/fr-changed.js', localeData.replace('janvier', 'changed-janvier')); - await prependToFile('src/main.ts', 'import \'./fr-changed.js\';\n'); + await prependToFile('src/main.ts', "import './fr-changed.js';\n"); // Run a build and test await ng('build'); @@ -35,18 +41,5 @@ export default async function() { // Execute Application E2E tests with dev server await ng('e2e', `--configuration=${lang}`, '--port=0'); - - // Execute Application E2E tests for a production build without dev server - const server = externalServer(outputPath, `/${lang}/`); - try { - await ng( - 'e2e', - `--configuration=${lang}`, - '--devServerTarget=', - `--baseUrl=http://localhost:4200/${lang}/`, - ); - } finally { - server.close(); - } } } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data.ts index b0221af3c382..e599ae0ce3dc 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-locale-data.ts @@ -1,20 +1,21 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; import { setupI18nConfig } from './setup'; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; // tslint:disable-next-line: no-any const i18n: Record = appProject.i18n; @@ -24,12 +25,12 @@ export default async function() { }); const { stderr: err1 } = await ng('build'); - if (!err1.includes(`Locale data for 'fr-Abcd' cannot be found. Using locale data for 'fr'.`)) { + if (!err1.includes(`Locale data for 'fr-Abcd' cannot be found. Using locale data for 'fr'.`)) { throw new Error('locale data fallback warning not shown'); } // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; // tslint:disable-next-line: no-any const i18n: Record = appProject.i18n; @@ -39,14 +40,17 @@ export default async function() { }); const { stderr: err2 } = await ng('build'); - if (err2.includes(`Locale data for 'en-US' cannot be found. No locale data will be included for this locale.`)) { + if ( + err2.includes( + `Locale data for 'en-US' cannot be found. No locale data will be included for this locale.`, + ) + ) { throw new Error('locale data not found warning shown'); } // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; - // tslint:disable-next-line: no-any const i18n: Record = appProject.i18n; i18n.sourceLocale = 'en-x-abc'; @@ -54,7 +58,11 @@ export default async function() { }); const { stderr: err3 } = await ng('build', '--configuration=development'); - if (err3.includes(`Locale data for 'en-x-abc' cannot be found. No locale data will be included for this locale.`)) { + if ( + err3.includes( + `Locale data for 'en-x-abc' cannot be found. No locale data will be included for this locale.`, + ) + ) { throw new Error('locale data not found warning shown'); } } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-merging.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-merging.ts index 75f568b6108e..2c5d5b5a287f 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-merging.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-merging.ts @@ -1,28 +1,26 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; import { setupI18nConfig } from './setup'; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; - // tslint:disable-next-line: no-any const i18n: Record = appProject.i18n; - i18n.locales['fr'] = [ - i18n.locales['fr'], - i18n.locales['fr'], - ] + i18n.locales['fr'] = [i18n.locales['fr'], i18n.locales['fr']]; appProject.architect['build'].options.localize = ['fr']; }); @@ -31,5 +29,18 @@ export default async function() { throw new Error('duplicate translations warning not shown'); } + await updateJsonFile('angular.json', (workspaceJson) => { + const appProject = workspaceJson.projects['test-project']; + appProject.architect['build'].options.i18nDuplicateTranslation = 'error'; + }); + await expectToFail(() => ng('build')); + await updateJsonFile('angular.json', (workspaceJson) => { + const appProject = workspaceJson.projects['test-project']; + appProject.architect['build'].options.i18nDuplicateTranslation = 'ignore'; + }); + const { stderr: err2 } = await ng('build'); + if (err2.includes('Duplicate translations for message')) { + throw new Error('duplicate translations message not ignore'); + } } diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-server.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-server.ts deleted file mode 100644 index c47c96fb8ed8..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-server.ts +++ /dev/null @@ -1,128 +0,0 @@ -import * as express from 'express'; -import { join } from 'path'; -import { getGlobalVariable } from '../../utils/env'; -import { appendToFile, expectFileToMatch, writeFile } from '../../utils/fs'; -import { installWorkspacePackages } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; -import { langTranslations, setupI18nConfig } from './setup'; - -const snapshots = require('../../ng-snapshot/package.json'); - -export default async function () { - // TODO: Re-enable pending further Ivy/Universal/i18n work - return; - - // Setup i18n tests and config. - await setupI18nConfig(); - - // Add universal to the project - const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; - await ng('add', '@nguniversal/express-engine@9.0.0-next.6', '--skip-install'); - - if (isSnapshotBuild) { - await updateJsonFile('package.json', packageJson => { - const dependencies = packageJson['dependencies']; - dependencies['@angular/platform-server'] = snapshots.dependencies['@angular/platform-server']; - }); - } - - await installWorkspacePackages(); - - const serverbaseDir = 'dist/test-project/server'; - const serverBuildArgs = ['run', 'test-project:server']; - - // Add server-specific config. - await updateJsonFile('angular.json', workspaceJson => { - const appProject = workspaceJson.projects['test-project']; - const appArchitect = appProject.architect || appProject.targets; - const serverOptions = appArchitect['server'].options; - - serverOptions.optimization = true; - serverOptions.fileReplacements = [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ]; - - // Enable localization for all locales - // TODO: re-enable all locales once localeData support is added. - // serverOptions.localize = true; - serverOptions.localize = ['fr']; - // Always error on missing translations. - serverOptions.i18nMissingTranslation = 'error'; - }); - - // Override 'main.ts' so that we never bootstrap the client side - // This is needed so that we can we can run E2E test against the server view - await writeFile( - 'src/main.ts', - ` - import { enableProdMode } from '@angular/core'; - - import { AppModule } from './app/app.module'; - import { environment } from './environments/environment'; - - if (environment.production) { - enableProdMode(); - } - `, - ); - - // By default the 'server.ts' doesn't support localized dist folders, - // so we create a copy of 'app' function with a locale parameter. - await appendToFile( - 'server.ts', - ` - export function i18nApp(locale: string) { - const server = express(); - const distFolder = join(process.cwd(), \`dist/test-project/browser/\${locale}\`); - - server.engine('html', ngExpressEngine({ - bootstrap: AppServerModule, - })); - - server.set('view engine', 'html'); - server.set('views', distFolder); - - server.get('*.*', express.static(distFolder, { - maxAge: '1y' - })); - - server.get('*', (req, res) => { - res.render('index', { req }); - }); - - return server; - } - `, - ); - - // Build each locale and verify the output. - await ng('build'); - await ng(...serverBuildArgs); - - for (const { lang, translation } of langTranslations) { - await expectFileToMatch(`${serverbaseDir}/${lang}/main.js`, translation.helloPartial); - await expectToFail(() => expectFileToMatch(`${serverbaseDir}/${lang}/main.js`, '$localize`')); - - // Run the server - const serverBundle = join(process.cwd(), `${serverbaseDir}/${lang}/main.js`); - const { i18nApp } = await import(serverBundle) as { i18nApp(locale: string): express.Express }; - const server = i18nApp(lang).listen(4200, 'localhost'); - try { - // Execute without a devserver. - await ng('e2e', `--configuration=${lang}`, '--devServerTarget='); - } finally { - server.close(); - } - } - - // Verify missing translation behaviour. - await appendToFile('src/app/app.component.html', '

Other content

'); - await ng(...serverBuildArgs, '--i18n-missing-translation', 'ignore'); - await expectFileToMatch(`${serverbaseDir}/fr/main.js`, /Other content/); - await expectToFail(() => ng(...serverBuildArgs)); -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts deleted file mode 100644 index 2402d2ed90cc..000000000000 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-serviceworker.ts +++ /dev/null @@ -1,188 +0,0 @@ -import * as express from 'express'; -import { resolve } from 'path'; -import { getGlobalVariable } from '../../utils/env'; -import { - copyFile, - expectFileToExist, - expectFileToMatch, - replaceInFile, - writeFile, -} from '../../utils/fs'; -import { installPackage } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; -import { readNgVersion } from '../../utils/version'; - -export default async function() { - // TEMP: disable pending i18n updates - // TODO: when re-enabling, use setupI18nConfig and helpers like other i18n tests. - return; - - let localizeVersion = '@angular/localize@' + readNgVersion(); - if (getGlobalVariable('argv')['ng-snapshots']) { - localizeVersion = require('../../ng-snapshot/package.json').dependencies['@angular/localize']; - } - await installPackage(localizeVersion); - - let serviceWorkerVersion = '@angular/service-worker@' + readNgVersion(); - if (getGlobalVariable('argv')['ng-snapshots']) { - serviceWorkerVersion = require('../../ng-snapshot/package.json').dependencies[ - '@angular/service-worker' - ]; - } - await installPackage(serviceWorkerVersion); - - await updateJsonFile('tsconfig.json', config => { - config.compilerOptions.target = 'es2015'; - if (!config.angularCompilerOptions) { - config.angularCompilerOptions = {}; - } - config.angularCompilerOptions.disableTypeScriptVersionCheck = true; - }); - - const baseDir = 'dist/test-project'; - - // Set configurations for each locale. - const langTranslations = [ - { lang: 'en-US', translation: 'Hello i18n!' }, - { lang: 'fr', translation: 'Bonjour i18n!' }, - ]; - - await updateJsonFile('angular.json', workspaceJson => { - const appProject = workspaceJson.projects['test-project']; - const appArchitect = appProject.architect || appProject.targets; - const serveConfigs = appArchitect['serve'].configurations; - const e2eConfigs = appArchitect['e2e'].configurations; - - // Make default builds prod. - appArchitect['build'].options.optimization = true; - appArchitect['build'].options.buildOptimizer = true; - appArchitect['build'].options.aot = true; - appArchitect['build'].options.fileReplacements = [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ]; - - // Enable service worker - appArchitect['build'].options.serviceWorker = true; - - // Enable localization for all locales - // appArchitect['build'].options.localize = true; - - // Add locale definitions to the project - // tslint:disable-next-line: no-any - const i18n: Record = (appProject.i18n = { locales: {} }); - for (const { lang } of langTranslations) { - if (lang == 'en-US') { - i18n.sourceLocale = lang; - } else { - i18n.locales[lang] = `src/locale/messages.${lang}.xlf`; - } - serveConfigs[lang] = { browserTarget: `test-project:build:${lang}` }; - e2eConfigs[lang] = { - specs: [`./src/app.${lang}.e2e-spec.ts`], - devServerTarget: `test-project:serve:${lang}`, - }; - } - }); - - // Add service worker source configuration - const manifest = { - index: '/index.html', - assetGroups: [ - { - name: 'app', - installMode: 'prefetch', - resources: { - files: ['/favicon.ico', '/index.html', '/manifest.webmanifest', '/*.css', '/*.js'], - }, - }, - { - name: 'assets', - installMode: 'lazy', - updateMode: 'prefetch', - resources: { - files: ['/assets/**', '/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)'], - }, - }, - ], - }; - await writeFile('ngsw-config.json', JSON.stringify(manifest)); - - // Add a translatable element. - await writeFile( - 'src/app/app.component.html', - '

Hello i18n!

', - ); - - // Extract the translation messages and copy them for each language. - await ng('extract-i18n', '--output-path=src/locale'); - await expectFileToExist('src/locale/messages.xlf'); - await expectFileToMatch('src/locale/messages.xlf', `source-language="en-US"`); - await expectFileToMatch('src/locale/messages.xlf', `An introduction header for this sample`); - - for (const { lang, translation } of langTranslations) { - if (lang != 'en-US') { - await copyFile('src/locale/messages.xlf', `src/locale/messages.${lang}.xlf`); - await replaceInFile( - `src/locale/messages.${lang}.xlf`, - 'source-language="en-US"', - `source-language="en-US" target-language="${lang}"`, - ); - await replaceInFile( - `src/locale/messages.${lang}.xlf`, - 'Hello i18n!', - `Hello i18n!\n${translation}`, - ); - } - } - - // Build each locale and verify the output. - await ng('build', '--i18n-missing-translation', 'error'); - for (const { lang, translation } of langTranslations) { - await expectFileToMatch(`${baseDir}/${lang}/main-es5.js`, translation); - await expectFileToMatch(`${baseDir}/${lang}/main-es2015.js`, translation); - await expectToFail(() => expectFileToMatch(`${baseDir}/${lang}/main-es5.js`, '$localize`')); - await expectToFail(() => expectFileToMatch(`${baseDir}/${lang}/main-es2015.js`, '$localize`')); - await expectFileToMatch(`${baseDir}/${lang}/main-es5.js`, lang); - await expectFileToMatch(`${baseDir}/${lang}/main-es2015.js`, lang); - - // Expect service worker configuration to be present - await expectFileToExist(`${baseDir}/${lang}/ngsw.json`); - - // Ivy i18n doesn't yet work with `ng serve` so we must use a separate server. - const app = express(); - app.use(express.static(resolve(baseDir, lang))); - const server = app.listen(4200, 'localhost'); - try { - // Add E2E test for locale - await writeFile( - 'e2e/src/app.e2e-spec.ts', - ` - import { browser, logging, element, by } from 'protractor'; - describe('workspace-project App', () => { - it('should display welcome message', () => { - browser.get(browser.baseUrl); - expect(element(by.css('h1')).getText()).toEqual('${translation}'); - }); - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - } as logging.Entry)); - }); - }); - `, - ); - - // Execute without a devserver. - await ng('e2e', '--devServerTarget='); - } finally { - server.close(); - } - } -} diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcelocale.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcelocale.ts index 2b3be750f672..4294eb88fb16 100644 --- a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcelocale.ts +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcelocale.ts @@ -1,21 +1,22 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + import { expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; import { langTranslations, setupI18nConfig } from './setup'; -export default async function() { +export default async function () { // Setup i18n tests and config. await setupI18nConfig(); // Update angular.json - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; // tslint:disable-next-line: no-any const i18n: Record = appProject.i18n; diff --git a/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcemaps.ts b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcemaps.ts new file mode 100644 index 000000000000..8f65ef450c0e --- /dev/null +++ b/tests/legacy-cli/e2e/tests/i18n/ivy-localize-sourcemaps.ts @@ -0,0 +1,22 @@ +import { readFile } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { langTranslations, setupI18nConfig } from './setup'; + +export default async function () { + // Setup i18n tests and config. + await setupI18nConfig(); + + await ng('build', '--source-map'); + + for (const { outputPath } of langTranslations) { + // Ensure sourcemap for modified file contains content + const mainSourceMap = JSON.parse(await readFile(`${outputPath}/main.js.map`)); + if ( + mainSourceMap.version !== 3 || + !Array.isArray(mainSourceMap.sources) || + typeof mainSourceMap.mappings !== 'string' + ) { + throw new Error('invalid localized sourcemap for main.js'); + } + } +} diff --git a/tests/legacy-cli/e2e/tests/i18n/setup.ts b/tests/legacy-cli/e2e/tests/i18n/setup.ts index efc957412df5..90e5f93e4c48 100644 --- a/tests/legacy-cli/e2e/tests/i18n/setup.ts +++ b/tests/legacy-cli/e2e/tests/i18n/setup.ts @@ -1,18 +1,20 @@ -import * as express from 'express'; -import { resolve } from 'path'; +import express from 'express'; +import { dirname, resolve } from 'path'; import { getGlobalVariable } from '../../utils/env'; -import { appendToFile, copyFile, expectFileToExist, expectFileToMatch, replaceInFile, writeFile } from '../../utils/fs'; +import { appendToFile, copyFile, createDir, replaceInFile, writeFile } from '../../utils/fs'; import { installPackage } from '../../utils/packages'; -import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; import { readNgVersion } from '../../utils/version'; +import { Server } from 'http'; +import { AddressInfo } from 'net'; // Configurations for each locale. +const translationFile = 'src/locale/messages.xlf'; export const baseDir = 'dist/test-project'; export const langTranslations = [ { - lang: 'en-US', outputPath: `${baseDir}/en-US`, + lang: 'en-US', + outputPath: `${baseDir}/en-US`, translation: { helloPartial: 'Hello', hello: 'Hello i18n!', @@ -21,7 +23,8 @@ export const langTranslations = [ }, }, { - lang: 'fr', outputPath: `${baseDir}/fr`, + lang: 'fr', + outputPath: `${baseDir}/fr`, translation: { helloPartial: 'Bonjour', hello: 'Bonjour i18n!', @@ -38,7 +41,8 @@ export const langTranslations = [ ], }, { - lang: 'de', outputPath: `${baseDir}/de`, + lang: 'de', + outputPath: `${baseDir}/de`, translation: { helloPartial: 'Hallo', hello: 'Hallo i18n!', @@ -57,55 +61,45 @@ export const langTranslations = [ ]; export const sourceLocale = langTranslations[0].lang; -export const externalServer = (outputPath: string, baseUrl = '/') => { +export interface ExternalServer { + readonly server: Server; + readonly port: number; + readonly url: string; +} + +/** + * Create an `express` `http.Server` listening on a random port. + * + * Call .close() on the server return value to close the server. + */ +export async function externalServer(outputPath: string, baseUrl = '/'): Promise { const app = express(); app.use(baseUrl, express.static(resolve(outputPath))); - // call .close() on the return value to close the server. - return app.listen(4200, 'localhost'); -}; + return new Promise((resolve) => { + const server = app.listen(0, 'localhost', () => { + const { port } = server.address() as AddressInfo; -export const formats = { - 'xlf': { - ext: 'xlf', - sourceCheck: 'source-language="en-US"', - replacements: [ - [/source/g, 'target'], - ], - }, - 'xlf2': { - ext: 'xlf', - sourceCheck: 'srcLang="en-US"', - replacements: [ - [/source/g, 'target'], - ], - }, - 'xmb': { - ext: 'xmb', - sourceCheck: '.*?<\/source>/g, ''], - ], - }, - 'json': { - ext: 'json', - sourceCheck: '"locale": "en-US"', - replacements: [ - ], - }, - 'arb': { - ext: 'arb', - sourceCheck: '"@@locale": "en-US"', - replacements: [ - ], - }, + resolve({ + server, + port, + url: `http://localhost:${port}${baseUrl}`, + }); + }); + }); +} + +export const baseHrefs: { [l: string]: string } = { + 'en-US': '/en/', + fr: '/fr-FR/', + de: '', }; -export async function setupI18nConfig(format: keyof typeof formats = 'xlf') { +export async function setupI18nConfig() { // Add component with i18n content, both translations and localeData (plural, dates). - await writeFile('src/app/app.component.ts', ` + await writeFile( + 'src/app/app.component.ts', + ` import { Component, Inject, LOCALE_ID } from '@angular/core'; @Component({ selector: 'app-root', @@ -117,24 +111,68 @@ export async function setupI18nConfig(format: keyof typeof formats = 'xlf') { jan = new Date(2000, 0, 1); minutes = 3; } - `); - await writeFile(`src/app/app.component.html`, ` + `, + ); + await writeFile( + `src/app/app.component.html`, + `

Hello {{ title }}!

{{ locale }}

{{ jan | date : 'LLLL' }}

Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}

- `); + `, + ); + + await createDir(dirname(translationFile)); + await writeFile( + translationFile, + ` + + + + + + Hello ! + + src/app/app.component.html + 2,3 + + An introduction header for this sample + + + Updated + + src/app/app.component.html + 5,6 + + + + {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago}} + + src/app/app.component.html + 5,6 + + + + + `, + ); // Add a dynamic import to ensure syntax is supported // ng serve support: https://github.com/angular/angular-cli/issues/16248 await writeFile('src/app/dynamic.ts', `export const abc = 5;`); - await appendToFile('src/app/app.component.ts', ` + await appendToFile( + 'src/app/app.component.ts', + ` (async () => { await import('./dynamic'); })(); - `); + `, + ); // Add e2e specs for each lang. for (const { lang, translation } of langTranslations) { - await writeFile(`./e2e/src/app.${lang}.e2e-spec.ts`, ` + await writeFile( + `./e2e/src/app.${lang}.e2e-spec.ts`, + ` import { browser, logging, element, by } from 'protractor'; describe('workspace-project App', () => { @@ -160,11 +198,12 @@ export async function setupI18nConfig(format: keyof typeof formats = 'xlf') { it('should display pluralized message', async () => expect(await getParagraph('plural')).toEqual('${translation.plural}')); }); - `); + `, + ); } // Update angular.json to build, serve, and test each locale. - await updateJsonFile('angular.json', workspaceJson => { + await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; const appArchitect = workspaceJson.projects['test-project'].architect; const buildConfigs = appArchitect['build'].configurations; @@ -177,10 +216,6 @@ export async function setupI18nConfig(format: keyof typeof formats = 'xlf') { appArchitect['build'].options.optimization = true; appArchitect['build'].options.buildOptimizer = true; appArchitect['build'].options.aot = true; - appArchitect['build'].options.fileReplacements = [{ - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }]; appArchitect['build'].options.i18nMissingTranslation = 'error'; appArchitect['build'].options.vendorChunk = true; appArchitect['build'].options.sourceMap = true; @@ -196,11 +231,10 @@ export async function setupI18nConfig(format: keyof typeof formats = 'xlf') { if (lang === sourceLocale) { i18n.sourceLocale = lang; } else { - i18n.locales[lang] = `src/locale/messages.${lang}.${formats[format].ext}`; + i18n.locales[lang] = `src/locale/messages.${lang}.xlf`; } buildConfigs[lang] = { localize: [lang] }; - serveConfigs[lang] = { browserTarget: `test-project:build:${lang}` }; e2eConfigs[lang] = { specs: [`./src/app.${lang}.e2e-spec.ts`], @@ -209,41 +243,29 @@ export async function setupI18nConfig(format: keyof typeof formats = 'xlf') { } }); -// Install the localize package if using ivy + // Install the localize package if using ivy let localizeVersion = '@angular/localize@' + readNgVersion(); if (getGlobalVariable('argv')['ng-snapshots']) { localizeVersion = require('../../ng-snapshot/package.json').dependencies['@angular/localize']; } - await installPackage(localizeVersion); - - // Extract the translation messages. - await ng( - 'extract-i18n', - '--output-path=src/locale', - `--format=${format}`, - ); - const translationFile = `src/locale/messages.${formats[format].ext}`; - await expectFileToExist(translationFile); - await expectFileToMatch(translationFile, formats[format].sourceCheck); - if (format !== 'json') { - await expectFileToMatch(translationFile, `An introduction header for this sample`); - } + await installPackage(localizeVersion); // Make translations for each language. for (const { lang, translationReplacements } of langTranslations) { if (lang != sourceLocale) { - await copyFile(translationFile, `src/locale/messages.${lang}.${formats[format].ext}`); - for (const replacements of translationReplacements) { + await copyFile(translationFile, `src/locale/messages.${lang}.xlf`); + for (const replacements of translationReplacements!) { await replaceInFile( - `src/locale/messages.${lang}.${formats[format].ext}`, + `src/locale/messages.${lang}.xlf`, new RegExp(replacements[0], 'g'), replacements[1] as string, ); } - for (const replacement of formats[format].replacements) { + + for (const replacement of [[/source/g, 'target']]) { await replaceInFile( - `src/locale/messages.${lang}.${formats[format].ext}`, + `src/locale/messages.${lang}.xlf`, new RegExp(replacement[0], 'g'), replacement[1] as string, ); diff --git a/tests/legacy-cli/e2e/tests/misc/ask-analytics-command.ts b/tests/legacy-cli/e2e/tests/misc/ask-analytics-command.ts deleted file mode 100644 index d571b38cb671..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/ask-analytics-command.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { execWithEnv, killAllProcesses, waitForAnyProcessOutputToMatch } from '../../utils/process'; -import { expectToFail } from '../../utils/utils'; - -export default async function() { - try { - // Execute a command with TTY force enabled - const execution = execWithEnv('ng', ['version'], { - ...process.env, - NG_FORCE_TTY: '1', - NG_CLI_ANALYTICS: 'ci', - }); - - // Check if the prompt is shown - await waitForAnyProcessOutputToMatch(/Would you like to share anonymous usage data/); - } finally { - killAllProcesses(); - } - - try { - // Execute a command with TTY force enabled - const execution = execWithEnv('ng', ['version'], { - ...process.env, - NG_FORCE_TTY: '1', - NG_CLI_ANALYTICS: 'false', - }); - - // Check if the prompt is shown - await expectToFail(() => - waitForAnyProcessOutputToMatch(/Would you like to share anonymous usage data/, 5), - ); - } finally { - killAllProcesses(); - } - - // Should not show a prompt when using update - try { - // Execute a command with TTY force enabled - const execution = execWithEnv('ng', ['update'], { - ...process.env, - NG_FORCE_TTY: '1', - NG_CLI_ANALYTICS: 'ci', - }); - - // Check if the prompt is shown - await expectToFail(() => - waitForAnyProcessOutputToMatch(/Would you like to share anonymous usage data/, 5), - ); - } finally { - killAllProcesses(); - } -} diff --git a/tests/legacy-cli/e2e/tests/misc/ask-analytics-install.ts b/tests/legacy-cli/e2e/tests/misc/ask-analytics-install.ts deleted file mode 100644 index 2731caf46a0f..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/ask-analytics-install.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createDir, rimraf } from '../../utils/fs'; -import { - execWithEnv, - killAllProcesses, - waitForAnyProcessOutputToMatch, -} from '../../utils/process'; - -export default async function() { - // Create a temporary directory to install the CLI - await createDir('../ask-analytics'); - const cwd = process.cwd(); - process.chdir('../ask-analytics'); - - try { - // Install the CLI with TTY force enabled - const execution = execWithEnv( - 'npm', - ['install', '@angular/cli'], - { ...process.env, 'NG_FORCE_TTY': '1' }, - ); - - // Check if the prompt is shown - await waitForAnyProcessOutputToMatch(/Would you like to share anonymous usage data/, 60000); - - } finally { - killAllProcesses(); - - // Cleanup - process.chdir(cwd); - await rimraf('../ask-analytics'); - } -} diff --git a/tests/legacy-cli/e2e/tests/misc/ask-missing-builder.ts b/tests/legacy-cli/e2e/tests/misc/ask-missing-builder.ts new file mode 100644 index 000000000000..ce4270cdbb47 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/ask-missing-builder.ts @@ -0,0 +1,24 @@ +import { execAndWaitForOutputToMatch, killAllProcesses } from '../../utils/process'; + +export default async function () { + // Execute a command with TTY force enabled and check that the prompt is shown. + await execAndWaitForOutputToMatch( + 'ng', + ['deploy'], + /Would you like to add a package with "deploy" capabilities/, + { + ...process.env, + NG_FORCE_TTY: '1', + NG_CLI_ANALYTICS: 'false', + }, + ); + + await killAllProcesses(); + + // Execute a command with TTY force enabled and check that the prompt is shown. + await execAndWaitForOutputToMatch('ng', ['lint'], /Would you like to add ESLint now/, { + ...process.env, + NG_FORCE_TTY: '1', + NG_CLI_ANALYTICS: 'false', + }); +} diff --git a/tests/legacy-cli/e2e/tests/misc/browsers.ts b/tests/legacy-cli/e2e/tests/misc/browsers.ts index fd221cca8d90..7f5638250f9f 100644 --- a/tests/legacy-cli/e2e/tests/misc/browsers.ts +++ b/tests/legacy-cli/e2e/tests/misc/browsers.ts @@ -1,32 +1,17 @@ -import * as express from 'express'; +import express from 'express'; import * as path from 'path'; import { copyProjectAsset } from '../../utils/assets'; -import { getGlobalVariable } from '../../utils/env'; import { replaceInFile } from '../../utils/fs'; import { ng } from '../../utils/process'; export default async function () { - if (!process.env['E2E_BROWSERS']) { - return; - } - // Ensure SauceLabs configuration if (!process.env['SAUCE_USERNAME'] || !process.env['SAUCE_ACCESS_KEY']) { throw new Error('SauceLabs is not configured.'); } - await replaceInFile( - '.browserslistrc', - 'not IE 11', - 'IE 11', - ); - // Workaround for https://github.com/angular/angular/issues/32192 - await replaceInFile( - 'src/app/app.component.html', - /class="material-icons"/g, - '', - ); + await replaceInFile('src/app/app.component.html', /class="material-icons"/g, ''); await ng('build'); @@ -58,8 +43,8 @@ export default async function () { await ng( 'e2e', 'test-project', - '--protractorConfig=e2e/protractor-saucelabs.conf.js', - '--devServerTarget=', + '--protractor-config=e2e/protractor-saucelabs.conf.js', + '--dev-server-target=', ); } finally { server.close(); diff --git a/tests/legacy-cli/e2e/tests/misc/check-postinstalls.ts b/tests/legacy-cli/e2e/tests/misc/check-postinstalls.ts new file mode 100644 index 000000000000..ecd605eb97c4 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/check-postinstalls.ts @@ -0,0 +1,66 @@ +import glob from 'glob'; +import { promisify } from 'util'; +import { readFile } from '../../utils/fs'; + +const globAsync = promisify(glob); + +const CURRENT_SCRIPT_PACKAGES: ReadonlySet = new Set([ + 'esbuild (postinstall)', + 'nice-napi (install)', +]); + +const POTENTIAL_SCRIPTS: ReadonlyArray = ['preinstall', 'install', 'postinstall']; + +// Some packages include test and/or example code that causes false positives +const FALSE_POSITIVE_PATHS: ReadonlySet = new Set([ + 'jasmine-spec-reporter/examples/protractor/package.json', + 'resolve/test/resolver/multirepo/package.json', +]); + +const INNER_NODE_MODULES_SEGMENT = '/node_modules/'; + +export default async function () { + const manifestPaths = await globAsync('node_modules/**/package.json'); + const newPackages: string[] = []; + + for (const manifestPath of manifestPaths) { + const lastNodeModuleIndex = manifestPath.lastIndexOf(INNER_NODE_MODULES_SEGMENT); + const packageRelativePath = manifestPath.slice( + lastNodeModuleIndex === -1 + ? INNER_NODE_MODULES_SEGMENT.length - 1 + : lastNodeModuleIndex + INNER_NODE_MODULES_SEGMENT.length, + ); + if (FALSE_POSITIVE_PATHS.has(packageRelativePath)) { + continue; + } + + let manifest; + try { + manifest = JSON.parse(await readFile(manifestPath)); + } catch { + continue; + } + + if (!manifest.scripts) { + continue; + } + + for (const script of POTENTIAL_SCRIPTS) { + if (!manifest.scripts[script]) { + continue; + } + + const packageScript = `${manifest.name} (${script})`; + + if (!CURRENT_SCRIPT_PACKAGES.has(packageScript)) { + newPackages.push(packageScript + `[${manifestPath}]`); + } + } + } + + if (newPackages.length) { + throw new Error( + 'New install script package(s) detected:\n' + JSON.stringify(newPackages, null, 2), + ); + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/circular-dependency.ts b/tests/legacy-cli/e2e/tests/misc/circular-dependency.ts deleted file mode 100644 index 0b1e4c8e49d2..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/circular-dependency.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { prependToFile } from '../../utils/fs'; -import { ng } from '../../utils/process'; - - -export default async function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - await prependToFile('src/app/app.component.ts', - `import { AppModule } from './app.module'; console.log(AppModule);`); - const { stderr } = await ng('build', '--show-circular-dependencies', '--configuration=development'); - if (!stderr.match(/Warning: Circular dependency detected/)) { - throw new Error('Expected to have circular dependency warning in output.'); - } -} diff --git a/tests/legacy-cli/e2e/tests/misc/cli-exit-interop.ts b/tests/legacy-cli/e2e/tests/misc/cli-exit-interop.ts new file mode 100644 index 000000000000..ed0fd050b5e9 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/cli-exit-interop.ts @@ -0,0 +1,35 @@ +import { createProjectFromAsset } from '../../utils/assets'; +import { moveFile, replaceInFile } from '../../utils/fs'; +import { noSilentNg } from '../../utils/process'; +import { useCIChrome, useCIDefaults } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; + +/** + * @fileoverview This tests that using the latest version of the CLI globally does not cause older (< 14) + * versions of the CLI to never exit after completing certain commands. + * This test will timeout in a failure condition. + */ + +export default async function () { + let restoreRegistry: (() => Promise) | undefined; + + try { + // We need to use the public registry because in the local NPM server we don't have + // older versions @angular/cli packages which would cause `npm install` during `ng update` to fail. + restoreRegistry = await createProjectFromAsset('13.0-project', true); + + // A missing stylesheet error will trigger the stuck process issue with v13 when building + await moveFile('src/styles.css', 'src/styles.scss'); + await expectToFail(() => noSilentNg('build')); + + // Setup a SCSS global stylesheet + // Simulates issue https://github.com/angular/angular-cli/issues/23289 + await replaceInFile('angular.json', /styles\.css/g, 'styles.scss'); + + await useCIChrome('thirteen-project'); + await useCIDefaults('thirteen-project'); + await noSilentNg('test', '--watch=false'); + } finally { + await restoreRegistry?.(); + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/common-async.ts b/tests/legacy-cli/e2e/tests/misc/common-async.ts deleted file mode 100644 index 90cf4a0227e3..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/common-async.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {readdirSync} from 'fs'; -import {oneLine} from 'common-tags'; -import { installPackage } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import {appendToFile, expectFileToExist, prependToFile, replaceInFile} from '../../utils/fs'; -import {expectToFail} from '../../utils/utils'; - - -export default function() { - // TODO(architect): The common chunk seems to have a different name in devkit/build-angular. - // Investigate, validate, then delete this test. - return; - - let oldNumberOfFiles = 0; - return Promise.resolve() - .then(() => ng('build')) - .then(() => oldNumberOfFiles = readdirSync('dist/test-project').length) - .then(() => ng('generate', 'module', 'lazyA', '--routing')) - .then(() => ng('generate', 'module', 'lazyB', '--routing')) - .then(() => prependToFile('src/app/app.module.ts', ` - import { RouterModule } from '@angular/router'; - `)) - .then(() => replaceInFile('src/app/app.module.ts', 'imports: [', `imports: [ - RouterModule.forRoot([{ path: "lazyA", loadChildren: "./lazy-a/lazy-a.module#LazyAModule" }]), - RouterModule.forRoot([{ path: "lazyB", loadChildren: "./lazy-b/lazy-b.module#LazyBModule" }]), - `)) - .then(() => ng('build')) - .then(() => readdirSync('dist').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles >= currentNumberOfDistFiles) { - throw new Error('A bundle for the lazy module was not created.'); - } - oldNumberOfFiles = currentNumberOfDistFiles; - }) - .then(() => installPackage('moment')) - .then(() => appendToFile('src/app/lazy-a/lazy-a.module.ts', ` - import * as moment from 'moment'; - console.log(moment); - `)) - .then(() => ng('build')) - .then(() => readdirSync('dist/test-project').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles != currentNumberOfDistFiles) { - throw new Error('The build contains a different number of files.'); - } - }) - .then(() => appendToFile('src/app/lazy-b/lazy-b.module.ts', ` - import * as moment from 'moment'; - console.log(moment); - `)) - .then(() => ng('build')) - .then(() => expectFileToExist('dist/test-project/common.chunk.js')) - .then(() => readdirSync('dist/test-project').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles >= currentNumberOfDistFiles) { - throw new Error(oneLine`The build contains the wrong number of files. - The test for 'dist/test-project/common.chunk.js' to exist should have failed.`); - } - oldNumberOfFiles = currentNumberOfDistFiles; - }) - .then(() => ng('build', '--no-common-chunk')) - .then(() => expectToFail(() => expectFileToExist('dist/test-project/common.chunk.js'))) - .then(() => readdirSync('dist/test-project').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles <= currentNumberOfDistFiles) { - throw new Error(oneLine`The build contains the wrong number of files. - The test for 'dist/test-project/common.chunk.js' not to exist should have failed.`); - } - }) - // Check for AoT and lazy routes. - .then(() => ng('build', '--aot')) - .then(() => readdirSync('dist/test-project').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles != currentNumberOfDistFiles) { - throw new Error('AoT build contains a different number of files.'); - } - }); -} diff --git a/tests/legacy-cli/e2e/tests/misc/coverage.ts b/tests/legacy-cli/e2e/tests/misc/coverage.ts deleted file mode 100644 index af42921fbf76..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/coverage.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {expectFileToExist, expectFileToMatch} from '../../utils/fs'; -import {updateJsonFile} from '../../utils/project'; -import {expectToFail} from '../../utils/utils'; -import {ng} from '../../utils/process'; - - -export default function () { - // TODO(architect): This test is broken in devkit/build-angular, istanbul and - // istanbul-instrumenter-loader are missing from the dependencies. - return; - - return ng('test', '--watch=false', '--code-coverage') - .then(output => expect(output.stdout).toContain('Coverage summary')) - .then(() => expectFileToExist('coverage/src/app')) - .then(() => expectFileToExist('coverage/lcov.info')) - // Verify code coverage exclude work - .then(() => expectFileToMatch('coverage/lcov.info', 'polyfills.ts')) - .then(() => expectFileToMatch('coverage/lcov.info', 'test.ts')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.test.options.codeCoverageExclude = [ - 'src/polyfills.ts', - '**/test.ts', - ]; - })) - .then(() => ng('test', '--watch=false', '--code-coverage')) - .then(() => expectToFail(() => expectFileToMatch('coverage/lcov.info', 'polyfills.ts'))) - .then(() => expectToFail(() => expectFileToMatch('coverage/lcov.info', 'test.ts'))); -} diff --git a/tests/legacy-cli/e2e/tests/misc/create-angular.ts b/tests/legacy-cli/e2e/tests/misc/create-angular.ts new file mode 100644 index 000000000000..3e57d7cc193f --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/create-angular.ts @@ -0,0 +1,42 @@ +import { join, resolve } from 'path'; +import { expectFileToExist, readFile, rimraf } from '../../utils/fs'; +import { getActivePackageManager } from '../../utils/packages'; +import { silentNpm, silentYarn } from '../../utils/process'; + +export default async function () { + const currentDirectory = process.cwd(); + const newDirectory = resolve('../'); + + const projectName = 'test-project-create'; + + try { + process.chdir(newDirectory); + const packageManager = getActivePackageManager(); + + switch (packageManager) { + case 'npm': + await silentNpm('init', '@angular', projectName, '--', '--skip-install', '--style=scss'); + + break; + case 'yarn': + await silentYarn('create', '@angular', projectName, '--skip-install', '--style=scss'); + + break; + default: + throw new Error(`This test is not configured to use ${packageManager}.`); + } + + // Check that package manager has been configured based on the package manager used to invoke the create command. + const workspace = JSON.parse(await readFile(join(projectName, 'angular.json'))); + if (workspace.cli?.packageManager !== packageManager) { + throw new Error(`Expected 'packageManager' option to be configured to ${packageManager}.`); + } + + // Verify styles was create with correct extension. + await expectFileToExist(join(projectName, 'src/styles.scss')); + } finally { + await rimraf(projectName); + // Change directory back + process.chdir(currentDirectory); + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/dedupe-duplicate-modules.ts b/tests/legacy-cli/e2e/tests/misc/dedupe-duplicate-modules.ts index 5d4cb01ac606..f8a7927662e6 100644 --- a/tests/legacy-cli/e2e/tests/misc/dedupe-duplicate-modules.ts +++ b/tests/legacy-cli/e2e/tests/misc/dedupe-duplicate-modules.ts @@ -6,7 +6,7 @@ import { expectToFail } from '../../utils/utils'; export default async function () { // Force duplicate modules - await updateJsonFile('package.json', json => { + await updateJsonFile('package.json', (json) => { json.dependencies = { ...json.dependencies, 'tslib': '2.0.0', @@ -17,7 +17,8 @@ export default async function () { await installWorkspacePackages(); - await writeFile('./src/main.ts', + await writeFile( + './src/main.ts', ` import { __assign as __assign_0 } from 'tslib'; import { __assign as __assign_1 } from 'tslib-1'; @@ -28,14 +29,23 @@ export default async function () { __assign_1, __assign_2, }) - `); + `, + ); - const { stderr } = await ng('build', '--verbose', '--no-vendor-chunk', '--no-progress', '--configuration=development'); + const { stderr } = await ng( + 'build', + '--verbose', + '--no-vendor-chunk', + '--no-progress', + '--configuration=development', + ); const outFile = 'dist/test-project/main.js'; if (/\[DedupeModuleResolvePlugin\]:.+tslib-1-copy.+ -> .+tslib-1.+/.test(stderr)) { await expectFileToMatch(outFile, './node_modules/tslib-1/tslib.es6.js'); - await expectToFail(() => expectFileToMatch(outFile, './node_modules/tslib-1-copy/tslib.es6.js')); + await expectToFail(() => + expectFileToMatch(outFile, './node_modules/tslib-1-copy/tslib.es6.js'), + ); } else if (/\[DedupeModuleResolvePlugin\]:.+tslib-1.+ -> .+tslib-1-copy.+/.test(stderr)) { await expectFileToMatch(outFile, './node_modules/tslib-1-copy/tslib.es6.js'); await expectToFail(() => expectFileToMatch(outFile, './node_modules/tslib-1/tslib.es6.js')); diff --git a/tests/legacy-cli/e2e/tests/misc/duplicate-command-line-option.ts b/tests/legacy-cli/e2e/tests/misc/duplicate-command-line-option.ts new file mode 100644 index 000000000000..a445e9051ade --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/duplicate-command-line-option.ts @@ -0,0 +1,19 @@ +import { ng } from '../../utils/process'; +import { expectFileToExist } from '../../utils/fs'; + +export default async function () { + const { stderr } = await ng( + 'generate', + 'component', + 'test-component', + '--style=scss', + '--style=sass', + ); + + const warningMatch = `Option 'style' has been specified multiple times. The value 'sass' will be used`; + if (!stderr.includes(warningMatch)) { + throw new Error(`Expected stderr to contain: "${warningMatch}".`); + } + + await expectFileToExist('src/app/test-component/test-component.component.sass'); +} diff --git a/tests/legacy-cli/e2e/tests/misc/e2e-host.ts b/tests/legacy-cli/e2e/tests/misc/e2e-host.ts index d5ae4de1a20c..398beb0599e5 100644 --- a/tests/legacy-cli/e2e/tests/misc/e2e-host.ts +++ b/tests/legacy-cli/e2e/tests/misc/e2e-host.ts @@ -1,9 +1,9 @@ import * as os from 'os'; -import { killAllProcesses, ng } from '../../utils/process'; +import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; export default async function () { - const interfaces = [].concat.apply([], Object.values(os.networkInterfaces())); + const interfaces = Object.values(os.networkInterfaces()).flat() as os.NetworkInterfaceInfo[]; let host = ''; for (const { family, address, internal } of interfaces) { if (family === 'IPv4' && !internal) { @@ -12,18 +12,13 @@ export default async function () { } } - try { - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.serve.options = appArchitect.serve.options || {}; - appArchitect.serve.options.port = 8888; - appArchitect.serve.options.host = host; - }); + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.serve.options = appArchitect.serve.options || {}; + appArchitect.serve.options.port = 8888; + appArchitect.serve.options.host = host; + }); - await ng('e2e'); - - await ng('e2e', '--host', host); - } finally { - await killAllProcesses(); - } + await ng('e2e'); + await ng('e2e', '--host', host); } diff --git a/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts b/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts index ef319e184249..3973636e07f5 100644 --- a/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts +++ b/tests/legacy-cli/e2e/tests/misc/es2015-nometa.ts @@ -1,17 +1,11 @@ -import { prependToFile, replaceInFile, writeFile } from '../../utils/fs'; +import { prependToFile, replaceInFile } from '../../utils/fs'; import { ng } from '../../utils/process'; -export default async function() { - // Ensure an ES2015 build is used in test - await writeFile('.browserslistrc', 'Chrome 65'); - +export default async function () { await ng('generate', 'service', 'user'); // Update the application to use the new service - await prependToFile( - 'src/app/app.component.ts', - 'import { UserService } from \'./user.service\';', - ); + await prependToFile('src/app/app.component.ts', "import { UserService } from './user.service';"); await replaceInFile( 'src/app/app.component.ts', diff --git a/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts b/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts deleted file mode 100644 index 417c2dd8e69a..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/es5-polyfills.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { oneLineTrim } from 'common-tags'; -import { expectFileNotToExist, expectFileToMatch, writeFile } from '../../utils/fs'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; - -export default async function () { - await updateJsonFile('tsconfig.json', configJson => { - const compilerOptions = configJson['compilerOptions']; - compilerOptions['target'] = 'es5'; - }); - - await writeFile('.browserslistrc', 'last 2 Chrome versions'); - await ng('build', '--configuration=development'); - await expectFileNotToExist('dist/test-project/polyfills-es5.js'); - await expectFileToMatch('dist/test-project/index.html', oneLineTrim` - - - - - `); - - await writeFile('.browserslistrc', 'IE 10'); - await ng('build', '--configuration=development'); - await expectFileToMatch('dist/test-project/polyfills-es5.js', 'core-js'); - await expectFileToMatch('dist/test-project/index.html', oneLineTrim` - - - - - - `); -} diff --git a/tests/legacy-cli/e2e/tests/misc/fallback.ts b/tests/legacy-cli/e2e/tests/misc/fallback.ts index 1c8d1ca56ea1..925d694a4800 100644 --- a/tests/legacy-cli/e2e/tests/misc/fallback.ts +++ b/tests/legacy-cli/e2e/tests/misc/fallback.ts @@ -1,35 +1,37 @@ -import { request } from '../../utils/http'; +import * as assert from 'assert'; +import fetch from 'node-fetch'; import { killAllProcesses } from '../../utils/process'; import { ngServe } from '../../utils/project'; import { updateJsonFile } from '../../utils/project'; import { moveFile } from '../../utils/fs'; - export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. // should fallback to config.app[0].index (index.html by default) - return Promise.resolve() - .then(() => ngServe()) - .then(() => request('http://localhost:4200/')) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - // should correctly fallback to a changed index - .then(() => moveFile('src/index.html', 'src/not-index.html')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.index = 'src/not-index.html'; - })) - .then(() => ngServe()) - .then(() => request('http://localhost:4200/')) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }); + return ( + Promise.resolve() + .then(() => ngServe()) + .then((port) => fetch(`http://localhost:${port}/`, { headers: { 'Accept': 'text/html' } })) + .then(async (response) => { + assert.strictEqual(response.status, 200); + assert.match(await response.text(), /<\/app-root>/); + }) + .finally(() => killAllProcesses()) + // should correctly fallback to a changed index + .then(() => moveFile('src/index.html', 'src/not-index.html')) + .then(() => + updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.index = 'src/not-index.html'; + }), + ) + .then(() => ngServe()) + .then((port) => fetch(`http://localhost:${port}/`, { headers: { 'Accept': 'text/html' } })) + .then(async (response) => { + assert.strictEqual(response.status, 200); + assert.match(await response.text(), /<\/app-root>/); + }) + .finally(() => killAllProcesses()) + ); } diff --git a/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts b/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts index 497f60aab160..cdf3eef6a313 100644 --- a/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts +++ b/tests/legacy-cli/e2e/tests/misc/forwardref-es2015.ts @@ -1,16 +1,13 @@ -import { appendToFile, replaceInFile, writeFile } from '../../utils/fs'; +import { appendToFile, replaceInFile } from '../../utils/fs'; import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; -export default async function() { - // Ensure an ES2015 build is used in test - await writeFile('.browserslistrc', 'Chrome 65'); - +export default async function () { // Update the application to use a forward reference await replaceInFile( 'src/app/app.component.ts', - 'import { Component } from \'@angular/core\';', - 'import { Component, Inject, Injectable, forwardRef } from \'@angular/core\';', + "import { Component } from '@angular/core';", + "import { Component, Inject, Injectable, forwardRef } from '@angular/core';", ); await appendToFile('src/app/app.component.ts', '\n@Injectable() export class Lock { }\n'); await replaceInFile( @@ -22,8 +19,8 @@ export default async function() { // Update the application's unit tests to include the new injectable await replaceInFile( 'src/app/app.component.spec.ts', - 'import { AppComponent } from \'./app.component\';', - 'import { AppComponent, Lock } from \'./app.component\';', + "import { AppComponent } from './app.component';", + "import { AppComponent, Lock } from './app.component';", ); await replaceInFile( 'src/app/app.component.spec.ts', diff --git a/tests/legacy-cli/e2e/tests/misc/http-headers.ts b/tests/legacy-cli/e2e/tests/misc/http-headers.ts new file mode 100644 index 000000000000..4f5ff6949621 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/http-headers.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + // This test ensures that ng e2e serves the HTTP headers that are configured + // in the 'headers' field of the serve options. We do this by serving the + // strictest possible CSP headers (default-src 'none') which blocks loading of + // any resources (including scripts, styles and images) and should cause ng + // e2e to fail with a CSP-related error, which is asserted below. + + await updateJsonFile('angular.json', (json) => { + const serve = json['projects']['test-project']['architect']['serve']; + if (!serve['options']) serve['options'] = {}; + serve['options']['headers'] = { + 'Content-Security-Policy': "default-src 'none'", + }; + }); + + let errorMessage: string | null = null; + try { + await ng('e2e'); + } catch (error) { + errorMessage = error instanceof Error ? error.message : null; + } + + if (!errorMessage) { + throw new Error( + 'Application loaded successfully, indicating that the CSP headers were not served.', + ); + } + if (!errorMessage.match(/Refused to load/)) { + throw new Error('Expected to see CSP loading failure in error logs.'); + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts b/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts index 4b8c05f04737..432f21167cdb 100644 --- a/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts +++ b/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts @@ -1,29 +1,14 @@ import { expectFileToMatch } from '../../utils/fs'; -import { ng, silentNpm } from '../../utils/process'; +import { execWithEnv, extractNpmEnv, ng, silentNpm } from '../../utils/process'; import { installPackage, uninstallPackage } from '../../utils/packages'; import { isPrereleaseCli } from '../../utils/project'; export default async function () { // Must publish old version to local registry to allow install. This is especially important // for release commits as npm will try to request tooling packages that are not on the npm registry yet - const { stdout: stdoutPack1 } = await silentNpm( - 'pack', - '@schematics/angular@7', - '--registry=https://registry.npmjs.org', - ); - await silentNpm('publish', stdoutPack1.trim(), '--tag=outdated'); - const { stdout: stdoutPack2 } = await silentNpm( - 'pack', - '@angular-devkit/core@7', - '--registry=https://registry.npmjs.org', - ); - await silentNpm('publish', stdoutPack2.trim(), '--tag=outdated'); - const { stdout: stdoutPack3 } = await silentNpm( - 'pack', - '@angular-devkit/schematics@7', - '--registry=https://registry.npmjs.org', - ); - await silentNpm('publish', stdoutPack3.trim(), '--tag=outdated'); + await publishOutdated('@schematics/angular@7'); + await publishOutdated('@angular-devkit/core@7'); + await publishOutdated('@angular-devkit/schematics@7'); // Install outdated and incompatible version await installPackage('@schematics/angular@7'); @@ -36,3 +21,17 @@ export default async function () { // Not doing so can cause adding material to fail if an incompatible cdk is present await uninstallPackage('@angular/cdk'); } + +async function publishOutdated(npmSpecifier: string): Promise { + const { stdout: stdoutPack } = await silentNpm( + 'pack', + npmSpecifier, + '--registry=https://registry.npmjs.org', + ); + await execWithEnv('npm', ['publish', stdoutPack.trim(), '--tag=outdated'], { + ...extractNpmEnv(), + // Also set an auth token value for the local test registry which is required by npm 7+ + // even though it is never actually used. + 'NPM_CONFIG__AUTH': 'e2e-testing', + }); +} diff --git a/tests/legacy-cli/e2e/tests/misc/karma-error-paths.ts b/tests/legacy-cli/e2e/tests/misc/karma-error-paths.ts index d8916ab6ea75..9a7a5daa2df8 100644 --- a/tests/legacy-cli/e2e/tests/misc/karma-error-paths.ts +++ b/tests/legacy-cli/e2e/tests/misc/karma-error-paths.ts @@ -19,6 +19,8 @@ export default async function () { } if (!message.includes('(src/app/app.component.spec.ts:4:25)')) { - throw new Error(`Expected logs to contain relative path to (src/app/app.component.spec.ts:4:25)\n${message}`); + throw new Error( + `Expected logs to contain relative path to (src/app/app.component.spec.ts:4:25)\n${message}`, + ); } } diff --git a/tests/legacy-cli/e2e/tests/misc/lazy-module.ts b/tests/legacy-cli/e2e/tests/misc/lazy-module.ts index 320f4206de05..366f93aa4b45 100644 --- a/tests/legacy-cli/e2e/tests/misc/lazy-module.ts +++ b/tests/legacy-cli/e2e/tests/misc/lazy-module.ts @@ -1,63 +1,83 @@ -import {readdirSync} from 'fs'; +import { readdirSync } from 'fs'; import { installPackage } from '../../utils/packages'; -import {ng} from '../../utils/process'; -import {appendToFile, writeFile, prependToFile, replaceInFile} from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { appendToFile, writeFile, prependToFile, replaceInFile } from '../../utils/fs'; - -export default function() { +export default function () { let oldNumberOfFiles = 0; - return Promise.resolve() - .then(() => ng('build', '--configuration=development')) - .then(() => oldNumberOfFiles = readdirSync('dist').length) - .then(() => ng('generate', 'module', 'lazy', '--routing')) - .then(() => ng('generate', 'module', 'too/lazy', '--routing')) - .then(() => prependToFile('src/app/app.module.ts', ` + return ( + Promise.resolve() + .then(() => ng('build', '--configuration=development')) + .then(() => (oldNumberOfFiles = readdirSync('dist').length)) + .then(() => ng('generate', 'module', 'lazy', '--routing')) + .then(() => ng('generate', 'module', 'too/lazy', '--routing')) + .then(() => + prependToFile( + 'src/app/app.module.ts', + ` import { RouterModule } from '@angular/router'; - `)) - .then(() => replaceInFile('src/app/app.module.ts', 'imports: [', `imports: [ + `, + ), + ) + .then(() => + replaceInFile( + 'src/app/app.module.ts', + 'imports: [', + `imports: [ RouterModule.forRoot([{ path: "lazy", loadChildren: () => import('src/app/lazy/lazy.module').then(m => m.LazyModule) }]), RouterModule.forRoot([{ path: "lazy1", loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) }]), RouterModule.forRoot([{ path: "lazy2", loadChildren: () => import('./too/lazy/lazy.module').then(m => m.LazyModule) }]), - `)) - .then(() => ng('build', '--named-chunks', '--configuration=development')) - .then(() => readdirSync('dist/test-project')) - .then((distFiles) => { - const currentNumberOfDistFiles = distFiles.length; - if (oldNumberOfFiles >= currentNumberOfDistFiles) { - throw new Error('A bundle for the lazy module was not created.'); - } - oldNumberOfFiles = currentNumberOfDistFiles; + `, + ), + ) + .then(() => ng('build', '--named-chunks', '--configuration=development')) + .then(() => readdirSync('dist/test-project')) + .then((distFiles) => { + const currentNumberOfDistFiles = distFiles.length; + if (oldNumberOfFiles >= currentNumberOfDistFiles) { + throw new Error('A bundle for the lazy module was not created.'); + } + oldNumberOfFiles = currentNumberOfDistFiles; - if (!distFiles.includes('src_app_too_lazy_lazy_module_ts.js')) { - throw new Error('The lazy module chunk did not use a unique name.'); - } - }) - // verify 'import *' syntax doesn't break lazy modules - .then(() => installPackage('moment')) - .then(() => appendToFile('src/app/app.component.ts', ` + if (!distFiles.includes('src_app_too_lazy_lazy_module_ts.js')) { + throw new Error('The lazy module chunk did not use a unique name.'); + } + }) + // verify 'import *' syntax doesn't break lazy modules + .then(() => installPackage('moment')) + .then(() => + appendToFile( + 'src/app/app.component.ts', + ` import * as moment from 'moment'; console.log(moment); - `)) - .then(() => ng('build', '--configuration=development')) - .then(() => readdirSync('dist/test-project').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles != currentNumberOfDistFiles) { - throw new Error('Bundles were not created after adding \'import *\'.'); - } - }) - .then(() => ng('build', '--no-named-chunks', '--configuration=development')) - .then(() => readdirSync('dist/test-project')) - .then((distFiles) => { - if (distFiles.includes('lazy-lazy-module.js') || distFiles.includes('too-lazy-lazy-module.js')) { - throw new Error('Lazy chunks shouldn\'t have a name but did.'); - } - }) - // Check for AoT and lazy routes. - .then(() => ng('build', '--aot', '--configuration=development')) - .then(() => readdirSync('dist/test-project').length) - .then(currentNumberOfDistFiles => { - if (oldNumberOfFiles != currentNumberOfDistFiles) { - throw new Error('AoT build contains a different number of files.'); - } - }); + `, + ), + ) + .then(() => ng('build', '--configuration=development')) + .then(() => readdirSync('dist/test-project').length) + .then((currentNumberOfDistFiles) => { + if (oldNumberOfFiles != currentNumberOfDistFiles) { + throw new Error("Bundles were not created after adding 'import *'."); + } + }) + .then(() => ng('build', '--no-named-chunks', '--configuration=development')) + .then(() => readdirSync('dist/test-project')) + .then((distFiles) => { + if ( + distFiles.includes('lazy-lazy-module.js') || + distFiles.includes('too-lazy-lazy-module.js') + ) { + throw new Error("Lazy chunks shouldn't have a name but did."); + } + }) + // Check for AoT and lazy routes. + .then(() => ng('build', '--aot', '--configuration=development')) + .then(() => readdirSync('dist/test-project').length) + .then((currentNumberOfDistFiles) => { + if (oldNumberOfFiles != currentNumberOfDistFiles) { + throw new Error('AoT build contains a different number of files.'); + } + }) + ); } diff --git a/tests/legacy-cli/e2e/tests/misc/loaders-resolution.ts b/tests/legacy-cli/e2e/tests/misc/loaders-resolution.ts index 7c011a1d7dba..b411ab60514a 100644 --- a/tests/legacy-cli/e2e/tests/misc/loaders-resolution.ts +++ b/tests/legacy-cli/e2e/tests/misc/loaders-resolution.ts @@ -1,18 +1,45 @@ import { createDir, moveFile } from '../../utils/fs'; import { ng } from '../../utils/process'; +import { assertIsError } from '../../utils/utils'; export default async function () { await createDir('node_modules/@angular-devkit/build-angular/node_modules'); - await moveFile( - 'node_modules/@ngtools', - 'node_modules/@angular-devkit/build-angular/node_modules/@ngtools' - ); + let originalInRootNodeModules = true; + + try { + await moveFile( + 'node_modules/@ngtools', + 'node_modules/@angular-devkit/build-angular/node_modules/@ngtools', + ); + } catch (e) { + assertIsError(e); + + if (e.code !== 'ENOENT') { + throw e; + } + + // In some cases due to module resolution '@ngtools' might already been under `@angular-devkit/build-angular`. + originalInRootNodeModules = false; + await moveFile( + 'node_modules/@angular-devkit/build-angular/node_modules/@ngtools', + 'node_modules/@ngtools', + ); + } await ng('build', '--configuration=development'); // Move it back. - await moveFile( - 'node_modules/@angular-devkit/build-angular/node_modules/@ngtools', - 'node_modules/@ngtools', - ); + await moveBack(originalInRootNodeModules); +} + +function moveBack(originalInRootNodeModules: Boolean): Promise { + return originalInRootNodeModules + ? moveFile( + 'node_modules/@angular-devkit/build-angular/node_modules/@ngtools', + 'node_modules/@ngtools', + ) + : moveFile( + 'node_modules/@ngtools', + 'node_modules/@angular-devkit/build-angular/node_modules/@ngtools', + ); } diff --git a/tests/legacy-cli/e2e/tests/misc/minimal-config.ts b/tests/legacy-cli/e2e/tests/misc/minimal-config.ts deleted file mode 100644 index a5ad29d11990..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/minimal-config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { writeFile, writeMultipleFiles } from '../../utils/fs'; -import { ng } from '../../utils/process'; - - -export default function () { - // TODO(architect): Figure out what a minimal config is for architect apps. - return; - - return Promise.resolve() - .then(() => writeFile('angular.json', JSON.stringify({ - apps: [{ - root: 'src', - main: 'main.ts', - scripts: [ - '../node_modules/core-js/client/shim.min.js', - '../node_modules/zone.js/dist/zone.js' - ] - }], - e2e: { protractor: { config: './protractor.conf.js' } } - }))) - .then(() => ng('e2e', 'test-project-e2e')) - .then(() => writeMultipleFiles({ - './src/script.js': ` - document.querySelector('app-root').innerHTML = '

app works!

'; - `, - './e2e/app.e2e-spec.ts': ` - import { browser, element, by } from 'protractor'; - - describe('minimal project App', function() { - it('should display message saying app works', () => { - browser.ignoreSynchronization = true; - browser.get('/'); - let el = element(by.css('app-root h1')).getText(); - expect(el).toEqual('app works!'); - }); - }); - `, - 'angular.json': JSON.stringify({ - apps: [{ - root: 'src', - scripts: ['./script.js'] - }], - e2e: { protractor: { config: './protractor.conf.js' } } - }), - })) - .then(() => ng('e2e', 'test-project-e2e')); -} diff --git a/tests/legacy-cli/e2e/tests/misc/module-resolution.ts b/tests/legacy-cli/e2e/tests/misc/module-resolution.ts deleted file mode 100644 index 5b812b6c7242..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/module-resolution.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { appendToFile, createDir, moveFile, prependToFile } from '../../utils/fs'; -import { installPackage } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; -import { expectToFail } from '../../utils/utils'; - - -export default async function () { - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '*': ['./node_modules/*'], - }; - }); - await ng('build', '--configuration=development'); - - await createDir('xyz'); - await moveFile( - 'node_modules/@angular/common', - 'xyz/common', - ); - - await expectToFail(() => ng('build', '--configuration=development')); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '@angular/common': [ './xyz/common' ], - }; - }); - await ng('build', '--configuration=development'); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '*': ['./node_modules/*'], - '@angular/common': [ './xyz/common' ], - }; - }); - await ng('build', '--configuration=development'); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '@angular/common': [ './xyz/common' ], - '*': ['./node_modules/*'], - }; - }); - await ng('build', '--configuration=development'); - - await updateJsonFile('tsconfig.json', tsconfig => { - delete tsconfig.compilerOptions.paths; - }); - - await prependToFile('src/app/app.module.ts', 'import * as firebase from \'firebase\';'); - await appendToFile('src/app/app.module.ts', 'firebase.initializeApp({});'); - - await installPackage('firebase@3.7.8'); - await ng('build', '--aot', '--configuration=development'); - await ng('test', '--watch=false'); - - await installPackage('firebase@4.9.0'); - await ng('build', '--aot', '--configuration=development'); - await ng('test', '--watch=false'); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = {}; - }); - await ng('build', '--configuration=development'); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '@app/*': ['*'], - '@lib/*/test': ['*/test'], - }; - }); - await ng('build', '--configuration=development'); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '@firebase/polyfill': ['./node_modules/@firebase/polyfill/index.ts'], - }; - }); - await expectToFail(() => ng('build', '--configuration=development')); - - await updateJsonFile('tsconfig.json', tsconfig => { - tsconfig.compilerOptions.paths = { - '@firebase/polyfill*': ['./node_modules/@firebase/polyfill/index.ts'], - }; - }); - await expectToFail(() => ng('build', '--configuration=development')); -} diff --git a/tests/legacy-cli/e2e/tests/misc/module-resolution/module-resolution-core-mapping.ts b/tests/legacy-cli/e2e/tests/misc/module-resolution/module-resolution-core-mapping.ts new file mode 100644 index 000000000000..2efae0ea5419 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/module-resolution/module-resolution-core-mapping.ts @@ -0,0 +1,41 @@ +import { createDir, moveFile } from '../../../utils/fs'; +import { ng } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; +import { expectToFail } from '../../../utils/utils'; + +export default async function () { + await updateJsonFile('tsconfig.json', (tsconfig) => { + tsconfig.compilerOptions.paths = { + '*': ['./node_modules/*'], + }; + }); + await ng('build', '--configuration=development'); + + await createDir('xyz'); + await moveFile('node_modules/@angular/common', 'xyz/common'); + await expectToFail(() => ng('build', '--configuration=development')); + + await updateJsonFile('tsconfig.json', (tsconfig) => { + tsconfig.compilerOptions.paths = { + '@angular/common': ['./xyz/common'], + }; + }); + await ng('build', '--configuration=development'); + + await updateJsonFile('tsconfig.json', (tsconfig) => { + tsconfig.compilerOptions.paths = { + '*': ['./node_modules/*'], + '@angular/common': ['./xyz/common'], + }; + }); + await ng('build', '--configuration=development'); + + await updateJsonFile('tsconfig.json', (tsconfig) => { + tsconfig.compilerOptions.paths = { + '@angular/common': ['./xyz/common'], + '*': ['./node_modules/*'], + }; + }); + await ng('build', '--configuration=development'); + await moveFile('xyz/common', 'node_modules/@angular/common'); +} diff --git a/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts b/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts index 5ac349343166..7ff7e32087e0 100644 --- a/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts +++ b/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts @@ -5,10 +5,6 @@ import { updateJsonFile } from '../../utils/project'; export default async function () { await ng('generate', 'app', 'secondary-app'); - await updateJsonFile('angular.json', workspaceJson => { - workspaceJson.defaultProject = undefined; - }); - await ng('build', 'secondary-app', '--configuration=development'); expectFileToExist('dist/secondary-app/index.html'); diff --git a/tests/legacy-cli/e2e/tests/misc/negated-boolean-options.ts b/tests/legacy-cli/e2e/tests/misc/negated-boolean-options.ts new file mode 100644 index 000000000000..377967785496 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/negated-boolean-options.ts @@ -0,0 +1,18 @@ +import { copyAssets } from '../../utils/assets'; +import { execAndWaitForOutputToMatch } from '../../utils/process'; + +export default async function () { + await copyAssets('schematic-boolean-option-negated', 'schematic-boolean-option-negated'); + + await execAndWaitForOutputToMatch( + 'ng', + ['generate', './schematic-boolean-option-negated:test', '--no-watch'], + /noWatch: true/, + ); + + await execAndWaitForOutputToMatch( + 'ng', + ['generate', './schematic-boolean-option-negated:test', '--watch'], + /noWatch: false/, + ); +} diff --git a/tests/legacy-cli/e2e/tests/misc/non-relative-module-resolution.ts b/tests/legacy-cli/e2e/tests/misc/non-relative-module-resolution.ts deleted file mode 100644 index 4d51c8bcae98..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/non-relative-module-resolution.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { prependToFile, writeMultipleFiles } from '../../utils/fs'; -import { ng } from '../../utils/process'; - - -export default async function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - await writeMultipleFiles({ - './src/app/foo.ts': ` - export const foo = 'fooo'; - `, - './src/app/bar.ts': ` - import { foo } from './foo'; - - console.log(foo); - ` - }), - - await prependToFile('src/app/app.module.ts', `import './bar';\n`); - - await ng('build', '--configuration=development'); -} diff --git a/tests/legacy-cli/e2e/tests/misc/npm-7.ts b/tests/legacy-cli/e2e/tests/misc/npm-7.ts index c2bff8ad9d34..deabdc21270a 100644 --- a/tests/legacy-cli/e2e/tests/misc/npm-7.ts +++ b/tests/legacy-cli/e2e/tests/misc/npm-7.ts @@ -1,11 +1,14 @@ -import { rimraf, writeFile } from '../../utils/fs'; +import * as assert from 'assert'; +import { valid as validSemVer } from 'semver'; +import { rimraf } from '../../utils/fs'; import { getActivePackageManager } from '../../utils/packages'; -import { ng, npm } from '../../utils/process'; +import { execWithEnv, ng, npm } from '../../utils/process'; +import { isPrereleaseCli } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; const warningText = 'npm version 7.5.6 or higher is recommended'; -export default async function() { +export default async function () { // Only relevant with npm as a package manager if (getActivePackageManager() !== 'npm') { return; @@ -16,13 +19,27 @@ export default async function() { return; } + // Get current package manager version to restore after tests + const initialVersionText = (await npm('--version')).stdout.trim(); + const initialVersion = validSemVer(initialVersionText); + assert.ok( + initialVersion, + `Invalid npm version string returned from "npm --version" [${initialVersionText}]`, + ); + const currentDirectory = process.cwd(); + + const extraArgs: string[] = []; + if (isPrereleaseCli()) { + extraArgs.push('--next'); + } + try { // Install version >=7.5.6 await npm('install', '--global', 'npm@>=7.5.6'); // Ensure `ng update` does not show npm warning - const { stderr: stderrUpdate1 } = await ng('update'); + const { stderr: stderrUpdate1 } = await ng('update', ...extraArgs); if (stderrUpdate1.includes(warningText)) { throw new Error('ng update expected to not show npm version warning.'); } @@ -31,13 +48,18 @@ export default async function() { await npm('install', '--global', 'npm@7.4.0'); // Ensure `ng add` shows npm warning - const { message: stderrAdd } = await expectToFail(() => ng('add')); + const { stderr: stderrAdd } = await execWithEnv( + 'ng', + ['add', '@angular/localize', '--skip-confirmation'], + { ...process.env, 'NPM_CONFIG_legacy_peer_deps': 'true' }, + ); + if (!stderrAdd.includes(warningText)) { throw new Error('ng add expected to show npm version warning.'); } // Ensure `ng update` shows npm warning - const { stderr: stderrUpdate2 } = await ng('update'); + const { stderr: stderrUpdate2 } = await ng('update', ...extraArgs); if (!stderrUpdate2.includes(warningText)) { throw new Error('ng update expected to show npm version warning.'); } @@ -82,8 +104,7 @@ export default async function() { // Change directory back process.chdir(currentDirectory); - // Reset version back to 6.x - await npm('install', '--global', 'npm@6'); + // Reset version back to initial version + await npm('install', '--global', `npm@${initialVersion}`); } - } diff --git a/tests/legacy-cli/e2e/tests/misc/npm-audit.ts b/tests/legacy-cli/e2e/tests/misc/npm-audit.ts deleted file mode 100644 index c96e17132e80..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/npm-audit.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { npm } from '../../utils/process'; - - -export default async function() { - try { - await npm('audit'); - } catch {} -} diff --git a/tests/legacy-cli/e2e/tests/misc/proxy-config.ts b/tests/legacy-cli/e2e/tests/misc/proxy-config.ts index ec5ce3d7101e..edc0619ad76c 100644 --- a/tests/legacy-cli/e2e/tests/misc/proxy-config.ts +++ b/tests/legacy-cli/e2e/tests/misc/proxy-config.ts @@ -1,14 +1,14 @@ -import * as express from 'express'; +import express from 'express'; import * as http from 'http'; -import {writeFile} from '../../utils/fs'; -import {request} from '../../utils/http'; -import {killAllProcesses, ng} from '../../utils/process'; -import {ngServe} from '../../utils/project'; -import {updateJsonFile} from '../../utils/project'; -import {expectToFail} from "../../utils/utils"; +import { writeFile } from '../../utils/fs'; +import fetch from 'node-fetch'; +import { killAllProcesses, ng } from '../../utils/process'; +import { ngServe } from '../../utils/project'; +import { AddressInfo } from 'net'; +import * as assert from 'assert'; -export default function() { +export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. // Create an express app that serves as a proxy. @@ -16,56 +16,31 @@ export default function() { const server = http.createServer(app); server.listen(0); - app.set('port', server.address().port); + app.set('port', (server.address() as AddressInfo).port); app.get('/api/test', function (req, res) { res.send('TEST_API_RETURN'); }); const backendHost = 'localhost'; - const backendPort = server.address().port; + const backendPort = (server.address() as AddressInfo).port; const proxyServerUrl = `http://${backendHost}:${backendPort}`; const proxyConfigFile = 'proxy.config.json'; const proxyConfig = { '/api/*': { - target: proxyServerUrl - } + target: proxyServerUrl, + }, }; return Promise.resolve() .then(() => writeFile(proxyConfigFile, JSON.stringify(proxyConfig, null, 2))) .then(() => ngServe('--proxy-config', proxyConfigFile)) - .then(() => request('http://localhost:4200/api/test')) - .then(body => { - if (!body.match(/TEST_API_RETURN/)) { - throw new Error('Response does not match expected value.'); - } + .then((port) => fetch(`http://localhost:${port}/api/test`)) + .then(async (response) => { + assert.strictEqual(response.status, 200); + assert.match(await response.text(), /TEST_API_RETURN/); }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - - // .then(() => updateJsonFile('angular.json', configJson => { - // const app = configJson.defaults; - // app.serve = { - // proxyConfig: proxyConfigFile - // }; - // })) - // .then(() => ngServe()) - // .then(() => request('http://localhost:4200/api/test')) - // .then(body => { - // if (!body.match(/TEST_API_RETURN/)) { - // throw new Error('Response does not match expected value.'); - // } - // }) - // .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - - .then(() => server.close(), (err) => { server.close(); throw err; }) - - // // A non-existing proxy file should error. - // .then(() => expectToFail(() => ng('serve', '--proxy-config', 'proxy.non-existent.json'))) - // .then(() => updateJsonFile('angular.json', configJson => { - // const app = configJson.defaults; - // app.serve = { - // proxyConfig: 'proxy.non-existent.json' - // }; - // })) - // .then(() => expectToFail(() => ng('serve'))); + .finally(async () => { + await killAllProcesses(); + server.close(); + }); } diff --git a/tests/legacy-cli/e2e/tests/misc/public-host.ts b/tests/legacy-cli/e2e/tests/misc/public-host.ts deleted file mode 100644 index faf63bd82316..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/public-host.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as os from 'os'; -import * as _ from 'lodash'; - -import { request } from '../../utils/http'; -import { killAllProcesses } from '../../utils/process'; -import { ngServe } from '../../utils/project'; - -export default function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - const firstLocalIp = _(os.networkInterfaces()) - .values() - .flatten() - .filter({ family: 'IPv4', internal: false }) - .map('address') - .first(); - const publicHost = `${firstLocalIp}:4200`; - const localAddress = `http://${publicHost}`; - - return Promise.resolve() - // Disabling this test. Webpack Dev Server does not check the hots anymore when binding to - // numeric IP addresses. - // .then(() => ngServe('--host=0.0.0.0')) - // .then(() => request(localAddress)) - // .then(body => { - // if (!body.match(/Invalid Host header/)) { - // throw new Error('Response does not match expected value.'); - // } - // }) - // .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - .then(() => ngServe('--host=0.0.0.0', `--public-host=${publicHost}`)) - .then(() => request(localAddress)) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - .then(() => ngServe('--host=0.0.0.0', `--disable-host-check`)) - .then(() => request(localAddress)) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - .then(() => ngServe('--host=0.0.0.0', `--public-host=${localAddress}`)) - .then(() => request(localAddress)) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }) - .then(() => ngServe('--host=0.0.0.0', `--public-host=${firstLocalIp}`)) - .then(() => request(localAddress)) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }); -} diff --git a/tests/legacy-cli/e2e/tests/misc/safari-15-class-properties.ts b/tests/legacy-cli/e2e/tests/misc/safari-15-class-properties.ts new file mode 100644 index 000000000000..ef9b42b85b11 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/safari-15-class-properties.ts @@ -0,0 +1,70 @@ +import assert from 'node:assert'; +import { expectFileToExist, readFile, writeFile, replaceInFile } from '../../utils/fs'; +import { ng } from '../../utils/process'; + +const unexpectedStaticFieldErrorMessage = + 'Found unexpected static field. This indicates that the Safari <=v15 ' + + 'workaround for a scope variable tracking is not working. ' + + 'See: https://github.com/angular/angular-cli/pull/24357'; + +export default async function () { + // Add a private method + await replaceInFile( + 'src/app/app.component.ts', + `title = 'test-project';`, + ` + #myPrivateMethod() { return 1 } + + constructor() { + console.log(this.#myPrivateMethod) + } + + title = 'test-project';`, + ); + + // Matches two types of static fields that indicate that the Safari bug + // may still occur. With the workaround this should not appear in bundles. + // - static { this.ecmp = bla } + // - static #_ = this.ecmp = bla + const staticIndicatorRegex = /static\s+(\{|#[_\d]+\s+=)/; + + await ng('build', '--configuration=development'); + await expectFileToExist('dist/test-project/main.js'); + const mainContent = await readFile('dist/test-project/main.js'); + // TODO: This default cause can be removed in the future when Safari v15 + // is longer included in the default browserlist configuration of CLI apps. + assert.doesNotMatch(mainContent, staticIndicatorRegex, unexpectedStaticFieldErrorMessage); + + await writeFile('.browserslistrc', 'last 1 chrome version'); + await ng('build', '--configuration=development'); + await expectFileToExist('dist/test-project/main.js'); + const mainContentChromeLatest = await readFile('dist/test-project/main.js'); + + assert.match( + mainContentChromeLatest, + staticIndicatorRegex, + 'Expected static fields to be used when Safari <=v15 is not targeted.', + ); + assert.match( + mainContentChromeLatest, + /#myPrivateMethod/, + 'Expected private method to be used when Safari <=v15 is not targeted.', + ); + + await writeFile('.browserslistrc', 'Safari <=15'); + + await ng('build', '--configuration=development'); + await expectFileToExist('dist/test-project/main.js'); + const mainContentSafari15Explicit = await readFile('dist/test-project/main.js'); + assert.doesNotMatch( + mainContentSafari15Explicit, + staticIndicatorRegex, + unexpectedStaticFieldErrorMessage, + ); + + assert.match( + mainContentSafari15Explicit, + /var _myPrivateMethod/, + 'Expected private method to be downlevelled when Safari <=v15 is targeted', + ); +} diff --git a/tests/legacy-cli/e2e/tests/misc/ssl-default.ts b/tests/legacy-cli/e2e/tests/misc/ssl-default.ts deleted file mode 100644 index e3b8cd2cfe4e..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/ssl-default.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { request } from '../../utils/http'; -import { killAllProcesses } from '../../utils/process'; -import { ngServe } from '../../utils/project'; - - -export default function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - return Promise.resolve() - .then(() => ngServe('--ssl', 'true')) - .then(() => request('https://localhost:4200/')) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }); -} diff --git a/tests/legacy-cli/e2e/tests/misc/ssl-with-cert.ts b/tests/legacy-cli/e2e/tests/misc/ssl-with-cert.ts deleted file mode 100644 index a905d17322bf..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/ssl-with-cert.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { request } from '../../utils/http'; -import { assetDir } from '../../utils/assets'; -import { killAllProcesses } from '../../utils/process'; -import { ngServe } from '../../utils/project'; - - -export default function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - return Promise.resolve() - .then(() => ngServe( - '--ssl', 'true', - '--ssl-key', assetDir('ssl/server.key'), - '--ssl-cert', assetDir('ssl/server.crt') - )) - .then(() => request('https://localhost:4200/')) - .then(body => { - if (!body.match(/<\/app-root>/)) { - throw new Error('Response does not match expected value.'); - } - }) - .then(() => killAllProcesses(), (err) => { killAllProcesses(); throw err; }); - -} diff --git a/tests/legacy-cli/e2e/tests/misc/supported-angular.ts b/tests/legacy-cli/e2e/tests/misc/supported-angular.ts index 271e8663c4c4..d5299485bb7f 100644 --- a/tests/legacy-cli/e2e/tests/misc/supported-angular.ts +++ b/tests/legacy-cli/e2e/tests/misc/supported-angular.ts @@ -4,7 +4,6 @@ import { readFile, writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; - export default async function () { if (getGlobalVariable('argv')['ng-snapshots']) { // The snapshots job won't work correctly because it doesn't use semver for Angular. @@ -25,12 +24,14 @@ export default async function () { // Major should succeed, but we don't need to test it here since it's tested everywhere else. // Major+1 and -1 should fail architect commands, but allow other commands. - await fakeCoreVersion(cliMajor + 1); - await expectToFail(() => ng('build'), 'Should fail Major+1'); - await ng('version'); - await fakeCoreVersion(cliMajor - 1); - await ng('version'); - - // Restore the original core package.json. - await writeFile(angularCorePkgPath, originalAngularCorePkgJson); + try { + await fakeCoreVersion(cliMajor + 1); + await expectToFail(() => ng('build'), 'Should fail Major+1'); + await ng('version'); + await fakeCoreVersion(cliMajor - 1); + await ng('version'); + } finally { + // Restore the original core package.json. + await writeFile(angularCorePkgPath, originalAngularCorePkgJson); + } } diff --git a/tests/legacy-cli/e2e/tests/misc/target-default-configuration.ts b/tests/legacy-cli/e2e/tests/misc/target-default-configuration.ts index dca271a838aa..edd17beb2f28 100644 --- a/tests/legacy-cli/e2e/tests/misc/target-default-configuration.ts +++ b/tests/legacy-cli/e2e/tests/misc/target-default-configuration.ts @@ -4,7 +4,7 @@ import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; export default async function () { - await updateJsonFile('angular.json', workspace => { + await updateJsonFile('angular.json', (workspace) => { const build = workspace.projects['test-project'].architect.build; build.defaultConfiguration = undefined; build.options = { @@ -21,7 +21,7 @@ export default async function () { await expectFileToExist('dist/test-project/main.js.map'); // Add new configuration and set "defaultConfiguration" - await updateJsonFile('angular.json', workspace => { + await updateJsonFile('angular.json', (workspace) => { const build = workspace.projects['test-project'].architect.build; build.defaultConfiguration = 'foo'; build.configurations.foo = { diff --git a/tests/legacy-cli/e2e/tests/misc/third-party-decorators.ts b/tests/legacy-cli/e2e/tests/misc/third-party-decorators.ts index 40a2b730c114..8aac6af53eb4 100644 --- a/tests/legacy-cli/e2e/tests/misc/third-party-decorators.ts +++ b/tests/legacy-cli/e2e/tests/misc/third-party-decorators.ts @@ -3,159 +3,163 @@ import { installWorkspacePackages } from '../../utils/packages'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -export default function () { - return updateJsonFile('package.json', packageJson => { - // Install ngrx - packageJson['dependencies']['@ngrx/effects'] = '^9.1.0'; - packageJson['dependencies']['@ngrx/schematics'] = '^9.1.0'; - packageJson['dependencies']['@ngrx/store'] = '^9.1.0'; - packageJson['dependencies']['@ngrx/store-devtools'] = '^9.1.0'; - }) - .then(() => installWorkspacePackages()) - // Create an app that uses ngrx decorators and has e2e tests. - .then(_ => writeMultipleFiles({ - './e2e/src/app.po.ts': ` - import { browser, by, element } from 'protractor'; - export class AppPage { - async navigateTo() { return browser.get('/'); } - getIncrementButton() { return element(by.buttonText('Increment')); } - getDecrementButton() { return element(by.buttonText('Decrement')); } - getResetButton() { return element(by.buttonText('Reset Counter')); } - async getCounter() { return element(by.xpath('/html/body/app-root/div/span')).getText(); } - } - `, - './e2e/src/app.e2e-spec.ts': ` - import { AppPage } from './app.po'; - - describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should operate counter', async () => { - await page.navigateTo(); - await page.getIncrementButton().click(); - await page.getIncrementButton().click(); - expect(await page.getCounter()).toEqual('2'); - await page.getDecrementButton().click(); - expect(await page.getCounter()).toEqual('1'); - await page.getResetButton().click(); - expect(await page.getCounter()).toEqual('0'); - }); +export default async function () { + await updateJsonFile('package.json', (packageJson) => { + // Install NGRX + packageJson['dependencies']['@ngrx/effects'] = '^14.3.0'; + packageJson['dependencies']['@ngrx/schematics'] = '^14.3.0'; + packageJson['dependencies']['@ngrx/store'] = '^14.3.0'; + packageJson['dependencies']['@ngrx/store-devtools'] = '^14.3.0'; + }); + + // Force is need to prevent npm 7+ from failing due to potential peer dependency resolution range errors. + // This is especially common when testing snapshot builds for new prereleases. + await installWorkspacePackages({ force: true }); + + // Create an app that uses ngrx decorators and has e2e tests. + await writeMultipleFiles({ + './e2e/src/app.po.ts': ` + import { browser, by, element } from 'protractor'; + export class AppPage { + async navigateTo() { return browser.get('/'); } + getIncrementButton() { return element(by.buttonText('Increment')); } + getDecrementButton() { return element(by.buttonText('Decrement')); } + getResetButton() { return element(by.buttonText('Reset Counter')); } + async getCounter() { return element(by.xpath('/html/body/app-root/div/span')).getText(); } + } + `, + './e2e/src/app.e2e-spec.ts': ` + import { AppPage } from './app.po'; + + describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); }); - `, - './src/app/app.component.ts': ` - import { Component } from '@angular/core'; - import { Store, select } from '@ngrx/store'; - import { Observable } from 'rxjs'; - import { INCREMENT, DECREMENT, RESET } from './counter.reducer'; - - interface AppState { - count: number; - } - - @Component({ - selector: 'app-root', - template: \` - -
Current Count: {{ count$ | async }}
- - - - \`, - }) - export class AppComponent { - count$: Observable; - - constructor(private store: Store) { - this.count$ = store.pipe(select(state => state.count)); - } - increment() { - this.store.dispatch({ type: INCREMENT }); - } - - decrement() { - this.store.dispatch({ type: DECREMENT }); - } - - reset() { - this.store.dispatch({ type: RESET }); - } + it('should operate counter', async () => { + await page.navigateTo(); + await page.getIncrementButton().click(); + await page.getIncrementButton().click(); + expect(await page.getCounter()).toEqual('2'); + await page.getDecrementButton().click(); + expect(await page.getCounter()).toEqual('1'); + await page.getResetButton().click(); + expect(await page.getCounter()).toEqual('0'); + }); + }); + `, + './src/app/app.component.ts': ` + import { Component } from '@angular/core'; + import { Store, select } from '@ngrx/store'; + import { Observable } from 'rxjs'; + import { INCREMENT, DECREMENT, RESET } from './counter.reducer'; + + interface AppState { + count: number; + } + + @Component({ + selector: 'app-root', + template: \` + +
Current Count: {{ count$ | async }}
+ + + + \`, + }) + export class AppComponent { + count$: Observable; + + constructor(private store: Store) { + this.count$ = store.pipe(select(state => state.count)); } - `, - './src/app/app.effects.ts': ` - import { Injectable } from '@angular/core'; - import { Actions, Effect } from '@ngrx/effects'; - import { filter, map, tap } from 'rxjs/operators'; - - @Injectable() - export class AppEffects { - - @Effect() - mapper$ = this.actions$.pipe(map(() => ({ type: 'ANOTHER'})), filter(() => false)); - @Effect({ dispatch: false }) - logger$ = this.actions$.pipe(tap(console.log)); - - constructor(private actions$: Actions) {} + increment() { + this.store.dispatch({ type: INCREMENT }); } - `, - './src/app/app.module.ts': ` - import { BrowserModule } from '@angular/platform-browser'; - import { NgModule } from '@angular/core'; - - import { AppComponent } from './app.component'; - import { StoreModule } from '@ngrx/store'; - import { StoreDevtoolsModule } from '@ngrx/store-devtools'; - import { environment } from '../environments/environment'; - import { EffectsModule } from '@ngrx/effects'; - import { AppEffects } from './app.effects'; - import { counterReducer } from './counter.reducer'; - - @NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - StoreModule.forRoot({ count: counterReducer }), - !environment.production ? StoreDevtoolsModule.instrument() : [], - EffectsModule.forRoot([AppEffects]) - ], - providers: [], - bootstrap: [AppComponent] - }) - export class AppModule { } - `, - './src/app/counter.reducer.ts': ` - import { Action } from '@ngrx/store'; - export const INCREMENT = 'INCREMENT'; - export const DECREMENT = 'DECREMENT'; - export const RESET = 'RESET'; + decrement() { + this.store.dispatch({ type: DECREMENT }); + } - const initialState = 0; + reset() { + this.store.dispatch({ type: RESET }); + } + } + `, + './src/app/app.effects.ts': ` + import { Injectable } from '@angular/core'; + import { Actions, Effect } from '@ngrx/effects'; + import { filter, map, tap } from 'rxjs/operators'; - export function counterReducer(state: number = initialState, action: Action) { - switch (action.type) { - case INCREMENT: - return state + 1; + @Injectable() + export class AppEffects { - case DECREMENT: - return state - 1; + @Effect() + mapper$ = this.actions$.pipe(map(() => ({ type: 'ANOTHER'})), filter(() => false)); - case RESET: - return 0; + @Effect({ dispatch: false }) + logger$ = this.actions$.pipe(tap(console.log)); - default: - return state; + constructor(private actions$: Actions) {} } - } `, - })) - // Run the e2e tests against a prod build. - .then(() => ng('e2e', '--prod')); + './src/app/app.module.ts': ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + + import { AppComponent } from './app.component'; + import { StoreModule } from '@ngrx/store'; + import { StoreDevtoolsModule } from '@ngrx/store-devtools'; + import { EffectsModule } from '@ngrx/effects'; + import { AppEffects } from './app.effects'; + import { counterReducer } from './counter.reducer'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + StoreModule.forRoot({ count: counterReducer }), + StoreDevtoolsModule.instrument(), + EffectsModule.forRoot([AppEffects]) + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + './src/app/counter.reducer.ts': ` + import { Action } from '@ngrx/store'; + + export const INCREMENT = 'INCREMENT'; + export const DECREMENT = 'DECREMENT'; + export const RESET = 'RESET'; + + const initialState = 0; + + export function counterReducer(state: number = initialState, action: Action) { + switch (action.type) { + case INCREMENT: + return state + 1; + + case DECREMENT: + return state - 1; + + case RESET: + return 0; + + default: + return state; + } + } + `, + }); + + // Run the e2e tests against a production build. + await ng('e2e', '--configuration=production'); } diff --git a/tests/legacy-cli/e2e/tests/misc/title.ts b/tests/legacy-cli/e2e/tests/misc/title.ts index 37f65e63c71d..85c2b79bfb9e 100644 --- a/tests/legacy-cli/e2e/tests/misc/title.ts +++ b/tests/legacy-cli/e2e/tests/misc/title.ts @@ -1,22 +1,17 @@ -import { execAndWaitForOutputToMatch, execWithEnv, killAllProcesses } from '../../utils/process'; +import { execAndWaitForOutputToMatch, execWithEnv } from '../../utils/process'; - -export default async function() { +export default async function () { if (process.platform.startsWith('win')) { // "On Windows, process.title affects the console title, but not the name of the process in the task manager." // https://stackoverflow.com/questions/44756196/how-to-change-the-node-js-process-name-on-windows-10#comment96259375_44756196 - return Promise.resolve(); + return; } - try { - await execAndWaitForOutputToMatch('ng', ['build', '--configuration=development', '--watch'], /./); + await execAndWaitForOutputToMatch('ng', ['build', '--configuration=development', '--watch'], /./); - const output = await execWithEnv('ps', ['x'], { COLUMNS: '200' }); + const output = await execWithEnv('ps', ['x'], { COLUMNS: '200' }); - if (!output.stdout.match(/ng build --configuration=development --watch/)) { - throw new Error('Title of the process was not properly set.'); - } - } finally { - killAllProcesses(); + if (!output.stdout.match(/ng build --configuration=development --watch/)) { + throw new Error('Title of the process was not properly set.'); } } diff --git a/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts b/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts index cce9ef382bf5..0827223e58a0 100644 --- a/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts +++ b/tests/legacy-cli/e2e/tests/misc/trace-resolution.ts @@ -2,7 +2,7 @@ import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; export default async function () { - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.json', (tsconfig) => { tsconfig.compilerOptions.traceResolution = true; }); @@ -11,7 +11,7 @@ export default async function () { throw new Error(`Modules resolutions must be printed when 'traceResolution' is enabled.`); } - await updateJsonFile('tsconfig.json', tsconfig => { + await updateJsonFile('tsconfig.json', (tsconfig) => { tsconfig.compilerOptions.traceResolution = false; }); diff --git a/tests/legacy-cli/e2e/tests/misc/trusted-types.ts b/tests/legacy-cli/e2e/tests/misc/trusted-types.ts new file mode 100644 index 000000000000..4efb8b9f70e3 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/trusted-types.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { appendToFile, prependToFile, replaceInFile, writeFile } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + // Add app routing. + // This is done automatically on a new app with --routing. + await prependToFile('src/app/app.module.ts', `import { RouterModule } from '@angular/router';`); + await replaceInFile( + 'src/app/app.module.ts', + `imports: [`, + `imports: [ RouterModule.forRoot([]),`, + ); + await appendToFile('src/app/app.component.html', ''); + + // Add lazy route. + await ng('generate', 'module', 'lazy', '--route', 'lazy', '--module', 'app.module'); + + // Add lazy route e2e + await writeFile( + 'e2e/src/app.e2e-spec.ts', + ` + import { browser, logging, element, by } from 'protractor'; + + describe('workspace-project App', () => { + it('should display lazy route', async () => { + await browser.get(browser.baseUrl + '/lazy'); + expect(await element(by.css('app-lazy p')).getText()).toEqual('lazy works!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + })); + }); + }); + `, + ); + + const testCases = [ + { + aot: false, + csp: `trusted-types angular angular#unsafe-bypass angular#unsafe-jit angular#bundler; require-trusted-types-for 'script';`, + }, + { + aot: true, + csp: `trusted-types angular angular#unsafe-bypass angular#bundler; require-trusted-types-for 'script';`, + }, + ]; + + for (const { aot, csp } of testCases) { + await updateJsonFile('angular.json', (json) => { + const architect = json['projects']['test-project']['architect']; + architect['build']['options']['aot'] = aot; + if (!architect['serve']['options']) architect['serve']['options'] = {}; + architect['serve']['options']['headers'] = { + 'Content-Security-Policy': csp, + }; + }); + + try { + await ng('e2e'); + } catch (error) { + console.error(`Test case AOT ${aot} with CSP header ${csp} failed.`); + throw error; + } + } +} diff --git a/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts b/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts deleted file mode 100644 index f00c1087589e..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as path from 'path'; -import { - createDir, - expectFileToMatch, - rimraf, - symlinkFile, - writeMultipleFiles, -} from '../../utils/fs'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; - -export default async function() { - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect['server'] = { - builder: '@angular-devkit/build-angular:server', - options: { - bundleDependencies: false, - outputPath: 'dist/test-project-server', - main: 'src/main.server.ts', - tsConfig: 'tsconfig.server.json', - }, - }; - }); - - await createDir('./dummy-lib'); - - await writeMultipleFiles({ - './tsconfig.server.json': ` - { - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../dist-server", - "baseUrl": "./", - "module": "commonjs", - "types": [] - }, - "include": [ - "src/main.server.ts" - ] - } - `, - './src/main.server.ts': ` - import { dummyVersion } from 'dummy-lib'; - console.log(dummyVersion); - `, - // create a dummy library - './dummy-lib/package.json': `{ - "name": "dummy-lib", - "version": "0.0.0", - "typings": "./main.d.ts", - "main": "./main.js" - }`, - './dummy-lib/main.js': 'export const dummyVersion = 1', - './dummy-lib/main.d.ts': 'export declare const dummyVersion = 1', - }); - - await symlinkFile(path.resolve('./dummy-lib'), path.resolve('./node_modules/dummy-lib'), 'dir'); - - await ng('run', 'test-project:server'); - // when preserve symlinks is true, it should not included node_modules in the bundle - await expectFileToMatch('dist/test-project-server/main.js', 'require("dummy-lib")'); - - // cleanup the package - await rimraf('node_modules/dummy-lib'); -} diff --git a/tests/legacy-cli/e2e/tests/misc/update-git-clean-subdirectory.ts b/tests/legacy-cli/e2e/tests/misc/update-git-clean-subdirectory.ts index 8e2a3f668c65..11040c618bbb 100644 --- a/tests/legacy-cli/e2e/tests/misc/update-git-clean-subdirectory.ts +++ b/tests/legacy-cli/e2e/tests/misc/update-git-clean-subdirectory.ts @@ -3,8 +3,8 @@ import { createDir, writeFile } from '../../utils/fs'; import { ng, silentGit } from '../../utils/process'; import { prepareProjectForE2e } from '../../utils/project'; -export default async function() { - process.chdir(getGlobalVariable('tmp-root')); +export default async function () { + process.chdir(getGlobalVariable('projects-root')); await createDir('./subdirectory'); process.chdir('./subdirectory'); @@ -15,7 +15,7 @@ export default async function() { process.chdir('./subdirectory-test-project'); await prepareProjectForE2e('subdirectory-test-project'); - await writeFile('../added.ts', 'console.log(\'created\');\n'); + await writeFile('../added.ts', "console.log('created');\n"); await silentGit('add', '../added.ts'); const { stderr } = await ng('update', '@angular/cli'); diff --git a/tests/legacy-cli/e2e/tests/misc/update-git-clean.ts b/tests/legacy-cli/e2e/tests/misc/update-git-clean.ts index 0026fff5c537..c992c695c4e3 100644 --- a/tests/legacy-cli/e2e/tests/misc/update-git-clean.ts +++ b/tests/legacy-cli/e2e/tests/misc/update-git-clean.ts @@ -2,8 +2,8 @@ import { appendToFile } from '../../utils/fs'; import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; -export default async function() { - await appendToFile('src/main.ts', 'console.log(\'changed\');\n'); +export default async function () { + await appendToFile('src/main.ts', "console.log('changed');\n"); const { message } = await expectToFail(() => ng('update', '@angular/cli')); if (!message || !message.includes('Repository is not clean.')) { diff --git a/tests/legacy-cli/e2e/tests/misc/update-help.ts b/tests/legacy-cli/e2e/tests/misc/update-help.ts deleted file mode 100644 index 2e98d9fd4a9d..000000000000 --- a/tests/legacy-cli/e2e/tests/misc/update-help.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ng } from '../../utils/process'; - -export default function () { - return Promise.resolve() - .then(() => ng('update', '--help')) - .then(({ stdout }) => { - if (!/next/.test(stdout)) { - throw 'Update help should contain "next" option'; - } - }); -} diff --git a/tests/legacy-cli/e2e/tests/misc/version.ts b/tests/legacy-cli/e2e/tests/misc/version.ts index 9d6858ac1362..4ad57adc9726 100644 --- a/tests/legacy-cli/e2e/tests/misc/version.ts +++ b/tests/legacy-cli/e2e/tests/misc/version.ts @@ -1,15 +1,15 @@ import { deleteFile } from '../../utils/fs'; import { ng } from '../../utils/process'; -export default async function() { +export default async function () { const { stdout: commandOutput } = await ng('version'); - const { stdout: optionOutput } = await ng('--version'); - if (!optionOutput.includes('Angular CLI:')) { - throw new Error('version not displayed'); + + if (commandOutput.includes(process.versions.node + ' (Unsupported)')) { + throw new Error('Node version should not show unsupported entry'); } - if (commandOutput !== optionOutput) { - throw new Error('version variants have differing output'); + if (commandOutput.includes('Warning: The current version of Node ')) { + throw new Error('Node support warning should not be shown'); } // doesn't fail on a project with missing angular.json diff --git a/tests/legacy-cli/e2e/tests/misc/workspace-verification.ts b/tests/legacy-cli/e2e/tests/misc/workspace-verification.ts index a9353edf7395..bf55841a9398 100644 --- a/tests/legacy-cli/e2e/tests/misc/workspace-verification.ts +++ b/tests/legacy-cli/e2e/tests/misc/workspace-verification.ts @@ -1,13 +1,14 @@ -import {deleteFile} from '../../utils/fs'; -import {ng} from '../../utils/process'; +import { deleteFile } from '../../utils/fs'; +import { ng } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; - -export default function() { - return ng('generate', 'component', 'foo', '--dry-run') - .then(() => deleteFile('angular.json')) - // fails because it needs to be inside a project - // without a workspace file - .then(() => expectToFail(() => ng('generate', 'component', 'foo', '--dry-run'))) - .then(() => ng('version')); +export default function () { + return ( + ng('generate', 'component', 'foo', '--dry-run') + .then(() => deleteFile('angular.json')) + // fails because it needs to be inside a project + // without a workspace file + .then(() => expectToFail(() => ng('generate', 'component', 'foo', '--dry-run'))) + .then(() => ng('version')) + ); } diff --git a/tests/legacy-cli/e2e/tests/packages/webpack/test-app.ts b/tests/legacy-cli/e2e/tests/packages/webpack/test-app.ts index 1ee5f70b03ed..2ddcca27f97f 100644 --- a/tests/legacy-cli/e2e/tests/packages/webpack/test-app.ts +++ b/tests/legacy-cli/e2e/tests/packages/webpack/test-app.ts @@ -1,14 +1,16 @@ import { normalize } from 'path'; import { createProjectFromAsset } from '../../../utils/assets'; import { expectFileSizeToBeUnder, expectFileToMatch, replaceInFile } from '../../../utils/fs'; -import { exec } from '../../../utils/process'; +import { execWithEnv } from '../../../utils/process'; -export default async function (skipCleaning: () => void) { +export default async function () { const webpackCLIBin = normalize('node_modules/.bin/webpack-cli'); + const restoreRegistry = await createProjectFromAsset('webpack/test-app'); - await createProjectFromAsset('webpack/test-app'); - - await exec(webpackCLIBin); + // DISABLE_V8_COMPILE_CACHE=1 is required to disable the `v8-compile-cache` package. + // It currently does not support dynamic import expressions which are now required by the + // CLI to support ESM. ref: https://github.com/zertosh/v8-compile-cache/issues/30 + await execWithEnv(webpackCLIBin, [], { ...process.env, 'DISABLE_V8_COMPILE_CACHE': '1' }); // Note: these sizes are without Build Optimizer or any advanced optimizations in the CLI. await expectFileSizeToBeUnder('dist/app.main.js', 656 * 1024); @@ -16,15 +18,16 @@ export default async function (skipCleaning: () => void) { await expectFileSizeToBeUnder('dist/888.app.main.js', 2 * 1024); await expectFileSizeToBeUnder('dist/972.app.main.js', 2 * 1024); - // test resource urls without ./ await replaceInFile('app/app.component.ts', './app.component.html', 'app.component.html'); await replaceInFile('app/app.component.ts', './app.component.scss', 'app.component.scss'); // test the inclusion of metadata // This build also test resource URLs without ./ - await exec(webpackCLIBin, '--mode=development'); + await execWithEnv(webpackCLIBin, ['--mode=development'], { + ...process.env, + 'DISABLE_V8_COMPILE_CACHE': '1', + }); await expectFileToMatch('dist/app.main.js', 'AppModule'); - - skipCleaning(); + await restoreRegistry(); } diff --git a/tests/legacy-cli/e2e/tests/schematics_cli/basic.ts b/tests/legacy-cli/e2e/tests/schematics_cli/basic.ts index fd2cf368dc50..da65b17ef02c 100644 --- a/tests/legacy-cli/e2e/tests/schematics_cli/basic.ts +++ b/tests/legacy-cli/e2e/tests/schematics_cli/basic.ts @@ -10,11 +10,7 @@ export default async function () { return; } - await silentNpm( - 'install', - '-g', - '@angular-devkit/schematics-cli', - ); + await silentNpm('install', '-g', '@angular-devkit/schematics-cli'); await exec(process.platform.startsWith('win') ? 'where' : 'which', 'schematics'); const startCwd = process.cwd(); @@ -30,7 +26,6 @@ export default async function () { ['.:', '--list-schematics'], /my-full-schematic/, ); - } finally { // restore path process.chdir(startCwd); diff --git a/tests/legacy-cli/e2e/tests/schematics_cli/blank-test.ts b/tests/legacy-cli/e2e/tests/schematics_cli/blank-test.ts index badc3a37cdf0..76b3cb67395e 100644 --- a/tests/legacy-cli/e2e/tests/schematics_cli/blank-test.ts +++ b/tests/legacy-cli/e2e/tests/schematics_cli/blank-test.ts @@ -11,11 +11,7 @@ export default async function () { return; } - await silentNpm( - 'install', - '-g', - '@angular-devkit/schematics-cli', - ); + await silentNpm('install', '-g', '@angular-devkit/schematics-cli'); await exec(process.platform.startsWith('win') ? 'where' : 'which', 'schematics'); const startCwd = process.cwd(); diff --git a/tests/legacy-cli/e2e/tests/schematics_cli/schematic-test.ts b/tests/legacy-cli/e2e/tests/schematics_cli/schematic-test.ts index 4c4993b4b0c3..0033f98cc96f 100644 --- a/tests/legacy-cli/e2e/tests/schematics_cli/schematic-test.ts +++ b/tests/legacy-cli/e2e/tests/schematics_cli/schematic-test.ts @@ -11,11 +11,7 @@ export default async function () { return; } - await silentNpm( - 'install', - '-g', - '@angular-devkit/schematics-cli', - ); + await silentNpm('install', '-g', '@angular-devkit/schematics-cli'); await exec(process.platform.startsWith('win') ? 'where' : 'which', 'schematics'); const startCwd = process.cwd(); diff --git a/tests/legacy-cli/e2e/tests/test/test-code-coverage-exclude.ts b/tests/legacy-cli/e2e/tests/test/test-code-coverage-exclude.ts new file mode 100644 index 000000000000..52b8989218b5 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/test/test-code-coverage-exclude.ts @@ -0,0 +1,22 @@ +import { expectFileToExist, rimraf } from '../../utils/fs'; +import { silentNg } from '../../utils/process'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + // This test is already in build-angular, but that doesn't run on Windows. + await silentNg('test', '--no-watch', '--code-coverage'); + await expectFileToExist('coverage/test-project/app.component.ts.html'); + // Delete coverage directory + await rimraf('coverage'); + + await silentNg( + 'test', + '--no-watch', + '--code-coverage', + `--code-coverage-exclude='src/**/app.component.ts'`, + ); + + // Doesn't include excluded. + await expectFileToExist('coverage/test-project/index.html'); + await expectToFail(() => expectFileToExist('coverage/test-project/app.component.ts.html')); +} diff --git a/tests/legacy-cli/e2e/tests/test/test-environment.ts b/tests/legacy-cli/e2e/tests/test/test-environment.ts index 09152caee409..e699a7ceb298 100644 --- a/tests/legacy-cli/e2e/tests/test/test-environment.ts +++ b/tests/legacy-cli/e2e/tests/test/test-environment.ts @@ -1,42 +1,62 @@ import { ng } from '../../utils/process'; -import { writeFile } from '../../utils/fs'; +import { writeFile, writeMultipleFiles } from '../../utils/fs'; import { updateJsonFile } from '../../utils/project'; export default function () { // Tests run in 'dev' environment by default. - return writeFile('src/app/environment.spec.ts', ` - import { environment } from '../environments/environment'; + return ( + writeMultipleFiles({ + 'src/environment.prod.ts': ` + export const environment = { + production: true + };`, + 'src/environment.ts': ` + export const environment = { + production: false + }; + `, + 'src/app/environment.spec.ts': ` + import { environment } from '../environment'; describe('Test environment', () => { it('should have production disabled', () => { expect(environment.production).toBe(false); }); }); - `) - .then(() => ng('test', '--watch=false')) - .then(() => updateJsonFile('angular.json', configJson => { - const appArchitect = configJson.projects['test-project'].architect; - appArchitect.test.configurations = { - production: { - fileReplacements: [ - { - src: 'src/environments/environment.ts', - replaceWith: 'src/environments/environment.prod.ts', - } - ], - } - }; - })) + `, + }) + .then(() => ng('test', '--watch=false')) + .then(() => + updateJsonFile('angular.json', (configJson) => { + const appArchitect = configJson.projects['test-project'].architect; + appArchitect.test.configurations = { + production: { + fileReplacements: [ + { + src: 'src/environment.ts', + replaceWith: 'src/environment.prod.ts', + }, + ], + }, + }; + }), + ) - // Tests can run in different environment. - .then(() => writeFile('src/app/environment.spec.ts', ` - import { environment } from '../environments/environment'; + // Tests can run in different environment. + .then(() => + writeFile( + 'src/app/environment.spec.ts', + ` + import { environment } from '../environment'; describe('Test environment', () => { it('should have production enabled', () => { expect(environment.production).toBe(true); }); }); - `)) - .then(() => ng('test', '--prod', '--watch=false')); + `, + ), + ) + .then(() => ng('test', '--configuration=production', '--watch=false')) + ); } diff --git a/tests/legacy-cli/e2e/tests/test/test-fail-single-run.ts b/tests/legacy-cli/e2e/tests/test/test-fail-single-run.ts index 62ac51d0199a..90f7d73736c1 100644 --- a/tests/legacy-cli/e2e/tests/test/test-fail-single-run.ts +++ b/tests/legacy-cli/e2e/tests/test/test-fail-single-run.ts @@ -2,11 +2,11 @@ import { ng } from '../../utils/process'; import { writeFile } from '../../utils/fs'; import { expectToFail } from '../../utils/utils'; - export default function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. // Fails on single run with broken compilation. - return writeFile('src/app.component.spec.ts', '

definitely not typescript

') - .then(() => expectToFail(() => ng('test', '--watch=false'))); + return writeFile('src/app.component.spec.ts', '

definitely not typescript

').then(() => + expectToFail(() => ng('test', '--watch=false')), + ); } diff --git a/tests/legacy-cli/e2e/tests/test/test-fail-watch.ts b/tests/legacy-cli/e2e/tests/test/test-fail-watch.ts deleted file mode 100644 index 69b28fabaeea..000000000000 --- a/tests/legacy-cli/e2e/tests/test/test-fail-watch.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - killAllProcesses, - waitForAnyProcessOutputToMatch, - execAndWaitForOutputToMatch, -} from '../../utils/process'; -import { expectToFail } from '../../utils/utils'; -import { readFile, writeFile } from '../../utils/fs'; - - -// Karma is only really finished with a run when it shows a non-zero total time in the first slot. -const karmaGoodRegEx = /Executed 3 of 3 SUCCESS \(\d+\.\d+ secs/; - -export default function () { - // TODO(architect): This test is behaving oddly both here and in devkit/build-angular. - // It seems to be because of file watchers. - return; - - let originalSpec: string; - return execAndWaitForOutputToMatch('ng', ['test'], karmaGoodRegEx) - .then(() => readFile('src/app/app.component.spec.ts')) - .then((data) => originalSpec = data) - // Trigger a failed rebuild, which shouldn't run tests again. - .then(() => writeFile('src/app/app.component.spec.ts', '

definitely not typescript

')) - .then(() => expectToFail(() => waitForAnyProcessOutputToMatch(karmaGoodRegEx, 10000))) - // Restore working spec. - .then(() => writeFile('src/app/app.component.spec.ts', originalSpec)) - .then(() => waitForAnyProcessOutputToMatch(karmaGoodRegEx, 20000)) - .then(() => killAllProcesses(), (err: any) => { - killAllProcesses(); - throw err; - }); -} diff --git a/tests/legacy-cli/e2e/tests/test/test-include-glob.ts b/tests/legacy-cli/e2e/tests/test/test-include-glob.ts new file mode 100644 index 000000000000..5dc55edbf8c7 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/test/test-include-glob.ts @@ -0,0 +1,5 @@ +import { ng } from '../../utils/process'; + +export default async function () { + await ng('test', '--no-watch', `--include='**/*.spec.ts'`); +} diff --git a/tests/legacy-cli/e2e/tests/test/test-scripts.ts b/tests/legacy-cli/e2e/tests/test/test-scripts.ts index 4114447796ae..5a21d698bf87 100644 --- a/tests/legacy-cli/e2e/tests/test/test-scripts.ts +++ b/tests/legacy-cli/e2e/tests/test/test-scripts.ts @@ -2,74 +2,74 @@ import { writeMultipleFiles } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; -import { stripIndent } from 'common-tags'; - -export default function () { +export default async function () { // TODO(architect): Delete this test. It is now in devkit/build-angular. - return Promise.resolve() - .then(() => ng('test', '--watch=false')) - // prepare global scripts test files - .then(() => writeMultipleFiles({ - 'src/string-script.js': `stringScriptGlobal = 'string-scripts.js';`, - 'src/input-script.js': `inputScriptGlobal = 'input-scripts.js';`, - 'src/typings.d.ts': stripIndent` - declare var stringScriptGlobal: any; - declare var inputScriptGlobal: any; - `, - 'src/app/app.component.ts': stripIndent` - import { Component } from '@angular/core'; + await ng('test', '--watch=false'); - @Component({ selector: 'app-root', template: '' }) - export class AppComponent { - stringScriptGlobalProp = stringScriptGlobal; - inputScriptGlobalProp = inputScriptGlobal; - } - `, - 'src/app/app.component.spec.ts': stripIndent` - import { TestBed } from '@angular/core/testing'; - import { AppComponent } from './app.component'; + // prepare global scripts test files + await writeMultipleFiles({ + 'src/string-script.js': `stringScriptGlobal = 'string-scripts.js';`, + 'src/input-script.js': `inputScriptGlobal = 'input-scripts.js';`, + 'src/typings.d.ts': ` + declare var stringScriptGlobal: any; + declare var inputScriptGlobal: any; + `, + 'src/app/app.component.ts': ` + import { Component } from '@angular/core'; - describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ AppComponent ] - }).compileComponents(); - }); + @Component({ selector: 'app-root', template: '' }) + export class AppComponent { + stringScriptGlobalProp = stringScriptGlobal; + inputScriptGlobalProp = inputScriptGlobal; + } + `, + 'src/app/app.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { AppComponent } from './app.component'; - it('should have access to string-script.js', () => { - let app = TestBed.createComponent(AppComponent).debugElement.componentInstance; - expect(app.stringScriptGlobalProp).toEqual('string-scripts.js'); - }); + describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AppComponent ] + }).compileComponents(); + }); - it('should have access to input-script.js', () => { - let app = TestBed.createComponent(AppComponent).debugElement.componentInstance; - expect(app.inputScriptGlobalProp).toEqual('input-scripts.js'); - }); + it('should have access to string-script.js', () => { + let app = TestBed.createComponent(AppComponent).debugElement.componentInstance; + expect(app.stringScriptGlobalProp).toEqual('string-scripts.js'); }); - describe('Spec', () => { - it('should have access to string-script.js', () => { - expect(stringScriptGlobal).toBe('string-scripts.js'); - }); + it('should have access to input-script.js', () => { + let app = TestBed.createComponent(AppComponent).debugElement.componentInstance; + expect(app.inputScriptGlobalProp).toEqual('input-scripts.js'); + }); + }); - it('should have access to input-script.js', () => { - expect(inputScriptGlobal).toBe('input-scripts.js'); - }); + describe('Spec', () => { + it('should have access to string-script.js', () => { + expect(stringScriptGlobal).toBe('string-scripts.js'); }); - ` - })) - // should fail because the global scripts were not added to scripts array - .then(() => expectToFail(() => ng('test', '--watch=false'))) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.test.options.scripts = [ - { input: 'src/string-script.js' }, - { input: 'src/input-script.js' }, - ]; - })) - // should pass now - .then(() => ng('test', '--watch=false')); -} + it('should have access to input-script.js', () => { + expect(inputScriptGlobal).toBe('input-scripts.js'); + }); + }); + `, + }); + + // should fail because the global scripts were not added to scripts array + await expectToFail(() => ng('test', '--watch=false')); + + await updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.test.options.scripts = [ + { input: 'src/string-script.js' }, + { input: 'src/input-script.js' }, + ]; + }); + + // should pass now + await ng('test', '--watch=false'); +} diff --git a/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts b/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts index 7bf102e0820a..620e5ab138b5 100644 --- a/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts +++ b/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts @@ -1,58 +1,33 @@ import { writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; export default async function () { - await writeFile('src/app/app.component.spec.ts', ` + await writeFile( + 'src/app/app.component.spec.ts', + ` it('show fail', () => { expect(undefined).toBeTruthy(); }); - `); - - await updateJsonFile('angular.json', configJson => { - const appArchitect = configJson.projects['test-project'].architect; - appArchitect.test.options.sourceMap = { - scripts: true, - }; - }); - - // when sourcemaps are 'on' the stacktrace will point to the spec.ts file. - try { - await ng('test', '--watch', 'false'); - throw new Error('ng test should have failed.'); - } catch (error) { - if (!error.message.includes('app.component.spec.ts')) { - throw error; - }; - } - - await updateJsonFile('angular.json', configJson => { - const appArchitect = configJson.projects['test-project'].architect; - appArchitect.test.options.sourceMap = true; - }); + `, + ); // when sourcemaps are 'on' the stacktrace will point to the spec.ts file. try { - await ng('test', '--watch', 'false'); + await ng('test', '--no-watch', '--source-map'); throw new Error('ng test should have failed.'); } catch (error) { - if (!error.message.includes('app.component.spec.ts')) { + if (!(error instanceof Error && error.message.includes('app.component.spec.ts'))) { throw error; - }; + } } - await updateJsonFile('angular.json', configJson => { - const appArchitect = configJson.projects['test-project'].architect; - appArchitect.test.options.sourceMap = false; - }); - // when sourcemaps are 'off' the stacktrace won't point to the spec.ts file. try { - await ng('test', '--watch', 'false'); + await ng('test', '--no-watch', '--no-source-map'); throw new Error('ng test should have failed.'); } catch (error) { - if (!error.message.includes('main.js')) { + if (!(error instanceof Error && error.message.includes('main.js'))) { throw error; - }; + } } } diff --git a/tests/legacy-cli/e2e/tests/test/test-target.ts b/tests/legacy-cli/e2e/tests/test/test-target.ts deleted file mode 100644 index ba1afd13d1c2..000000000000 --- a/tests/legacy-cli/e2e/tests/test/test-target.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; - -export default function () { - // TODO(architect): This is giving odd errors in devkit/build-angular. - // TypeError: Assignment to constant variable. - return; - - return updateJsonFile('tsconfig.json', configJson => { - const compilerOptions = configJson['compilerOptions']; - compilerOptions['target'] = 'es2015'; - }) - .then(() => updateJsonFile('src/tsconfig.spec.json', configJson => { - const compilerOptions = configJson['compilerOptions']; - compilerOptions['target'] = 'es2015'; - })) - .then(() => ng('test', '--watch=false')); -} diff --git a/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts b/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts index 04e2a51b0213..7df90fd3956c 100644 --- a/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts +++ b/tests/legacy-cli/e2e/tests/third-party/bootstrap.ts @@ -1,41 +1,42 @@ import { installPackage } from '../../utils/packages'; -import {ng} from '../../utils/process'; -import {updateJsonFile} from '../../utils/project'; -import {expectFileToMatch} from '../../utils/fs'; -import {oneLineTrim} from 'common-tags'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import { expectFileToMatch } from '../../utils/fs'; - -export default function() { - // TODO(architect): Delete this test. It is now in devkit/build-angular. +export default function () { + // TODO(architect): Delete this test. It is now in devkit/build-angular. return Promise.resolve() .then(() => installPackage('bootstrap@4.0.0-beta.3')) - .then(() => updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'node_modules/bootstrap/dist/css/bootstrap.css' }, - ]; - appArchitect.build.options.scripts = [ - { input: 'node_modules/bootstrap/dist/js/bootstrap.js' }, - ]; - })) - .then(() => ng('build', '--extract-css', '--configuration=development')) + .then(() => + updateJsonFile('angular.json', (workspaceJson) => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect.build.options.styles = [ + { input: 'node_modules/bootstrap/dist/css/bootstrap.css' }, + ]; + appArchitect.build.options.scripts = [ + { input: 'node_modules/bootstrap/dist/js/bootstrap.js' }, + ]; + }), + ) + .then(() => ng('build', '--configuration=development')) .then(() => expectFileToMatch('dist/test-project/scripts.js', '* Bootstrap')) .then(() => expectFileToMatch('dist/test-project/styles.css', '* Bootstrap')) - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` - - `)) - .then(() => ng( - 'build', - '--configuration=development', - '--optimization', - '--extract-css', - '--output-hashing=none', - '--vendor-chunk=false', - )) + .then(() => + expectFileToMatch('dist/test-project/index.html', ''), + ) + .then(() => + ng( + 'build', + '--configuration=development', + '--optimization', + '--output-hashing=none', + '--vendor-chunk=false', + ), + ) .then(() => expectFileToMatch('dist/test-project/scripts.js', 'jQuery')) - .then(() => expectFileToMatch('dist/test-project/styles.css', '* Bootstrap')) - .then(() => expectFileToMatch('dist/test-project/index.html', oneLineTrim` - - `)); + .then(() => expectFileToMatch('dist/test-project/styles.css', ':root')) + .then(() => + expectFileToMatch('dist/test-project/index.html', ''), + ); } diff --git a/tests/legacy-cli/e2e/tests/third-party/material-icons.ts b/tests/legacy-cli/e2e/tests/third-party/material-icons.ts deleted file mode 100644 index b8fc94763d25..000000000000 --- a/tests/legacy-cli/e2e/tests/third-party/material-icons.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { expectFileToMatch } from '../../utils/fs'; -import { installPackage } from '../../utils/packages'; -import { ng } from '../../utils/process'; -import { updateJsonFile } from '../../utils/project'; - -export default async function() { - // Install material design icons - await installPackage('material-design-icons@3.0.1'); - - // Add icon stylesheet to application - await updateJsonFile('angular.json', workspaceJson => { - const appArchitect = workspaceJson.projects['test-project'].architect; - appArchitect.build.options.styles = [ - { input: 'node_modules/material-design-icons/iconfont/material-icons.css' }, - ]; - }); - - // Build dev application - await ng('build', '--extract-css', '--configuration=development'); - - // Ensure icons are included - await expectFileToMatch('dist/test-project/styles.css', 'Material Icons'); - - // Build prod application - await ng('build', '--extract-css', '--output-hashing=none'); - - // Ensure icons are included - await expectFileToMatch('dist/test-project/styles.css', 'Material Icons'); -} diff --git a/tests/legacy-cli/e2e/tests/update/update-8.ts b/tests/legacy-cli/e2e/tests/update/update-8.ts deleted file mode 100644 index 11c5a2ca23ec..000000000000 --- a/tests/legacy-cli/e2e/tests/update/update-8.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createProjectFromAsset } from '../../utils/assets'; -import { expectFileMatchToExist } from '../../utils/fs'; -import { installPackage, installWorkspacePackages, setRegistry } from '../../utils/packages'; -import { ng, noSilentNg } from '../../utils/process'; -import { isPrereleaseCli, useCIChrome, useCIDefaults } from '../../utils/project'; - -export default async function () { - const extraUpdateArgs = await isPrereleaseCli() || true ? ['--next', '--force'] : []; - - // We need to use the public registry because in the local NPM server we don't have - // older versions @angular/cli packages which would cause `npm install` during `ng update` to fail. - try { - await createProjectFromAsset('8.0-project', true, true); - - await setRegistry(false); - await installWorkspacePackages(); - - // Update Angular to 9 - await installPackage('@angular/cli@8'); - const { stdout } = await ng('update', '@angular/cli@9.x', '@angular/core@9.x'); - if (!stdout.includes('Executing migrations of package \'@angular/cli\'')) { - throw new Error('Update did not execute migrations. OUTPUT: \n' + stdout); - } - - // Update Angular to 10 - await ng('update', '@angular/cli@10', '@angular/core@10'); - - // Update Angular to 11 - await ng('update', '@angular/cli@11', '@angular/core@11'); - } finally { - await setRegistry(true); - } - - // Update Angular current build - await ng('update', '@angular/cli', '@angular/core', ...extraUpdateArgs); - - // Setup testing to use CI Chrome. - await useCIChrome('./'); - await useCIChrome('./e2e/'); - await useCIDefaults('eight-project'); - - // Run CLI commands. - await ng('generate', 'component', 'my-comp'); - await ng('test', '--watch=false'); - await ng('lint'); - await ng('e2e'); - await ng('e2e', '--prod'); - - // Verify project now creates bundles for differential loading. - await noSilentNg('build', '--prod'); - await expectFileMatchToExist('dist/eight-project/', /main-es5\.[0-9a-f]{20}\.js/); - await expectFileMatchToExist('dist/eight-project/', /main-es2015\.[0-9a-f]{20}\.js/); -} diff --git a/tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts b/tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts new file mode 100644 index 000000000000..3f11413fd39c --- /dev/null +++ b/tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts @@ -0,0 +1,42 @@ +import { createProjectFromAsset } from '../../utils/assets'; +import { setRegistry } from '../../utils/packages'; +import { ng } from '../../utils/process'; +import { isPrereleaseCli } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + let restoreRegistry: (() => Promise) | undefined; + try { + restoreRegistry = await createProjectFromAsset('13.0-project', true); + await setRegistry(true); + + const extraArgs = ['--force']; + if (isPrereleaseCli()) { + extraArgs.push('--next'); + } + + // Update Angular from v13 to 14 + const { stdout } = await ng('update', ...extraArgs); + if (!/@angular\/core\s+13\.\d\.\d+ -> 14\.\d\.\d+\s+ng update @angular\/core@14/.test(stdout)) { + // @angular/core 13.x.x -> 14.x.x ng update @angular/core@14 + throw new Error( + `Output didn't match "@angular/core 13.x.x -> 14.x.x ng update @angular/core@14". OUTPUT: \n` + + stdout, + ); + } + + const { message } = await expectToFail(() => ng('update', '@angular/core', ...extraArgs)); + if ( + !message.includes( + `Updating multiple major versions of '@angular/core' at once is not supported`, + ) + ) { + throw new Error( + `Expected error message to include "Updating multiple major versions of '@angular/core' at once is not supported" but didn't. OUTPUT: \n` + + message, + ); + } + } finally { + await restoreRegistry?.(); + } +} diff --git a/tests/legacy-cli/e2e/tests/update/update-secure-registry.ts b/tests/legacy-cli/e2e/tests/update/update-secure-registry.ts new file mode 100644 index 000000000000..a06929f22fbd --- /dev/null +++ b/tests/legacy-cli/e2e/tests/update/update-secure-registry.ts @@ -0,0 +1,46 @@ +import { exec, ng } from '../../utils/process'; +import { createNpmConfigForAuthentication } from '../../utils/registry'; +import { expectToFail } from '../../utils/utils'; +import { isPrereleaseCli } from '../../utils/project'; +import { getActivePackageManager } from '../../utils/packages'; +import assert from 'node:assert'; + +export default async function () { + // The environment variable has priority over the .npmrc + delete process.env['NPM_CONFIG_REGISTRY']; + const worksMessage = 'We analyzed your package.json'; + + const extraArgs: string[] = []; + if (isPrereleaseCli()) { + extraArgs.push('--next'); + } + + // Valid authentication token + await createNpmConfigForAuthentication(false); + const { stdout: stdout1 } = await ng('update', ...extraArgs); + if (!stdout1.includes(worksMessage)) { + throw new Error(`Expected stdout to contain "${worksMessage}"`); + } + + await createNpmConfigForAuthentication(true); + const { stdout: stdout2 } = await ng('update', ...extraArgs); + if (!stdout2.includes(worksMessage)) { + throw new Error(`Expected stdout to contain "${worksMessage}"`); + } + + // Invalid authentication token + await createNpmConfigForAuthentication(false, true); + await expectToFail(() => ng('update', ...extraArgs)); + + await createNpmConfigForAuthentication(true, true); + await expectToFail(() => ng('update', ...extraArgs)); + + if (getActivePackageManager() === 'yarn') { + // When running `ng update` using yarn (`yarn ng update`), yarn will set the `npm_config_registry` env variable to `https://registry.yarnpkg.com` + // Validate the the registry in the RC is used. + await createNpmConfigForAuthentication(true, true); + + const error = await expectToFail(() => exec('yarn', 'ng', 'update', ...extraArgs)); + assert.match(error.message, /not allowed to access package/); + } +} diff --git a/tests/legacy-cli/e2e/tests/update/update.ts b/tests/legacy-cli/e2e/tests/update/update.ts new file mode 100644 index 000000000000..bb8b9467292d --- /dev/null +++ b/tests/legacy-cli/e2e/tests/update/update.ts @@ -0,0 +1,89 @@ +import { appendFile } from 'fs/promises'; +import { SemVer } from 'semver'; +import { createProjectFromAsset } from '../../utils/assets'; +import { expectFileMatchToExist, readFile } from '../../utils/fs'; +import { getActivePackageManager } from '../../utils/packages'; +import { ng, noSilentNg } from '../../utils/process'; +import { isPrereleaseCli, useCIChrome, useCIDefaults, getNgCLIVersion } from '../../utils/project'; + +export default async function () { + let restoreRegistry: (() => Promise) | undefined; + + try { + // We need to use the public registry because in the local NPM server we don't have + // older versions @angular/cli packages which would cause `npm install` during `ng update` to fail. + restoreRegistry = await createProjectFromAsset('13.0-project', true); + + // If using npm, enable legacy peer deps mode to avoid defects in npm 7+'s peer dependency resolution + // Example error where 11.2.14 satisfies the SemVer range ^11.0.0 but still fails: + // npm ERR! Conflicting peer dependency: @angular/compiler-cli@11.2.14 + // npm ERR! node_modules/@angular/compiler-cli + // npm ERR! peer @angular/compiler-cli@"^11.0.0 || ^11.2.0-next" from @angular-devkit/build-angular@0.1102.19 + // npm ERR! node_modules/@angular-devkit/build-angular + // npm ERR! dev @angular-devkit/build-angular@"~0.1102.19" from the root project + if (getActivePackageManager() === 'npm') { + await appendFile('.npmrc', '\nlegacy-peer-deps=true'); + } + + // CLI project version + const { version: cliVersion } = JSON.parse( + await readFile('./node_modules/@angular/cli/package.json'), + ); + const cliMajorProjectVersion = new SemVer(cliVersion).major; + + // CLI current version. + const cliMajorVersion = getNgCLIVersion().major; + + for (let version = cliMajorProjectVersion + 1; version < cliMajorVersion; version++) { + // Run all the migrations until the current build major version - 1. + // Example: when the project is using CLI version 10 and the build CLI version is 14. + // We will run the following migrations: + // - 10 -> 11 + // - 11 -> 12 + // - 12 -> 13 + const { stdout } = await ng('update', `@angular/cli@${version}`, `@angular/core@${version}`); + if (!stdout.includes("Executing migrations of package '@angular/cli'")) { + throw new Error('Update did not execute migrations for @angular/cli. OUTPUT: \n' + stdout); + } + if (!stdout.includes("Executing migrations of package '@angular/core'")) { + throw new Error('Update did not execute migrations for @angular/core. OUTPUT: \n' + stdout); + } + } + } finally { + await restoreRegistry?.(); + } + + // Update Angular current build + const extraUpdateArgs = isPrereleaseCli() ? ['--next', '--force'] : []; + + // For the latest/next release we purposely don't run `ng update @angular/core`. + + // During a major release when the branch version is bumped from `12.0.0-rc.x` to `12.0.0` there would be a period were in + // the local NPM registry `@angular/cli@latest` will point to `12.0.0`, but on the public NPM repository `@angular/core@latest` will be `11.2.x`. + + // This causes `ng update @angular/core` to fail because of mismatching peer dependencies. + + // The reason for this is because of our bumping and release strategy. When we release a major version on NPM we don't tag it + // `@latest` right away, but we wait for all teams to release their packages before doing so. While this is good because all team + // packages gets tagged with `@latest` at the same time. This is problematic for our CI, since we test against the public NPM repo and are dependent on tags. + + // NB: `ng update @angular/cli` will still cause `@angular/core` packages to be updated therefore we still test updating the core package without running the command. + + await ng('update', '@angular/cli', ...extraUpdateArgs); + + // Setup testing to use CI Chrome. + await useCIChrome('thirteen-project', './'); + await useCIChrome('thirteen-project', './e2e/'); + await useCIDefaults('thirteen-project'); + + // Run CLI commands. + await ng('generate', 'component', 'my-comp'); + await ng('test', '--watch=false'); + + await ng('e2e'); + await ng('e2e', '--configuration=production'); + + // Verify project now creates bundles + await noSilentNg('build', '--configuration=production'); + await expectFileMatchToExist('dist/thirteen-project/', /main\.[0-9a-f]{16}\.js/); +} diff --git a/tests/legacy-cli/e2e/utils/BUILD.bazel b/tests/legacy-cli/e2e/utils/BUILD.bazel new file mode 100644 index 000000000000..7a242b4bd137 --- /dev/null +++ b/tests/legacy-cli/e2e/utils/BUILD.bazel @@ -0,0 +1,27 @@ +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "utils", + testonly = True, + srcs = glob(["**/*.ts"]), + visibility = ["//visibility:public"], + deps = [ + "//tests/legacy-cli/e2e/ng-snapshot", + "@npm//@types/glob", + "@npm//@types/node-fetch", + "@npm//@types/semver", + "@npm//@types/tar", + "@npm//@types/yargs-parser", + "@npm//ansi-colors", + "@npm//glob", + "@npm//npm", + "@npm//protractor", + "@npm//puppeteer", + "@npm//rxjs", + "@npm//semver", + "@npm//tar", + "@npm//tree-kill", + "@npm//verdaccio", + "@npm//verdaccio-auth-memory", + ], +) diff --git a/tests/legacy-cli/e2e/utils/assets.ts b/tests/legacy-cli/e2e/utils/assets.ts index e2d3f2f0a969..4fc9fee81537 100644 --- a/tests/legacy-cli/e2e/utils/assets.ts +++ b/tests/legacy-cli/e2e/utils/assets.ts @@ -1,17 +1,18 @@ import { join } from 'path'; -import * as glob from 'glob'; +import { chmod } from 'fs/promises'; +import glob from 'glob'; import { getGlobalVariable } from './env'; -import { relative, resolve } from 'path'; -import { copyFile, writeFile } from './fs'; -import { installWorkspacePackages } from './packages'; -import { useBuiltPackages } from './project'; +import { resolve } from 'path'; +import { copyFile } from './fs'; +import { installWorkspacePackages, setRegistry } from './packages'; +import { useBuiltPackagesVersions } from './project'; export function assetDir(assetName: string) { return join(__dirname, '../assets', assetName); } export function copyProjectAsset(assetName: string, to?: string) { - const tempRoot = join(getGlobalVariable('tmp-root'), 'test-project'); + const tempRoot = join(getGlobalVariable('projects-root'), 'test-project'); const sourcePath = assetDir(assetName); const targetPath = join(tempRoot, to || assetName); @@ -20,44 +21,47 @@ export function copyProjectAsset(assetName: string, to?: string) { export function copyAssets(assetName: string, to?: string) { const seed = +Date.now(); - const tempRoot = join(getGlobalVariable('tmp-root'), 'assets', assetName + '-' + seed); + const tempRoot = join(getGlobalVariable('projects-root'), 'assets', assetName + '-' + seed); const root = assetDir(assetName); return Promise.resolve() .then(() => { - const allFiles = glob.sync(join(root, '**/*'), { dot: true, nodir: true }); + const allFiles = glob.sync('**/*', { dot: true, nodir: true, cwd: root }); return allFiles.reduce((promise, filePath) => { - const relPath = relative(root, filePath); const toPath = to !== undefined - ? resolve(getGlobalVariable('tmp-root'), 'test-project', to, relPath) - : join(tempRoot, relPath); + ? resolve(getGlobalVariable('projects-root'), 'test-project', to, filePath) + : join(tempRoot, filePath); - return promise.then(() => copyFile(filePath, toPath)); + return promise + .then(() => copyFile(join(root, filePath), toPath)) + .then(() => chmod(toPath, 0o777)); }, Promise.resolve()); }) .then(() => tempRoot); } +/** + * @returns a method that once called will restore the environment + * to use the local NPM registry. + * */ export async function createProjectFromAsset( assetName: string, useNpmPackages = false, skipInstall = false, -) { +): Promise<() => Promise> { const dir = await copyAssets(assetName); process.chdir(dir); + + await setRegistry(!useNpmPackages /** useTestRegistry */); + if (!useNpmPackages) { - await useBuiltPackages(); - if (!getGlobalVariable('ci')) { - const testRegistry = getGlobalVariable('package-registry'); - await writeFile('.npmrc', `registry=${testRegistry}`); - } + await useBuiltPackagesVersions(); } - if (!skipInstall) { await installWorkspacePackages(); } - return dir; + return () => setRegistry(true /** useTestRegistry */); } diff --git a/tests/legacy-cli/e2e/utils/env.ts b/tests/legacy-cli/e2e/utils/env.ts index dd80596f0698..d2f0feece0a7 100644 --- a/tests/legacy-cli/e2e/utils/env.ts +++ b/tests/legacy-cli/e2e/utils/env.ts @@ -1,13 +1,26 @@ -const global: {[name: string]: any} = Object.create(null); - +const ENV_PREFIX = 'LEGACY_CLI__'; export function setGlobalVariable(name: string, value: any) { - global[name] = value; + if (value === undefined) { + delete process.env[ENV_PREFIX + name]; + } else { + process.env[ENV_PREFIX + name] = JSON.stringify(value); + } } -export function getGlobalVariable(name: string): any { - if (!(name in global)) { +export function getGlobalVariable(name: string): T { + const value = process.env[ENV_PREFIX + name]; + if (value === undefined) { throw new Error(`Trying to access variable "${name}" but it's not defined.`); } - return global[name]; + return JSON.parse(value) as T; +} + +export function getGlobalVariablesEnv(): NodeJS.ProcessEnv { + return Object.keys(process.env) + .filter((v) => v.startsWith(ENV_PREFIX)) + .reduce((vars, n) => { + vars[n] = process.env[n]; + return vars; + }, {}); } diff --git a/tests/legacy-cli/e2e/utils/fs.ts b/tests/legacy-cli/e2e/utils/fs.ts index 6b696065b829..fd419b45683e 100644 --- a/tests/legacy-cli/e2e/utils/fs.ts +++ b/tests/legacy-cli/e2e/utils/fs.ts @@ -1,195 +1,120 @@ -import * as fs from 'fs-extra'; -import {dirname} from 'path'; -import {stripIndents} from 'common-tags'; - - -export function readFile(fileName: string) { - return new Promise((resolve, reject) => { - fs.readFile(fileName, 'utf-8', (err: any, data: string) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); -} +import { PathLike, promises as fs, constants } from 'fs'; +import { dirname, join } from 'path'; -export function writeFile(fileName: string, content: string, options?: any) { - return new Promise((resolve, reject) => { - fs.writeFile(fileName, content, options, (err: any) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); +export function readFile(fileName: string): Promise { + return fs.readFile(fileName, 'utf-8'); } - -export function deleteFile(path: string) { - return new Promise((resolve, reject) => { - fs.unlink(path, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); +export function writeFile(fileName: string, content: string, options?: any): Promise { + return fs.writeFile(fileName, content, options); } - -export function rimraf(path: string) { - return new Promise((resolve, reject) => { - fs.remove(path, (err?: any) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); +export function deleteFile(path: string): Promise { + return fs.unlink(path); } - -export function moveFile(from: string, to: string) { - return new Promise((resolve, reject) => { - fs.rename(from, to, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); +export function rimraf(path: string): Promise { + return fs.rm(path, { + force: true, + recursive: true, + maxRetries: 3, }); } - -export function symlinkFile(from: string, to: string, type?: string) { - return new Promise((resolve, reject) => { - fs.symlink(from, to, type, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); +export function moveFile(from: string, to: string): Promise { + return fs.rename(from, to); } -export function createDir(path: string) { - return _recursiveMkDir(path); +export function symlinkFile(from: string, to: string, type?: string): Promise { + return fs.symlink(from, to, type); } - -function _recursiveMkDir(path: string): Promise { - if (fs.existsSync(path)) { - return Promise.resolve(); - } else { - return _recursiveMkDir(dirname(path)) - .then(() => fs.mkdirSync(path)); - } +export function createDir(path: string): Promise { + return fs.mkdir(path, { recursive: true }); } -export function copyFile(from: string, to: string) { - return _recursiveMkDir(dirname(to)) - .then(() => new Promise((resolve, reject) => { - const rd = fs.createReadStream(from); - rd.on('error', (err: Error) => reject(err)); +export async function copyFile(from: string, to: string): Promise { + await createDir(dirname(to)); - const wr = fs.createWriteStream(to); - wr.on('error', (err: Error) => reject(err)); - wr.on('close', () => resolve()); - - rd.pipe(wr); - })); + return fs.copyFile(from, to, constants.COPYFILE_FICLONE); } -export function moveDirectory(from: string, to: string) { - return fs.move(from, to, { overwrite: true }); -} +export async function moveDirectory(from: string, to: string): Promise { + await rimraf(to); + await createDir(to); + for (const entry of await fs.readdir(from)) { + const fromEntry = join(from, entry); + const toEntry = join(to, entry); + if ((await fs.stat(fromEntry)).isFile()) { + await copyFile(fromEntry, toEntry); + } else { + await moveDirectory(fromEntry, toEntry); + } + } +} export function writeMultipleFiles(fs: { [path: string]: string }) { - return Promise.all(Object.keys(fs).map(fileName => writeFile(fileName, fs[fileName]))); + return Promise.all(Object.keys(fs).map((fileName) => writeFile(fileName, fs[fileName]))); } - export function replaceInFile(filePath: string, match: RegExp | string, replacement: string) { - return readFile(filePath) - .then((content: string) => writeFile(filePath, content.replace(match, replacement))); + return readFile(filePath).then((content: string) => + writeFile(filePath, content.replace(match, replacement)), + ); } - export function appendToFile(filePath: string, text: string, options?: any) { - return readFile(filePath) - .then((content: string) => writeFile(filePath, content.concat(text), options)); + return readFile(filePath).then((content: string) => + writeFile(filePath, content.concat(text), options), + ); } - export function prependToFile(filePath: string, text: string, options?: any) { - return readFile(filePath) - .then((content: string) => writeFile(filePath, text.concat(content), options)); + return readFile(filePath).then((content: string) => + writeFile(filePath, text.concat(content), options), + ); } +export async function expectFileMatchToExist(dir: string, regex: RegExp): Promise { + const files = await fs.readdir(dir); + const fileName = files.find((name) => regex.test(name)); -export function expectFileMatchToExist(dir: string, regex: RegExp) { - return new Promise((resolve, reject) => { - const [fileName] = fs.readdirSync(dir).filter(name => name.match(regex)); - if (!fileName) { - reject(new Error(`File ${regex} was expected to exist but not found...`)); - } - resolve(fileName); - }); + if (!fileName) { + throw new Error(`File ${regex} was expected to exist but not found...`); + } + + return fileName; } -export function expectFileNotToExist(fileName: string) { - return new Promise((resolve, reject) => { - fs.exists(fileName, (exist) => { - if (exist) { - reject(new Error(`File ${fileName} was expected not to exist but found...`)); - } else { - resolve(); - } - }); - }); +export async function expectFileNotToExist(fileName: string): Promise { + try { + await fs.access(fileName, constants.F_OK); + } catch { + return; + } + + throw new Error(`File ${fileName} was expected not to exist but found...`); } -export function expectFileToExist(fileName: string) { - return new Promise((resolve, reject) => { - fs.exists(fileName, (exist) => { - if (exist) { - resolve(); - } else { - reject(new Error(`File ${fileName} was expected to exist but not found...`)); - } - }); - }); +export async function expectFileToExist(fileName: string): Promise { + try { + await fs.access(fileName, constants.F_OK); + } catch { + throw new Error(`File ${fileName} was expected to exist but not found...`); + } } -export function expectFileToMatch(fileName: string, regEx: RegExp | string) { - return readFile(fileName) - .then(content => { - if (typeof regEx == 'string') { - if (content.indexOf(regEx) == -1) { - throw new Error(stripIndents`File "${fileName}" did not contain "${regEx}"... - Content: - ${content} - ------ - `); - } - } else { - if (!content.match(regEx)) { - throw new Error(stripIndents`File "${fileName}" did not contain "${regEx}"... - Content: - ${content} - ------ - `); - } - } - }); +export async function expectFileToMatch(fileName: string, regEx: RegExp | string): Promise { + const content = await readFile(fileName); + + const found = typeof regEx === 'string' ? content.includes(regEx) : content.match(regEx); + + if (!found) { + throw new Error( + `File "${fileName}" did not contain "${regEx}"...\nContent:\n${content}\n------`, + ); + } } export async function getFileSize(fileName: string) { @@ -202,6 +127,8 @@ export async function expectFileSizeToBeUnder(fileName: string, sizeInBytes: num const fileSize = await getFileSize(fileName); if (fileSize > sizeInBytes) { - throw new Error(`File "${fileName}" exceeded file size of "${sizeInBytes}". Size is ${fileSize}.`); + throw new Error( + `File "${fileName}" exceeded file size of "${sizeInBytes}". Size is ${fileSize}.`, + ); } } diff --git a/tests/legacy-cli/e2e/utils/git.ts b/tests/legacy-cli/e2e/utils/git.ts index 7da09d308c01..39bb47ce7d52 100644 --- a/tests/legacy-cli/e2e/utils/git.ts +++ b/tests/legacy-cli/e2e/utils/git.ts @@ -1,37 +1,21 @@ -import {git, silentGit} from './process'; +import { git, silentGit } from './process'; - -export function gitClean() { - console.log(' Cleaning git...'); - return silentGit('clean', '-df') - .then(() => silentGit('reset', '--hard')) - .then(() => { - // Checkout missing files - return silentGit('status', '--porcelain') - .then(({ stdout }) => stdout - .split(/[\n\r]+/g) - .filter(line => line.match(/^ D/)) - .map(line => line.replace(/^\s*\S+\s+/, ''))) - .then(files => silentGit('checkout', ...files)); - }) - .then(() => expectGitToBeClean()); +export async function gitClean(): Promise { + await silentGit('clean', '-df'); + await silentGit('reset', '--hard'); } -export function expectGitToBeClean() { - return silentGit('status', '--porcelain') - .then(({ stdout }) => { - if (stdout != '') { - throw new Error('Git repo is not clean...\n' + stdout); - } - }); +export async function expectGitToBeClean(): Promise { + const { stdout } = await silentGit('status', '--porcelain'); + if (stdout != '') { + throw new Error('Git repo is not clean...\n' + stdout); + } } -export function gitCommit(message: string) { - return git('add', '-A') - .then(() => silentGit('status', '--porcelain')) - .then(({ stdout }) => { - if (stdout != '') { - return git('commit', '-am', message); - } - }); +export async function gitCommit(message: string): Promise { + await git('add', '-A'); + const { stdout } = await silentGit('status', '--porcelain'); + if (stdout != '') { + await git('commit', '-am', message); + } } diff --git a/tests/legacy-cli/e2e/utils/http.ts b/tests/legacy-cli/e2e/utils/http.ts deleted file mode 100644 index fa0fbc32c3c5..000000000000 --- a/tests/legacy-cli/e2e/utils/http.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {IncomingMessage} from 'http'; -import * as _request from 'request'; - - -export function request(url: string): Promise { - return new Promise((resolve, reject) => { - let options = { - url: url, - headers: { 'Accept': 'text/html' }, - agentOptions: { rejectUnauthorized: false } - }; - _request(options, (error: any, response: IncomingMessage, body: string) => { - if (error) { - reject(error); - } else if (response.statusCode >= 400) { - reject(new Error(`Requesting "${url}" returned status code ${response.statusCode}.`)); - } else { - resolve(body); - } - }); - }); -} diff --git a/tests/legacy-cli/e2e/utils/network.ts b/tests/legacy-cli/e2e/utils/network.ts new file mode 100644 index 000000000000..6528da8bbfff --- /dev/null +++ b/tests/legacy-cli/e2e/utils/network.ts @@ -0,0 +1,13 @@ +import { AddressInfo, createServer } from 'net'; + +export function findFreePort(): Promise { + return new Promise((resolve, reject) => { + const srv = createServer(); + srv.once('listening', () => { + const port = (srv.address() as AddressInfo).port; + srv.close((e) => (e ? reject(e) : resolve(port))); + }); + srv.once('error', (e) => srv.close(() => reject(e))); + srv.listen(); + }); +} diff --git a/tests/legacy-cli/e2e/utils/packages.ts b/tests/legacy-cli/e2e/utils/packages.ts index a8416c9ae24c..20313d194cbb 100644 --- a/tests/legacy-cli/e2e/utils/packages.ts +++ b/tests/legacy-cli/e2e/utils/packages.ts @@ -1,6 +1,11 @@ import { getGlobalVariable } from './env'; -import { writeFile } from './fs'; -import { ProcessOutput, npm, silentNpm, silentYarn } from './process'; +import { ProcessOutput, silentNpm, silentYarn } from './process'; + +export interface PkgInfo { + readonly name: string; + readonly version: string; + readonly path: string; +} export function getActivePackageManager(): 'npm' | 'yarn' { const value = getGlobalVariable('package-manager'); @@ -11,10 +16,14 @@ export function getActivePackageManager(): 'npm' | 'yarn' { return value || 'npm'; } -export async function installWorkspacePackages(): Promise { +export async function installWorkspacePackages(options?: { force?: boolean }): Promise { switch (getActivePackageManager()) { case 'npm': - await silentNpm('install'); + const npmArgs = ['install']; + if (options?.force) { + npmArgs.push('--force'); + } + await silentNpm(...npmArgs); break; case 'yarn': await silentYarn(); @@ -46,15 +55,7 @@ export async function setRegistry(useTestRegistry: boolean): Promise { ? getGlobalVariable('package-registry') : 'https://registry.npmjs.org/'; - const isCI = getGlobalVariable('ci'); - // Ensure local test registry is used when outside a project - if (isCI) { - // Safe to set a user configuration on CI - await npm('config', 'set', 'registry', url); - } else { - // Yarn does not use the environment variable so an .npmrc file is also required - await writeFile('.npmrc', `registry=${url}`); - process.env['NPM_CONFIG_REGISTRY'] = url; - } + // Yarn supports both `NPM_CONFIG_REGISTRY` and `YARN_REGISTRY`. + process.env['NPM_CONFIG_REGISTRY'] = url; } diff --git a/tests/legacy-cli/e2e/utils/process.ts b/tests/legacy-cli/e2e/utils/process.ts index add60058cb17..541aaf31a6a6 100644 --- a/tests/legacy-cli/e2e/utils/process.ts +++ b/tests/legacy-cli/e2e/utils/process.ts @@ -1,19 +1,22 @@ import * as ansiColors from 'ansi-colors'; -import { SpawnOptions } from "child_process"; +import { spawn, SpawnOptions } from 'child_process'; import * as child_process from 'child_process'; -import { concat, defer, EMPTY, from} from 'rxjs'; -import {repeat, takeLast} from 'rxjs/operators'; -import {getGlobalVariable} from './env'; -import {catchError} from 'rxjs/operators'; -const treeKill = require('tree-kill'); - +import { concat, defer, EMPTY, from } from 'rxjs'; +import { repeat, takeLast } from 'rxjs/operators'; +import { getGlobalVariable, getGlobalVariablesEnv } from './env'; +import { catchError } from 'rxjs/operators'; +import treeKill from 'tree-kill'; +import { delimiter, join, resolve } from 'path'; interface ExecOptions { silent?: boolean; waitForMatch?: RegExp; - env?: { [varname: string]: string }; + env?: NodeJS.ProcessEnv; + stdin?: string; + cwd?: string; } +const NPM_CONFIG_RE = /^(npm_config_|yarn_|no_update_notifier)/i; let _processes: child_process.ChildProcess[] = []; @@ -22,35 +25,39 @@ export type ProcessOutput = { stderr: string; }; - -function _exec(options: ExecOptions, cmd: string, args: string[]): Promise { +function _exec(options: ExecOptions, cmd: string, args: string[]): Promise { // Create a separate instance to prevent unintended global changes to the color configuration - // Create function is not defined in the typings. See: https://github.com/doowb/ansi-colors/pull/44 - const colors = (ansiColors as typeof ansiColors & { create: () => typeof ansiColors }).create(); + const colors = ansiColors.create(); - let stdout = ''; - let stderr = ''; - const cwd = process.cwd(); - const env = options.env; + const cwd = options.cwd ?? process.cwd(); + const env = options.env ?? process.env; console.log( - `==========================================================================================` + `==========================================================================================`, ); - args = args.filter(x => x !== undefined); + // Ensure the custom npm and yarn global bin is on the PATH + // https://docs.npmjs.com/cli/v8/configuring-npm/folders#executables + const paths = [ + join(getGlobalVariable('yarn-global'), 'bin'), + join(getGlobalVariable('npm-global'), process.platform.startsWith('win') ? '' : 'bin'), + env.PATH || process.env['PATH'], + ].join(delimiter); + + args = args.filter((x) => x !== undefined); const flags = [ options.silent && 'silent', - options.waitForMatch && `matching(${options.waitForMatch})` + options.waitForMatch && `matching(${options.waitForMatch})`, ] - .filter(x => !!x) // Remove false and undefined. + .filter((x) => !!x) // Remove false and undefined. .join(', ') - .replace(/^(.+)$/, ' [$1]'); // Proper formatting. + .replace(/^(.+)$/, ' [$1]'); // Proper formatting. - console.log(colors.blue(`Running \`${cmd} ${args.map(x => `"${x}"`).join(' ')}\`${flags}...`)); + console.log(colors.blue(`Running \`${cmd} ${args.map((x) => `"${x}"`).join(' ')}\`${flags}...`)); console.log(colors.blue(`CWD: ${cwd}`)); - console.log(colors.blue(`ENV: ${JSON.stringify(env)}`)); + const spawnOptions: SpawnOptions = { cwd, - ...env ? { env } : {}, + env: { ...env, PATH: paths }, }; if (process.platform.startsWith('win')) { @@ -60,61 +67,117 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise { - stdout += data.toString('utf-8'); - if (options.silent) { - return; - } - data.toString('utf-8') - .split(/[\n\r]+/) - .filter(line => line !== '') - .forEach(line => console.log(' ' + line)); - }); - childProcess.stderr.on('data', (data: Buffer) => { - stderr += data.toString('utf-8'); - if (options.silent) { - return; - } - data.toString('utf-8') - .split(/[\n\r]+/) - .filter(line => line !== '') - .forEach(line => console.error(colors.yellow(' ' + line))); - }); _processes.push(childProcess); // Create the error here so the stack shows who called this function. - const err = new Error(`Running "${cmd} ${args.join(' ')}" returned error code `); - return new Promise((resolve, reject) => { - childProcess.on('exit', (error: any) => { - _processes = _processes.filter(p => p !== childProcess); + const error = new Error(); + + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + let matched = false; - if (!error) { + // Return log info about the current process status + function envDump() { + return `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`; + } + + childProcess.stdout!.on('data', (data: Buffer) => { + stdout += data.toString('utf-8'); + + if (options.waitForMatch && stdout.match(options.waitForMatch)) { resolve({ stdout, stderr }); - } else { - err.message += `${error}...\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}\n`; - reject(err); + matched = true; + } + + if (options.silent) { + return; } + + data + .toString('utf-8') + .split(/[\n\r]+/) + .filter((line) => line !== '') + .forEach((line) => console.log(' ' + line)); }); - if (options.waitForMatch) { - const match = options.waitForMatch; - childProcess.stdout.on('data', (data: Buffer) => { - if (data.toString().match(match)) { - resolve({ stdout, stderr }); - } - }); - childProcess.stderr.on('data', (data: Buffer) => { - if (data.toString().match(match)) { - resolve({ stdout, stderr }); - } - }); + childProcess.stderr!.on('data', (data: Buffer) => { + stderr += data.toString('utf-8'); + + if (options.waitForMatch && stderr.match(options.waitForMatch)) { + resolve({ stdout, stderr }); + matched = true; + } + + if (options.silent) { + return; + } + + data + .toString('utf-8') + .split(/[\n\r]+/) + .filter((line) => line !== '') + .forEach((line) => console.error(colors.yellow(' ' + line))); + }); + + childProcess.on('close', (code) => { + _processes = _processes.filter((p) => p !== childProcess); + + if (options.waitForMatch && !matched) { + reject( + `Process output didn't match - "${cmd} ${args.join(' ')}": '${ + options.waitForMatch + }': ${code}...\n\n${envDump()}\n`, + ); + return; + } + + if (!code) { + resolve({ stdout, stderr }); + return; + } + + reject(`Process exit error - "${cmd} ${args.join(' ')}": ${code}...\n\n${envDump()}\n`); + }); + + childProcess.on('error', (err) => { + reject(`Process error - "${cmd} ${args.join(' ')}": ${err}...\n\n${envDump()}\n`); + }); + + // Provide input to stdin if given. + if (options.stdin) { + childProcess.stdin!.write(options.stdin); + childProcess.stdin!.end(); } + }).catch((err) => { + error.message = err.toString(); + return Promise.reject(error); }); } -export function waitForAnyProcessOutputToMatch(match: RegExp, - timeout = 30000): Promise { +export function extractNpmEnv() { + return Object.keys(process.env) + .filter((v) => NPM_CONFIG_RE.test(v)) + .reduce((vars, n) => { + vars[n] = process.env[n]; + return vars; + }, {}); +} + +function extractCIEnv(): NodeJS.ProcessEnv { + return Object.keys(process.env) + .filter((v) => v.startsWith('SAUCE_') || v === 'CI' || v === 'CIRCLECI' || v === 'CHROME_BIN') + .reduce((vars, n) => { + vars[n] = process.env[n]; + return vars; + }, {}); +} + +export function waitForAnyProcessOutputToMatch( + match: RegExp, + timeout = 30000, +): Promise { // Race between _all_ processes, and the timeout. First one to resolve/reject wins. const timeoutPromise: Promise = new Promise((_resolve, reject) => { // Wait for 30 seconds and timeout. @@ -124,29 +187,53 @@ export function waitForAnyProcessOutputToMatch(match: RegExp, }); const matchPromises: Promise[] = _processes.map( - childProcess => new Promise(resolve => { - let stdout = ''; - let stderr = ''; - childProcess.stdout.on('data', (data: Buffer) => { - stdout += data.toString(); - if (data.toString().match(match)) { - resolve({ stdout, stderr }); - } - }); - childProcess.stderr.on('data', (data: Buffer) => { - stderr += data.toString(); - if (data.toString().match(match)) { - resolve({ stdout, stderr }); - } - }); - })); + (childProcess) => + new Promise((resolve) => { + let stdout = ''; + let stderr = ''; + + childProcess.stdout!.on('data', (data: Buffer) => { + stdout += data.toString(); + if (stdout.match(match)) { + resolve({ stdout, stderr }); + } + }); + + childProcess.stderr!.on('data', (data: Buffer) => { + stderr += data.toString(); + if (stderr.match(match)) { + resolve({ stdout, stderr }); + } + }); + }), + ); return Promise.race(matchPromises.concat([timeoutPromise])); } -export function killAllProcesses(signal = 'SIGTERM') { - _processes.forEach(process => treeKill(process.pid, signal)); - _processes = []; +export async function killAllProcesses(signal = 'SIGTERM'): Promise { + const processesToKill: Promise[] = []; + + while (_processes.length) { + const childProc = _processes.pop(); + if (!childProc) { + continue; + } + + processesToKill.push( + new Promise((resolve) => { + treeKill(childProc.pid, signal, () => { + // Ignore all errors. + // This is due to a race condition with the `waitForMatch` logic. + // where promises are resolved on matches and not when the process terminates. + // Also in some cases in windows we get `The operation attempted is not supported`. + resolve(); + }); + }), + ); + } + + await Promise.all(processesToKill); } export function exec(cmd: string, ...args: string[]) { @@ -157,11 +244,33 @@ export function silentExec(cmd: string, ...args: string[]) { return _exec({ silent: true }, cmd, args); } -export function execWithEnv(cmd: string, args: string[], env: { [varname: string]: string }) { - return _exec({ env }, cmd, args); +export function execWithEnv(cmd: string, args: string[], env: NodeJS.ProcessEnv, stdin?: string) { + return _exec({ env, stdin }, cmd, args); } -export function execAndWaitForOutputToMatch(cmd: string, args: string[], match: RegExp) { +export async function execAndCaptureError( + cmd: string, + args: string[], + env?: NodeJS.ProcessEnv, + stdin?: string, +): Promise { + try { + await _exec({ env, stdin }, cmd, args); + throw new Error('Tried to capture subprocess exception, but it completed successfully.'); + } catch (err) { + if (err instanceof Error) { + return err; + } + throw new Error('Subprocess exception was not an Error instance'); + } +} + +export function execAndWaitForOutputToMatch( + cmd: string, + args: string[], + match: RegExp, + env?: NodeJS.ProcessEnv, +) { if (cmd === 'ng' && args[0] === 'serve') { // Accept matches up to 20 times after the initial match. // Useful because the Webpack watcher can rebuild a few times due to files changes that @@ -169,18 +278,16 @@ export function execAndWaitForOutputToMatch(cmd: string, args: string[], match: // This seems to be due to host file system differences, see // https://nodejs.org/docs/latest/api/fs.html#fs_caveats return concat( - from( - _exec({ waitForMatch: match }, cmd, args) - ), + from(_exec({ waitForMatch: match, env }, cmd, args)), defer(() => waitForAnyProcessOutputToMatch(match, 2500)).pipe( repeat(20), catchError(() => EMPTY), ), - ).pipe( - takeLast(1), - ).toPromise(); + ) + .pipe(takeLast(1)) + .toPromise(); } else { - return _exec({ waitForMatch: match }, cmd, args); + return _exec({ waitForMatch: match, env }, cmd, args); } } @@ -190,8 +297,7 @@ export function ng(...args: string[]) { if (['build', 'serve', 'test', 'e2e', 'extract-i18n'].indexOf(args[0]) != -1) { if (args[0] == 'e2e') { // Wait 1 second before running any end-to-end test. - return new Promise(resolve => setTimeout(resolve, 1000)) - .then(() => maybeSilentNg(...args)); + return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => maybeSilentNg(...args)); } return maybeSilentNg(...args); @@ -205,15 +311,41 @@ export function noSilentNg(...args: string[]) { } export function silentNg(...args: string[]) { - return _exec({silent: true}, 'ng', args); + return _exec({ silent: true }, 'ng', args); } -export function silentNpm(...args: string[]) { - return _exec({silent: true}, 'npm', args); +export function silentNpm(...args: string[]): Promise; +export function silentNpm(args: string[], options?: { cwd?: string }): Promise; +export function silentNpm( + ...args: string[] | [args: string[], options?: { cwd?: string }] +): Promise { + if (Array.isArray(args[0])) { + const [params, options] = args; + return _exec( + { + silent: true, + cwd: (options as { cwd?: string } | undefined)?.cwd, + }, + 'npm', + params, + ); + } else { + return _exec({ silent: true }, 'npm', args as string[]); + } } export function silentYarn(...args: string[]) { - return _exec({silent: true}, 'yarn', args); + return _exec({ silent: true }, 'yarn', args); +} + +export function globalNpm(args: string[], env?: NodeJS.ProcessEnv) { + if (!process.env.LEGACY_CLI_RUNNER) { + throw new Error( + 'The global npm cli should only be executed from the primary e2e runner process', + ); + } + + return _exec({ silent: true, env }, 'node', [require.resolve('npm'), ...args]); } export function npm(...args: string[]) { @@ -229,5 +361,51 @@ export function git(...args: string[]) { } export function silentGit(...args: string[]) { - return _exec({silent: true}, 'git', args); + return _exec({ silent: true }, 'git', args); +} + +/** + * Launch the given entry in an child process isolated to the test environment. + * + * The test environment includes the local NPM registry, isolated NPM globals, + * the PATH variable only referencing the local node_modules and local NPM + * registry (not the test runner or standard global node_modules). + */ +export async function launchTestProcess(entry: string, ...args: any[]): Promise { + const tempRoot: string = getGlobalVariable('tmp-root'); + + // Extract explicit environment variables for the test process. + const env: NodeJS.ProcessEnv = { + ...extractNpmEnv(), + ...extractCIEnv(), + ...getGlobalVariablesEnv(), + }; + + // Modify the PATH environment variable... + env.PATH = (env.PATH || process.env.PATH) + ?.split(delimiter) + // Only include paths within the sandboxed test environment or external + // non angular-cli paths such as /usr/bin for generic commands. + .filter((p) => p.startsWith(tempRoot) || !p.includes('angular-cli')) + .join(delimiter); + + const testProcessArgs = [resolve(__dirname, 'run_test_process'), entry, ...args]; + + return new Promise((resolve, reject) => { + spawn(process.execPath, testProcessArgs, { + stdio: 'inherit', + env, + }) + .on('close', (code) => { + if (!code) { + resolve(); + return; + } + + reject(`Process error - "${testProcessArgs}`); + }) + .on('error', (err) => { + reject(`Process exit error - "${testProcessArgs}]\n\n${err}`); + }); + }); } diff --git a/tests/legacy-cli/e2e/utils/project.ts b/tests/legacy-cli/e2e/utils/project.ts index e53158b69cb2..d48d6e6340be 100644 --- a/tests/legacy-cli/e2e/utils/project.ts +++ b/tests/legacy-cli/e2e/utils/project.ts @@ -1,117 +1,114 @@ import * as fs from 'fs'; import * as path from 'path'; -import { prerelease } from 'semver'; -import { packages } from '../../../../lib/packages'; +import { prerelease, SemVer } from 'semver'; +import yargsParser from 'yargs-parser'; import { getGlobalVariable } from './env'; import { prependToFile, readFile, replaceInFile, writeFile } from './fs'; import { gitCommit } from './git'; -import { installWorkspacePackages } from './packages'; -import { execAndWaitForOutputToMatch, git, ng } from './process'; - -const tsConfigPath = 'tsconfig.json'; - +import { findFreePort } from './network'; +import { installWorkspacePackages, PkgInfo } from './packages'; +import { exec, execAndWaitForOutputToMatch, git, ng } from './process'; export function updateJsonFile(filePath: string, fn: (json: any) => any | void) { - return readFile(filePath) - .then(tsConfigJson => { - // Remove single and multiline comments - const tsConfig = JSON.parse(tsConfigJson.replace(/\/\*\s(.|\n|\r)*\s\*\/|\/\/.*/g, '')); - const result = fn(tsConfig) || tsConfig; + return readFile(filePath).then((tsConfigJson) => { + // Remove single and multiline comments + const tsConfig = JSON.parse(tsConfigJson.replace(/\/\*\s(.|\n|\r)*\s\*\/|\/\/.*/g, '')); + const result = fn(tsConfig) || tsConfig; - return writeFile(filePath, JSON.stringify(result, null, 2)); - }); + return writeFile(filePath, JSON.stringify(result, null, 2)); + }); } - export function updateTsConfig(fn: (json: any) => any | void) { - return updateJsonFile(tsConfigPath, fn); -} - - -export function ngServe(...args: string[]) { - return execAndWaitForOutputToMatch('ng', - ['serve', ...args], - / Compiled successfully./); + return updateJsonFile('tsconfig.json', fn); } -export async function prepareProjectForE2e(name) { - const argv: string[] = getGlobalVariable('argv'); +export async function ngServe(...args: string[]) { + const port = await findFreePort(); - await git( - 'config', - 'user.email', - 'angular-core+e2e@google.com', - ); - await git( - 'config', - 'user.name', - 'Angular CLI E2e', - ); - await git( - 'config', - 'commit.gpgSign', - 'false', + await execAndWaitForOutputToMatch( + 'ng', + ['serve', '--port', String(port), ...args], + / Compiled successfully./, ); - await useCIChrome( - 'e2e', - ); - await useCIChrome( - '', - ); + return port; +} +export async function prepareProjectForE2e(name: string) { + const argv: yargsParser.Arguments = getGlobalVariable('argv'); - // legacy projects - await useCIChrome( - 'src', - ); + await git('config', 'user.email', 'angular-core+e2e@google.com'); + await git('config', 'user.name', 'Angular CLI E2E'); + await git('config', 'commit.gpgSign', 'false'); + await git('config', 'core.longpaths', 'true'); if (argv['ng-snapshots'] || argv['ng-tag']) { await useSha(); } - console.log( - `Project ${name} created... Installing npm.`, - ); + console.log(`Project ${name} created... Installing packages.`); await installWorkspacePackages(); - await useCIDefaults( - name, - ); + await ng('generate', 'e2e', '--related-app-name', name); + + const protractorPath = require.resolve('protractor'); + const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { + paths: [protractorPath], + }); + const webdriverUpdate = JSON.parse(await readFile(webdriverUpdatePath)) as { + chrome: { last: string }; + }; + + const chromeDriverVersion = webdriverUpdate.chrome.last.match(/chromedriver_([\d|\.]+)/)?.[1]; + if (!chromeDriverVersion) { + throw new Error('Could not extract chrome webdriver version.'); + } + + // Initialize selenium webdriver. + // Often fails the first time so attempt twice if necessary. + const runWebdriverUpdate = () => + exec( + 'node', + 'node_modules/protractor/bin/webdriver-manager', + 'update', + '--standalone', + 'false', + '--gecko', + 'false', + '--versions.chrome', + chromeDriverVersion, + ); + try { + await runWebdriverUpdate(); + } catch { + await runWebdriverUpdate(); + } + + await useCIChrome(name, 'e2e'); + await useCIChrome(name, ''); + await useCIDefaults(name); + // Force sourcemaps to be from the root of the filesystem. - await updateJsonFile( - 'tsconfig.json', - json => { - json[ - 'compilerOptions' - ][ - 'sourceRoot' - ] = - '/'; - }, - ); - await gitCommit( - 'prepare-project-for-e2e', - ); + await updateJsonFile('tsconfig.json', (json) => { + json['compilerOptions']['sourceRoot'] = '/'; + }); + await gitCommit('prepare-project-for-e2e'); } -export function useBuiltPackages() { - return Promise.resolve() - .then(() => updateJsonFile('package.json', json => { - if (!json['dependencies']) { - json['dependencies'] = {}; - } - if (!json['devDependencies']) { - json['devDependencies'] = {}; - } +export function useBuiltPackagesVersions(): Promise { + const packages: { [name: string]: PkgInfo } = getGlobalVariable('package-tars'); - for (const packageName of Object.keys(packages)) { - if (json['dependencies'].hasOwnProperty(packageName) - ) { - json['dependencies'][packageName] = packages[packageName].tar; - } else if (json['devDependencies'].hasOwnProperty(packageName)) { - json['devDependencies'][packageName] = packages[packageName].tar; - } + return updateJsonFile('package.json', (json) => { + json['dependencies'] ??= {}; + json['devDependencies'] ??= {}; + + for (const packageName of Object.keys(packages)) { + if (packageName in json['dependencies']) { + json['dependencies'][packageName] = packages[packageName].version; + } else if (packageName in json['devDependencies']) { + json['devDependencies'][packageName] = packages[packageName].version; } - })); + } + }); } export function useSha() { @@ -122,20 +119,19 @@ export function useSha() { // 6.1.6+4a8d56a const label = argv['ng-tag'] ? argv['ng-tag'] : ''; const ngSnapshotVersions = require('../ng-snapshot/package.json'); - return updateJsonFile('package.json', json => { + return updateJsonFile('package.json', (json) => { // Install over the project with snapshot builds. function replaceDependencies(key: string) { - const missingSnapshots = []; + const missingSnapshots: string[] = []; Object.keys(json[key] || {}) - .filter(name => name.match(/^@angular\//)) - .forEach(name => { + .filter((name) => name.match(/^@angular\//)) + .forEach((name) => { const pkgName = name.split(/\//)[1]; if (pkgName == 'cli') { return; } if (label) { - json[key][`@angular/${pkgName}`] - = `github:angular/${pkgName}-builds${label}`; + json[key][`@angular/${pkgName}`] = `github:angular/${pkgName}-builds${label}`; } else { const replacement = ngSnapshotVersions.dependencies[`@angular/${pkgName}`]; if (!replacement) { @@ -145,8 +141,11 @@ export function useSha() { } }); if (missingSnapshots.length > 0) { - throw new Error('e2e test with --ng-snapshots requires all angular packages be ' + - 'listed in tests/legacy-cli/e2e/ng-snapshot/package.json.\nErrors:\n' + missingSnapshots.join('\n ')); + throw new Error( + 'e2e test with --ng-snapshots requires all angular packages be ' + + 'listed in tests/legacy-cli/e2e/ng-snapshot/package.json.\nErrors:\n' + + missingSnapshots.join('\n '), + ); } } try { @@ -161,83 +160,43 @@ export function useSha() { } } -export function useNgVersion(version: string) { - return updateJsonFile('package.json', json => { - // Install over the project with specific versions. - Object.keys(json['dependencies'] || {}) - .filter(name => name.match(/^@angular\//)) - .forEach(name => { - const pkgName = name.split(/\//)[1]; - if (pkgName == 'cli') { - return; - } - json['dependencies'][`@angular/${pkgName}`] = version; - }); - - Object.keys(json['devDependencies'] || {}) - .filter(name => name.match(/^@angular\//)) - .forEach(name => { - const pkgName = name.split(/\//)[1]; - if (pkgName == 'cli') { - return; - } - json['devDependencies'][`@angular/${pkgName}`] = version; - }); - // Set the correct peer dependencies for @angular/core and @angular/compiler-cli. - // This list should be kept up to date with each major release. - if (version.startsWith('^5')) { - json['devDependencies']['typescript'] = '>=2.4.2 <2.5'; - json['dependencies']['rxjs'] = '^5.5.0'; - json['dependencies']['zone.js'] = '~0.8.4'; - } else if (version.startsWith('^6')) { - json['devDependencies']['typescript'] = '>=2.7.2 <2.8'; - json['dependencies']['rxjs'] = '^6.0.0'; - json['dependencies']['zone.js'] = '~0.8.26'; - } else if (version.startsWith('^7')) { - json['devDependencies']['typescript'] = '>=3.1.1 <3.2'; - json['dependencies']['rxjs'] = '^6.0.0'; - json['dependencies']['zone.js'] = '~0.8.26'; - } - }); -} - -export function useCIDefaults(projectName = 'test-project') { - return updateJsonFile('angular.json', workspaceJson => { +export function useCIDefaults(projectName = 'test-project'): Promise { + return updateJsonFile('angular.json', (workspaceJson) => { // Disable progress reporting on CI to reduce spam. const project = workspaceJson.projects[projectName]; const appTargets = project.targets || project.architect; appTargets.build.options.progress = false; appTargets.test.options.progress = false; - // Disable auto-updating webdriver in e2e. if (appTargets.e2e) { + // Disable auto-updating webdriver in e2e. appTargets.e2e.options.webdriverUpdate = false; + // Use a random port in e2e. + appTargets.e2e.options.port = 0; } - // legacy project structure - const e2eProject = workspaceJson.projects[projectName + '-e2e']; - if (e2eProject) { - const e2eTargets = e2eProject.targets || e2eProject.architect; - e2eTargets.e2e.options.webdriverUpdate = false; + if (appTargets.serve) { + // Use a random port in serve. + appTargets.serve.options ??= {}; + appTargets.serve.options.port = 0; } }); } -export async function useCIChrome(projectDir: string = ''): Promise { +export async function useCIChrome(projectName: string, projectDir = ''): Promise { const protractorConf = path.join(projectDir, 'protractor.conf.js'); - const karmaConf = path.join(projectDir, 'karma.conf.js'); - const chromePath = require('puppeteer').executablePath(); - const protractorPath = require.resolve('protractor'); - const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { - paths: [protractorPath], - }); - const webdriverUpdate = JSON.parse(await readFile(webdriverUpdatePath)) as { - chrome: { last: string }; - }; - const chromeDriverPath = webdriverUpdate.chrome.last; // Use Puppeteer in protractor if a config is found on the project. if (fs.existsSync(protractorConf)) { + const protractorPath = require.resolve('protractor'); + const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { + paths: [protractorPath], + }); + const webdriverUpdate = JSON.parse(await readFile(webdriverUpdatePath)) as { + chrome: { last: string }; + }; + const chromeDriverPath = webdriverUpdate.chrome.last; + await replaceInFile( protractorConf, `browserName: 'chrome'`, @@ -254,16 +213,20 @@ export async function useCIChrome(projectDir: string = ''): Promise { ); } - // Use Puppeteer in karma if a config is found on the project. - if (fs.existsSync(karmaConf)) { - await prependToFile(karmaConf, `process.env.CHROME_BIN = String.raw\`${chromePath}\`;`); - await replaceInFile(karmaConf, `browsers: ['Chrome']`, `browsers: ['ChromeHeadless']`); - } + // Use ChromeHeadless. + return updateJsonFile('angular.json', (workspaceJson) => { + const project = workspaceJson.projects[projectName]; + const appTargets = project.targets || project.architect; + appTargets.test.options.browsers = 'ChromeHeadless'; + }); } -export async function isPrereleaseCli() { - const angularCliPkgJson = JSON.parse(await readFile('node_modules/@angular/cli/package.json')); - const pre = prerelease(angularCliPkgJson.version); +export function getNgCLIVersion(): SemVer { + const packages: { [name: string]: PkgInfo } = getGlobalVariable('package-tars'); + + return new SemVer(packages['@angular/cli'].version); +} - return pre && pre.length > 0; +export function isPrereleaseCli(): boolean { + return (prerelease(getNgCLIVersion())?.length ?? 0) > 0; } diff --git a/tests/legacy-cli/e2e/utils/registry.ts b/tests/legacy-cli/e2e/utils/registry.ts new file mode 100644 index 000000000000..3cfee5f71405 --- /dev/null +++ b/tests/legacy-cli/e2e/utils/registry.ts @@ -0,0 +1,83 @@ +import { spawn } from 'child_process'; +import { join } from 'path'; +import { getGlobalVariable } from './env'; +import { writeFile, readFile } from './fs'; +import { mktempd } from './utils'; + +export async function createNpmRegistry( + port: number, + httpsPort: number, + withAuthentication = false, +) { + // Setup local package registry + const registryPath = await mktempd('angular-cli-e2e-registry-'); + + let configContent = await readFile( + join(__dirname, '../../', withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'), + ); + configContent = configContent.replace(/\$\{HTTP_PORT\}/g, String(port)); + configContent = configContent.replace(/\$\{HTTPS_PORT\}/g, String(httpsPort)); + await writeFile(join(registryPath, 'verdaccio.yaml'), configContent); + + return spawn('node', [require.resolve('verdaccio/bin/verdaccio'), '-c', './verdaccio.yaml'], { + cwd: registryPath, + stdio: 'inherit', + }); +} + +// Token was generated using `echo -n 'testing:s3cret' | openssl base64`. +const VALID_TOKEN = `dGVzdGluZzpzM2NyZXQ=`; + +export function createNpmConfigForAuthentication( + /** + * When true, the authentication token will be scoped to the registry URL. + * @example + * ```ini + * //localhost:4876/:_auth="dGVzdGluZzpzM2NyZXQ=" + * ``` + * + * When false, the authentication will be added as seperate key. + * @example + * ```ini + * _auth="dGVzdGluZzpzM2NyZXQ="` + * ``` + */ + scopedAuthentication: boolean, + /** When true, an incorrect token is used. Use this to validate authentication failures. */ + invalidToken = false, +): Promise { + const token = invalidToken ? `invalid=` : VALID_TOKEN; + const registry = (getGlobalVariable('package-secure-registry') as string).replace(/^\w+:/, ''); + + return writeFile( + '.npmrc', + scopedAuthentication + ? ` + ${registry}:_auth="${token}" + registry=http:${registry} + ` + : ` + _auth="${token}" + registry=http:${registry} + `, + ); +} + +export function setNpmEnvVarsForAuthentication( + /** When true, an incorrect token is used. Use this to validate authentication failures. */ + invalidToken = false, + /** When true, `YARN_REGISTRY` is used instead of `NPM_CONFIG_REGISTRY`. */ + useYarnEnvVariable = false, +): void { + delete process.env['YARN_REGISTRY']; + delete process.env['NPM_CONFIG_REGISTRY']; + + const registryKey = useYarnEnvVariable ? 'YARN_REGISTRY' : 'NPM_CONFIG_REGISTRY'; + process.env[registryKey] = getGlobalVariable('package-secure-registry'); + + process.env['NPM_CONFIG__AUTH'] = invalidToken ? `invalid=` : VALID_TOKEN; + + // Needed for verdaccio when used with yarn + // https://verdaccio.org/docs/en/cli-registry#yarn + process.env['NPM_CONFIG_ALWAYS_AUTH'] = 'true'; +} diff --git a/tests/legacy-cli/e2e/utils/run_test_process.js b/tests/legacy-cli/e2e/utils/run_test_process.js new file mode 100644 index 000000000000..1a7fa92ccfc7 --- /dev/null +++ b/tests/legacy-cli/e2e/utils/run_test_process.js @@ -0,0 +1,3 @@ +'use strict'; +require('../../../../lib/bootstrap-local'); +require('./test_process'); diff --git a/tests/legacy-cli/e2e/utils/tar.ts b/tests/legacy-cli/e2e/utils/tar.ts new file mode 100644 index 000000000000..9c5fbdb0406e --- /dev/null +++ b/tests/legacy-cli/e2e/utils/tar.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import fs from 'fs'; +import { normalize } from 'path'; +import { Parse } from 'tar'; + +/** + * Extract and return the contents of a single file out of a tar file. + * + * @param tarball the tar file to extract from + * @param filePath the path of the file to extract + * @returns the Buffer of file or an error on fs/tar error or file not found + */ +export async function extractFile(tarball: string, filePath: string): Promise { + return new Promise((resolve, reject) => { + fs.createReadStream(tarball) + .pipe( + new Parse({ + strict: true, + filter: (p) => normalize(p) === normalize(filePath), + // TODO: @types/tar 'entry' does not have ReadEntry.on + onentry: (entry: any) => { + const chunks: Buffer[] = []; + + entry.on('data', (chunk: any) => chunks!.push(chunk)); + entry.on('error', reject); + entry.on('finish', () => resolve(Buffer.concat(chunks!))); + }, + }), + ) + .on('close', () => reject(`${tarball} does not contain ${filePath}`)); + }); +} diff --git a/tests/legacy-cli/e2e/utils/test_process.ts b/tests/legacy-cli/e2e/utils/test_process.ts new file mode 100644 index 000000000000..10e41eb17b29 --- /dev/null +++ b/tests/legacy-cli/e2e/utils/test_process.ts @@ -0,0 +1,23 @@ +import { killAllProcesses } from './process'; + +const testScript: string = process.argv[2]; +const testModule = require(testScript); +const testFunction: () => Promise | void = + typeof testModule == 'function' + ? testModule + : typeof testModule.default == 'function' + ? testModule.default + : () => { + throw new Error('Invalid test module.'); + }; + +(async () => { + try { + await testFunction(); + } catch (e) { + console.error('Test Process error', e); + process.exitCode = -1; + } finally { + await killAllProcesses(); + } +})(); diff --git a/tests/legacy-cli/e2e/utils/utils.ts b/tests/legacy-cli/e2e/utils/utils.ts index 3f73ec59ba94..6e9c8da3e756 100644 --- a/tests/legacy-cli/e2e/utils/utils.ts +++ b/tests/legacy-cli/e2e/utils/utils.ts @@ -1,12 +1,21 @@ +import assert from 'assert'; +import { mkdtemp, realpath, rm } from 'fs/promises'; +import { tmpdir } from 'os'; +import path from 'path'; -export function expectToFail(fn: () => Promise, errorMessage?: string): Promise { - return fn() - .then(() => { +export function expectToFail(fn: () => Promise, errorMessage?: string): Promise { + return fn().then( + () => { const functionSource = fn.name || (fn).source || fn.toString(); const errorDetails = errorMessage ? `\n\tDetails:\n\t${errorMessage}` : ''; throw new Error( - `Function ${functionSource} was expected to fail, but succeeded.${errorDetails}`); - }, (err) => { return err; }); + `Function ${functionSource} was expected to fail, but succeeded.${errorDetails}`, + ); + }, + (err) => { + return err instanceof Error ? err : new Error(err); + }, + ); } export function wait(msecs: number): Promise { @@ -14,3 +23,30 @@ export function wait(msecs: number): Promise { setTimeout(resolve, msecs); }); } + +export async function mktempd(prefix: string): Promise { + return realpath(await mkdtemp(path.join(tmpdir(), prefix))); +} + +export async function mockHome(cb: (home: string) => Promise): Promise { + const tempHome = await mktempd('angular-cli-e2e-home-'); + + const oldHome = process.env.HOME; + process.env.HOME = tempHome; + + try { + await cb(tempHome); + } finally { + process.env.HOME = oldHome; + + await rm(tempHome, { recursive: true, force: true }); + } +} + +export function assertIsError(value: unknown): asserts value is Error & { code?: string } { + const isError = + value instanceof Error || + // The following is needing to identify errors coming from RxJs. + (typeof value === 'object' && value && 'name' in value && 'message' in value); + assert(isError, 'catch clause variable is not an Error instance'); +} diff --git a/tests/legacy-cli/e2e/utils/version.ts b/tests/legacy-cli/e2e/utils/version.ts index 0be47e2216e3..0ad0150d3483 100644 --- a/tests/legacy-cli/e2e/utils/version.ts +++ b/tests/legacy-cli/e2e/utils/version.ts @@ -1,9 +1,10 @@ import * as fs from 'fs'; import * as semver from 'semver'; - export function readNgVersion(): string { - const packageJson: any = JSON.parse(fs.readFileSync('./node_modules/@angular/core/package.json', 'utf8')); + const packageJson: any = JSON.parse( + fs.readFileSync('./node_modules/@angular/core/package.json', 'utf8'), + ); return packageJson['version']; } diff --git a/tests/legacy-cli/e2e_runner.ts b/tests/legacy-cli/e2e_runner.ts index 182bc2edb714..fa5ecd74bb23 100644 --- a/tests/legacy-cli/e2e_runner.ts +++ b/tests/legacy-cli/e2e_runner.ts @@ -1,16 +1,18 @@ -// This may seem awkward but we're using Logger in our e2e. At this point the unit tests -// have run already so it should be "safe", teehee. -import { logging } from '@angular-devkit/core'; -import { createConsoleLogger } from '@angular-devkit/core/node'; +import { logging } from '../../packages/angular_devkit/core/src'; +import { createConsoleLogger } from '../../packages/angular_devkit/core/node'; import * as colors from 'ansi-colors'; -import { spawn } from 'child_process'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as minimist from 'minimist'; -import * as os from 'os'; +import glob from 'glob'; +import yargsParser from 'yargs-parser'; import * as path from 'path'; -import { setGlobalVariable } from './e2e/utils/env'; +import { getGlobalVariable, setGlobalVariable } from './e2e/utils/env'; import { gitClean } from './e2e/utils/git'; +import { createNpmRegistry } from './e2e/utils/registry'; +import { launchTestProcess } from './e2e/utils/process'; +import { join } from 'path'; +import { findFreePort } from './e2e/utils/network'; +import { extractFile } from './e2e/utils/tar'; +import { realpathSync } from 'fs'; +import { PkgInfo } from './e2e/utils/packages'; Error.stackTraceLimit = Infinity; @@ -20,14 +22,11 @@ Error.stackTraceLimit = Infinity; * Here's a short description of those flags: * --debug If a test fails, block the thread so the temporary directory isn't deleted. * --noproject Skip creating a project or using one. - * --nobuild Skip building the packages. Use with --noglobal and --reuse to quickly - * rerun tests. * --noglobal Skip linking your local @angular/cli directory. Can save a few seconds. * --nosilent Never silence ng commands. * --ng-tag=TAG Use a specific tag for build snapshots. Similar to ng-snapshots but point to a - * tag instead of using the latest master. + * tag instead of using the latest `main`. * --ng-snapshots Install angular snapshot builds in the test project. - * --ve Use the View Engine compiler. * --glob Run tests matching this glob pattern (relative to tests/e2e/). * --ignore Ignore tests matching this glob pattern. * --reuse=/path Use a path instead of create a new project. That project should have been @@ -36,13 +35,33 @@ Error.stackTraceLimit = Infinity; * --nb-shards Total number of shards that this is part of. Default is 2 if --shard is * passed in. * --shard Index of this processes' shard. - * --devkit=path Path to the devkit to use. The devkit will be built prior to running. * --tmpdir=path Override temporary directory to use for new projects. + * --yarn Use yarn as package manager. + * --package=path An npm package to be published before running tests + * * If unnamed flags are passed in, the list of tests will be filtered to include only those passed. */ -const argv = minimist(process.argv.slice(2), { - boolean: ['debug', 'ng-snapshots', 'noglobal', 'nosilent', 'noproject', 'verbose'], +const argv = yargsParser(process.argv.slice(2), { + boolean: [ + 'debug', + 'esbuild', + 'ng-snapshots', + 'noglobal', + 'nosilent', + 'noproject', + 'verbose', + 'yarn', + ], string: ['devkit', 'glob', 'ignore', 'reuse', 'ng-tag', 'tmpdir', 'ng-version'], + number: ['nb-shards', 'shard'], + array: ['package'], + configuration: { + 'dot-notation': false, + 'camel-case-expansion': false, + }, + default: { + 'package': ['./dist/_*.tgz'], + }, }); /** @@ -56,12 +75,17 @@ const argv = minimist(process.argv.slice(2), { */ process.exitCode = 255; +/** + * Mark this process as the main e2e_runner + */ +process.env.LEGACY_CLI_RUNNER = '1'; + const logger = createConsoleLogger(argv.verbose, process.stdout, process.stderr, { - info: s => s, - debug: s => s, - warn: s => colors.bold.yellow(s), - error: s => colors.bold.red(s), - fatal: s => colors.bold.red(s), + info: (s) => s, + debug: (s) => s, + warn: (s) => colors.bold.yellow(s), + error: (s) => colors.bold.red(s), + fatal: (s) => colors.bold.red(s), }); const logStack = [logger]; @@ -70,32 +94,45 @@ function lastLogger() { } const testGlob = argv.glob || 'tests/**/*.ts'; -let currentFileName = null; const e2eRoot = path.join(__dirname, 'e2e'); -const allSetups = glob - .sync(path.join(e2eRoot, 'setup/**/*.ts'), { nodir: true }) - .map(name => path.relative(e2eRoot, name)) - .sort(); +const allSetups = glob.sync('setup/**/*.ts', { nodir: true, cwd: e2eRoot }).sort(); +const allInitializers = glob.sync('initialize/**/*.ts', { nodir: true, cwd: e2eRoot }).sort(); const allTests = glob - .sync(path.join(e2eRoot, testGlob), { nodir: true, ignore: argv.ignore }) - .map(name => path.relative(e2eRoot, name)) + .sync(testGlob, { nodir: true, cwd: e2eRoot, ignore: argv.ignore }) // Replace windows slashes. - .map(name => name.replace(/\\/g, '/')) - .sort() - .filter(name => !name.endsWith('/setup.ts')); + .map((name) => name.replace(/\\/g, '/')) + .filter((name) => { + if (name.endsWith('/setup.ts')) { + return false; + } + + // The below is to exclude specific tests that are not intented to run for the current package manager. + // This is also important as without the trickery the tests that take the longest ex: update.ts (2.5mins) + // will be executed on the same shard. + const fileName = path.basename(name); + if ( + (fileName.startsWith('yarn-') && !argv.yarn) || + (fileName.startsWith('npm-') && argv.yarn) + ) { + return false; + } + + return true; + }) + .sort(); const shardId = 'shard' in argv ? argv['shard'] : null; const nbShards = (shardId === null ? 1 : argv['nb-shards']) || 2; -const tests = allTests.filter(name => { +const tests = allTests.filter((name) => { // Check for naming tests on command line. if (argv._.length == 0) { return true; } - return argv._.some(argName => { + return argv._.some((argName) => { return ( - path.join(process.cwd(), argName) == path.join(__dirname, 'e2e', name) || + path.join(process.cwd(), argName + '') == path.join(__dirname, 'e2e', name) || argName == name || argName == name.replace(/\.ts$/, '') ); @@ -103,15 +140,22 @@ const tests = allTests.filter(name => { }); // Remove tests that are not part of this shard. -const shardedTests = tests.filter((name, i) => shardId === null || i % nbShards == shardId); -const testsToRun = allSetups.concat(shardedTests); +const testsToRun = tests.filter((name, i) => shardId === null || i % nbShards == shardId); + +if (testsToRun.length === 0) { + if (shardId !== null && tests.length >= shardId ? 1 : 0) { + console.log(`No tests to run on shard ${shardId}, exiting.`); + process.exit(0); + } else { + console.log(`No tests would be ran, aborting.`); + process.exit(1); + } +} -if (shardedTests.length === 0) { - console.log(`No tests would be ran, aborting.`); - process.exit(1); +if (shardId !== null) { + console.log(`Running shard ${shardId} of ${nbShards}`); } -console.log(testsToRun.join('\n')); /** * Load all the files from the e2e, filter and sort them and build a promise of their default * export. @@ -119,114 +163,48 @@ console.log(testsToRun.join('\n')); if (testsToRun.length == allTests.length) { console.log(`Running ${testsToRun.length} tests`); } else { - console.log(`Running ${testsToRun.length} tests (${allTests.length + allSetups.length} total)`); + console.log(`Running ${testsToRun.length} tests (${allTests.length} total)`); } +console.log(['Tests:', ...testsToRun].join('\n ')); + setGlobalVariable('argv', argv); -setGlobalVariable('ci', process.env['CI']?.toLowerCase() === 'true' || process.env['CI'] === '1'); setGlobalVariable('package-manager', argv.yarn ? 'yarn' : 'npm'); -setGlobalVariable('package-registry', 'http://localhost:4873/'); - -// Setup local package registry -const registryPath = - fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'angular-cli-e2e-registry-')); -fs.copyFileSync( - path.join(__dirname, 'verdaccio.yaml'), - path.join(registryPath, 'verdaccio.yaml'), -); -const registryProcess = spawn( - 'node', - [require.resolve('verdaccio/bin/verdaccio'), '-c', './verdaccio.yaml'], - { cwd: registryPath, stdio: 'inherit' }, -); - -testsToRun - .reduce((previous, relativeName, testIndex) => { - // Make sure this is a windows compatible path. - let absoluteName = path.join(e2eRoot, relativeName); - if (/^win/.test(process.platform)) { - absoluteName = absoluteName.replace(/\\/g, path.posix.sep); - } +// This is needed by karma-chrome-launcher +// https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer +process.env['CHROME_BIN'] = require('puppeteer').executablePath(); - return previous.then(() => { - currentFileName = relativeName.replace(/\.ts$/, ''); - const start = +new Date(); - - const module = require(absoluteName); - const fn: (skipClean?: () => void) => Promise | void = - typeof module == 'function' - ? module - : typeof module.default == 'function' - ? module.default - : () => { - throw new Error('Invalid test module.'); - }; - - let clean = true; - let previousDir = null; - - return Promise.resolve() - .then(() => printHeader(currentFileName, testIndex)) - .then(() => (previousDir = process.cwd())) - .then(() => logStack.push(lastLogger().createChild(currentFileName))) - .then(() => fn(() => (clean = false))) - .then( - () => logStack.pop(), - err => { - logStack.pop(); - throw err; - }, - ) - .then(() => console.log('----')) - .then(() => { - // If we're not in a setup, change the directory back to where it was before the test. - // This allows tests to chdir without worrying about keeping the original directory. - if (allSetups.indexOf(relativeName) == -1 && previousDir) { - process.chdir(previousDir); - } - }) - .then(() => { - // Only clean after a real test, not a setup step. Also skip cleaning if the test - // requested an exception. - if (allSetups.indexOf(relativeName) == -1 && clean) { - logStack.push(new logging.NullLogger()); - - return gitClean().then( - () => logStack.pop(), - err => { - logStack.pop(); - throw err; - }, - ); - } - }) - .then( - () => printFooter(currentFileName, start), - err => { - printFooter(currentFileName, start); - console.error(err); - throw err; - }, - ); - }); - }, Promise.resolve()) - .then( - () => { - if (registryProcess) { - registryProcess.kill(); - } +Promise.all([findFreePort(), findFreePort(), findPackageTars()]) + .then(async ([httpPort, httpsPort, packageTars]) => { + setGlobalVariable('package-registry', 'http://localhost/' + httpPort); + setGlobalVariable('package-secure-registry', 'http://localhost/' + httpsPort); + setGlobalVariable('package-tars', packageTars); - console.log(colors.green('Done.')); - process.exit(0); - }, - err => { - console.log('\n'); - console.error(colors.red(`Test "${currentFileName}" failed...`)); - console.error(colors.red(err.message)); - console.error(colors.red(err.stack)); + // NPM registries for the lifetime of the test execution + const registryProcess = await createNpmRegistry(httpPort, httpPort); + const secureRegistryProcess = await createNpmRegistry(httpPort, httpsPort, true); - if (registryProcess) { - registryProcess.kill(); + try { + await runSteps(runSetup, allSetups, 'setup'); + await runSteps(runInitializer, allInitializers, 'initializer'); + await runSteps(runTest, testsToRun, 'test'); + + if (shardId !== null) { + console.log(colors.green(`Done shard ${shardId} of ${nbShards}.`)); + } else { + console.log(colors.green('Done.')); + } + + process.exitCode = 0; + } catch (err) { + if (err instanceof Error) { + console.log('\n'); + console.error(colors.red(err.message)); + if (err.stack) { + console.error(colors.red(err.stack)); + } + } else { + console.error(colors.red(String(err))); } if (argv.debug) { @@ -239,25 +217,127 @@ testsToRun } } - process.exit(1); - }, - ); + process.exitCode = 1; + } finally { + registryProcess.kill(); + secureRegistryProcess.kill(); + } + }) + .catch((err) => { + console.error(colors.red(`Unkown Error: ${err}`)); + process.exitCode = 1; + }); + +async function runSteps( + run: (name: string) => Promise | void, + steps: string[], + type: 'setup' | 'test' | 'initializer', +) { + const capsType = type[0].toUpperCase() + type.slice(1); + + for (const [stepIndex, relativeName] of steps.entries()) { + // Make sure this is a windows compatible path. + let absoluteName = path.join(e2eRoot, relativeName).replace(/\.ts$/, ''); + if (/^win/.test(process.platform)) { + absoluteName = absoluteName.replace(/\\/g, path.posix.sep); + } + + const name = relativeName.replace(/\.ts$/, ''); + const start = Date.now(); + + printHeader(relativeName, stepIndex, steps.length, type); + + // Run the test function with the current file on the logStack. + logStack.push(lastLogger().createChild(absoluteName)); + try { + await run(absoluteName); + } catch (e) { + console.log('\n'); + console.error(colors.red(`${capsType} "${name}" failed...`)); -function printHeader(testName: string, testIndex: number) { - const text = `${testIndex + 1} of ${testsToRun.length}`; - const fullIndex = - (testIndex < allSetups.length - ? testIndex - : (testIndex - allSetups.length) * nbShards + shardId + allSetups.length) + 1; - const length = tests.length + allSetups.length; + throw e; + } finally { + logStack.pop(); + } + + console.log('----'); + printFooter(name, type, start); + } +} + +function runSetup(absoluteName: string): Promise { + const module = require(absoluteName); + + return (typeof module === 'function' ? module : module.default)(); +} + +/** + * Run a file from the projects root directory in a subprocess via launchTestProcess(). + */ +function runInitializer(absoluteName: string): Promise { + process.chdir(getGlobalVariable('projects-root')); + + return launchTestProcess(absoluteName); +} + +/** + * Run a file from the main 'test-project' directory in a subprocess via launchTestProcess(). + */ +async function runTest(absoluteName: string): Promise { + process.chdir(join(getGlobalVariable('projects-root'), 'test-project')); + + await launchTestProcess(absoluteName); + await gitClean(); +} + +function printHeader( + testName: string, + testIndex: number, + count: number, + type: 'setup' | 'initializer' | 'test', +) { + const text = `${testIndex + 1} of ${count}`; + const fullIndex = testIndex * nbShards + shardId + 1; const shard = - shardId === null ? '' : colors.yellow(` [${shardId}:${nbShards}]` + colors.bold(` (${fullIndex}/${length})`)); - console.log(colors.green(`Running "${colors.bold.blue(testName)}" (${colors.bold.white(text)}${shard})...`)); + shardId === null || type !== 'test' + ? '' + : colors.yellow(` [${shardId}:${nbShards}]` + colors.bold(` (${fullIndex}/${tests.length})`)); + console.log( + colors.green( + `Running ${type} "${colors.bold.blue(testName)}" (${colors.bold.white(text)}${shard})...`, + ), + ); } -function printFooter(testName: string, startTime: number) { +function printFooter(testName: string, type: 'setup' | 'initializer' | 'test', startTime: number) { + const capsType = type[0].toUpperCase() + type.slice(1); + // Round to hundredth of a second. const t = Math.round((Date.now() - startTime) / 10) / 100; - console.log(colors.green('Last step took ') + colors.bold.blue('' + t) + colors.green('s...')); + console.log( + colors.green(`${capsType} "${colors.bold.blue(testName)}" took `) + + colors.bold.blue('' + t) + + colors.green('s...'), + ); console.log(''); } + +// Collect the packages passed as arguments and return as {package-name => pkg-path} +async function findPackageTars(): Promise<{ [pkg: string]: PkgInfo }> { + const pkgs: string[] = (getGlobalVariable('argv').package as string[]).flatMap((p) => + glob.sync(p, { realpath: true }), + ); + + const pkgJsons = await Promise.all(pkgs.map((pkg) => extractFile(pkg, './package/package.json'))); + + return pkgs.reduce((all, pkg, i) => { + const json = pkgJsons[i].toString('utf8'); + const { name, version } = JSON.parse(json); + if (!name) { + throw new Error(`Package ${pkg} - package.json name/version not found`); + } + + all[name] = { path: realpathSync(pkg), name, version }; + return all; + }, {} as { [pkg: string]: PkgInfo }); +} diff --git a/tests/legacy-cli/tsconfig.json b/tests/legacy-cli/tsconfig.json new file mode 100644 index 000000000000..235d85b0bb7c --- /dev/null +++ b/tests/legacy-cli/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig-test.json", + "compilerOptions": { "paths": {} }, + "exclude": ["e2e/assets/**"] +} diff --git a/tests/legacy-cli/verdaccio.yaml b/tests/legacy-cli/verdaccio.yaml index f19bc9332694..1352103a3dcc 100644 --- a/tests/legacy-cli/verdaccio.yaml +++ b/tests/legacy-cli/verdaccio.yaml @@ -3,7 +3,7 @@ storage: ./storage auth: auth-memory: users: {} - +listen: localhost:${HTTP_PORT} uplinks: npmjs: url: https://registry.npmjs.org/ @@ -17,10 +17,10 @@ uplinks: maxFreeSockets: 8 packages: - '@angular/{cli,pwa}': + '@angular/{create,cli,pwa}': access: $all publish: $all - + '@angular-devkit/*': access: $all publish: $all @@ -42,8 +42,10 @@ packages: proxy: npmjs logs: - - {type: stdout, format: pretty, level: warn} + type: stdout + format: pretty + level: warn # https://github.com/verdaccio/verdaccio/issues/301 server: - keepAliveTimeout: 0 \ No newline at end of file + keepAliveTimeout: 0 diff --git a/tests/legacy-cli/verdaccio_auth.yaml b/tests/legacy-cli/verdaccio_auth.yaml new file mode 100644 index 000000000000..e230030b1095 --- /dev/null +++ b/tests/legacy-cli/verdaccio_auth.yaml @@ -0,0 +1,33 @@ +storage: ./storage +auth: + auth-memory: + users: + testing: + name: testing + password: s3cret +listen: localhost:${HTTPS_PORT} +uplinks: + local: + url: http://localhost:${HTTP_PORT} + cache: false + maxage: 20m + max_fails: 32 + timeout: 60s + agent_options: + keepAlive: true + maxSockets: 32 + maxFreeSockets: 8 + +packages: + '**': + access: $authenticated + proxy: local + +logs: + type: stdout + format: pretty + level: warn + +# https://github.com/verdaccio/verdaccio/issues/301 +server: + keepAliveTimeout: 0 diff --git a/tests/schematics/update/packages/update-migrations/v1_5.js b/tests/schematics/update/packages/update-migrations/v1_5.js index 7e79eda93d49..0be7c20c1fff 100644 --- a/tests/schematics/update/packages/update-migrations/v1_5.js +++ b/tests/schematics/update/packages/update-migrations/v1_5.js @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + exports.default = function() { return function(tree) { tree.create('/version1_5', ''); diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 379eb2e4f008..c8cd4ad1ba77 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -7,6 +7,10 @@ load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") package(default_visibility = ["//visibility:public"]) +exports_files([ + "package_json_release_filter.jq", +]) + nodejs_binary( name = "ng_cli_schema", data = [ @@ -24,25 +28,4 @@ nodejs_binary( entry_point = "quicktype_runner.js", templated_args = ["--bazel_patch_module_resolver"], ) - -platform( - name = "rbe_ubuntu1604-angular", - parents = ["@rbe_ubuntu1604_angular//config:platform"], - remote_execution_properties = """ - {PARENT_REMOTE_EXECUTION_PROPERTIES} - properties: { - name: "dockerAddCapabilities" - value: "SYS_ADMIN" - } - properties: { - name: "dockerNetwork" - value: "standard" - } - properties: { - name: "Pool" - value: "default" - } - """, -) - # @external_end diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 29a0d95baec9..50c21b3c82db 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -1,7 +1,17 @@ """Re-export of some bazel rules with repository-wide defaults.""" -load("@npm//@bazel/typescript:index.bzl", _ts_library = "ts_library") +load("@npm//@bazel/concatjs/internal:build_defs.bzl", _ts_library = "ts_library_macro") +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin", _js_library = "js_library", _pkg_npm = "pkg_npm") +load("@rules_pkg//:pkg.bzl", "pkg_tar") +load("@npm//@angular/build-tooling/bazel:extract_js_module_output.bzl", "extract_js_module_output") +load("@aspect_bazel_lib//lib:utils.bzl", "to_label") +load("@aspect_bazel_lib//lib:jq.bzl", "jq") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("//tools:link_package_json_to_tarballs.bzl", "link_package_json_to_tarballs") +load("//tools:snapshot_repo_filter.bzl", "SNAPSHOT_REPO_JQ_FILTER") +load("//:constants.bzl", "RELEASE_ENGINES_NODE", "RELEASE_ENGINES_NPM", "RELEASE_ENGINES_YARN") +_DEFAULT_TSCONFIG = "//:tsconfig-build.json" _DEFAULT_TSCONFIG_TEST = "//:tsconfig-test.json" def ts_library( @@ -17,8 +27,11 @@ def ts_library( # Match the types[] in //packages:tsconfig-test.json deps.append("@npm//@types/jasmine") deps.append("@npm//@types/node") - if not tsconfig and testonly: - tsconfig = _DEFAULT_TSCONFIG_TEST + if not tsconfig: + if testonly: + tsconfig = _DEFAULT_TSCONFIG_TEST + else: + tsconfig = _DEFAULT_TSCONFIG if not devmode_module: devmode_module = "commonjs" @@ -36,3 +49,153 @@ def ts_library( # @external_end **kwargs ) + +js_library = _js_library + +def pkg_npm(name, pkg_deps = [], use_prodmode_output = False, **kwargs): + """Override of pkg_npm to produce package outputs and version substitutions conventional to the angular-cli project. + + Produces a package and a tar of that package. Expects a package.json file + in the same folder to exist. + + Args: + name: Name of the pkg_npm rule. '_archive.tar.gz' is appended to create the tarball. + pkg_deps: package.json files of dependent packages. These are used for local path substitutions when --config=local is set. + use_prodmode_output: False to ship ES5 devmode output, True to ship ESM output. Defaults to False. + **kwargs: Additional arguments passed to the real pkg_npm. + """ + pkg_json = ":package.json" + + visibility = kwargs.pop("visibility", None) + + NPM_PACKAGE_SUBSTITUTIONS = { + # Version of the local package being built, generated via the `--workspace_status_command` flag. + "0.0.0-PLACEHOLDER": "{BUILD_SCM_VERSION}", + "0.0.0-EXPERIMENTAL-PLACEHOLDER": "{BUILD_SCM_EXPERIMENTAL_VERSION}", + "BUILD_SCM_HASH-PLACEHOLDER": "{BUILD_SCM_ABBREV_HASH}", + "0.0.0-ENGINES-NODE": RELEASE_ENGINES_NODE, + "0.0.0-ENGINES-NPM": RELEASE_ENGINES_NPM, + "0.0.0-ENGINES-YARN": RELEASE_ENGINES_YARN, + } + + NO_STAMP_PACKAGE_SUBSTITUTIONS = dict(NPM_PACKAGE_SUBSTITUTIONS, **{ + "0.0.0-PLACEHOLDER": "0.0.0", + "0.0.0-EXPERIMENTAL-PLACEHOLDER": "0.0.0", + }) + + deps = kwargs.pop("deps", []) + + # The `pkg_npm` rule brings in devmode (`JSModuleInfo`) and prodmode (`JSEcmaScriptModuleInfo`) + # output into the the NPM package We do not intend to ship the prodmode ECMAScript `.mjs` + # files, but the `JSModuleInfo` outputs (which correspond to devmode output). Depending on + # the `use_prodmode_output` macro attribute, we either ship the ESM output of dependencies, + # or continue shipping the devmode ES5 output. + # TODO: Clean this up in the future if we have combined devmode and prodmode output. + # https://github.com/bazelbuild/rules_nodejs/commit/911529fd364eb3ee1b8ecdc568a9fcf38a8b55ca. + # https://github.com/bazelbuild/rules_nodejs/blob/stable/packages/typescript/internal/build_defs.bzl#L334-L337. + extract_js_module_output( + name = "%s_js_module_output" % name, + provider = "JSEcmaScriptModuleInfo" if use_prodmode_output else "JSModuleInfo", + include_declarations = True, + include_default_files = True, + forward_linker_mappings = False, + include_external_npm_packages = False, + deps = deps, + ) + + # Merge package.json with root package.json and perform various substitutions to + # prepare it for release. For jq docs, see https://stedolan.github.io/jq/manual/. + jq( + name = "basic_substitutions", + # Note: this jq filter relies on the order of the inputs + # buildifier: do not sort + srcs = ["//:package.json", pkg_json], + filter_file = "//tools:package_json_release_filter.jq", + args = ["--slurp"], + out = "substituted/package.json", + ) + + # Copy package.json files to bazel-out so we can use their bazel-out paths to determine + # the corresponding package npm package tar.gz path for substitutions. + copy_to_bin( + name = "package_json_copy", + srcs = [pkg_json], + ) + pkg_deps_copies = [] + for pkg_dep in pkg_deps: + pkg_label = to_label(pkg_dep) + if pkg_label.name != "package.json": + fail("ERROR: only package.json files allowed in pkg_deps of pkg_npm macro") + pkg_deps_copies.append("@%s//%s:package_json_copy" % (pkg_label.workspace_name, pkg_label.package)) + + # Substitute dependencies on other packages in this repo with tarballs. + link_package_json_to_tarballs( + name = "tar_substitutions", + src = "substituted/package.json", + pkg_deps = [":package_json_copy"] + pkg_deps_copies, + out = "substituted_with_tars/package.json", + ) + + # Substitute dependencies on other packages in this repo with snapshot repos. + jq( + name = "snapshot_repo_substitutions", + srcs = ["substituted/package.json"], + filter = SNAPSHOT_REPO_JQ_FILTER, + out = "substituted_with_snapshot_repos/package.json", + ) + + # Move the generated package.json along with other deps into a directory for pkg_npm + # to package up because pkg_npm requires that all inputs be in the same directory. + copy_to_directory( + name = "package", + srcs = select({ + # Do tar substitution if config_setting 'package_json_use_tar_deps' is true (local builds) + "//:package_json_use_tar_deps": [":%s_js_module_output" % name, "substituted_with_tars/package.json"], + "//:package_json_use_snapshot_repo_deps": [":%s_js_module_output" % name, "substituted_with_snapshot_repos/package.json"], + "//conditions:default": [":%s_js_module_output" % name, "substituted/package.json"], + }), + replace_prefixes = { + "substituted_with_tars/": "", + "substituted_with_snapshot_repos/": "", + "substituted/": "", + }, + exclude_srcs_patterns = [ + "packages/**/*", # Exclude compiled outputs of dependent packages + ], + allow_overwrites = True, + ) + + _pkg_npm( + name = name, + # We never set a `package_name` for NPM packages, neither do we enable validation. + # This is necessary because the source targets of the NPM packages all have + # package names set and setting a similar `package_name` on the NPM package would + # result in duplicate linker mappings that will conflict. e.g. consider the following + # scenario: We have a `ts_library` for `@angular/core`. We will configure a package + # name for the target so that it can be resolved in NodeJS executions from `node_modules`. + # If we'd also set a `package_name` for the associated `pkg_npm` target, there would be + # two mappings for `@angular/core` and the linker will complain. For a better development + # experience, we want the mapping to resolve to the direct outputs of the `ts_library` + # instead of requiring tests and other targets to assemble the NPM package first. + # TODO(devversion): consider removing this if `rules_nodejs` allows for duplicate + # linker mappings where transitive-determined mappings are skipped on conflicts. + # https://github.com/bazelbuild/rules_nodejs/issues/2810. + package_name = None, + validate = False, + substitutions = select({ + "//:stamp": NPM_PACKAGE_SUBSTITUTIONS, + "//conditions:default": NO_STAMP_PACKAGE_SUBSTITUTIONS, + }), + visibility = visibility, + nested_packages = ["package"], + tgz = None, + **kwargs + ) + + pkg_tar( + name = name + "_archive", + srcs = [":%s" % name], + extension = "tar.gz", + strip_prefix = "./%s" % name, + visibility = visibility, + ) diff --git a/tools/link_package_json_to_tarballs.bzl b/tools/link_package_json_to_tarballs.bzl new file mode 100644 index 000000000000..809e3a50add9 --- /dev/null +++ b/tools/link_package_json_to_tarballs.bzl @@ -0,0 +1,87 @@ +# Copyright Google Inc. All Rights Reserved. +# +# Use of this source code is governed by an MIT-style license that can be +# found in the LICENSE file at https://angular.io/license +load("@aspect_bazel_lib//lib:jq.bzl", "jq") +load("@aspect_bazel_lib//lib:utils.bzl", "to_label") + +def link_package_json_to_tarballs(name, src, pkg_deps, out): + """Substitute tar paths into a package.json file for the packages it depends on. + + src and pkg_deps must be labels in the bazel-out tree for the derived path to the npm_package_archive.tar.gz to be correct. + + Args: + name: Name of the rule + src: package.json file to perform substitions on + pkg_deps: package.json files of dependencies to substitute + out: Output package.json file + """ + + src_pkg = to_label(src).package + + # Generate partial jq filters for each dependent package that, when run + # against a package.json file, can replace its dependency with a tar path. + filter_files = [] + for i, pkg_dep in enumerate(pkg_deps): + pkg_dep_name = "%s_%s.name" % (name, i) + pkg_dep_filter = "%s_%s.filter" % (name, i) + jq( + name = "%s_%s_name" % (name, i), + srcs = [pkg_dep], + filter = ".name", + out = pkg_dep_name, + ) + + srcs = [ + pkg_dep_name, + pkg_dep, + ] + + # Add dependent tars as srcs to include them in the dependency graph, except + # for the tar for this package as that would create a circular dependency. + pkg_label = to_label(pkg_dep) + if pkg_label.package != src_pkg: + pkg_tar = "@%s//%s:npm_package_archive.tar.gz" % (pkg_label.workspace_name, pkg_label.package) + srcs.append(pkg_tar) + + # Deriving the absolute path to the tar in the execroot requries different + # commands depending on whether or not the action is sandboxed. + abs_path_sandbox = "readlink $(execpath {pkg_dep})".format(pkg_dep = pkg_dep) + abs_path_nosandbox = "(cd $$(dirname $(execpath {pkg_dep})) && pwd)".format(pkg_dep = pkg_dep) + + native.genrule( + name = "%s_%s_filter" % (name, i), + srcs = srcs, + cmd = """ + TAR=$$(dirname $$({abs_path_sandbox} || {abs_path_nosandbox}))/npm_package_archive.tar.gz + PKGNAME=$$(cat $(execpath {pkg_name})) + if [[ "$$TAR" != *bazel-out* ]]; then + echo "ERROR: package.json passed to substitute_tar_deps must be in the output tree. You can use copy_to_bin to copy a source file to the output tree." + exit 1 + fi + echo "(..|objects|select(has($${{PKGNAME}})))[$${{PKGNAME}}] |= \\"file:$${{TAR}}\\"" > $@ + """.format( + pkg_name = pkg_dep_name, + abs_path_sandbox = abs_path_sandbox, + abs_path_nosandbox = abs_path_nosandbox, + ), + outs = [pkg_dep_filter], + ) + filter_files.append(pkg_dep_filter) + + # Combine all of the filter files into a single filter by joining with | + filter = "%s.filter" % name + native.genrule( + name = "%s_filter" % name, + srcs = filter_files, + cmd = "cat $(SRCS) | sed '$$!s#$$# |#' > $@", + outs = [filter], + ) + + # Generate final package.json with tar substitutions using the above filter + jq( + name = name, + srcs = [src], + filter_file = filter, + out = out, + ) diff --git a/tools/ng_cli_schema_generator.bzl b/tools/ng_cli_schema_generator.bzl index 6bffacb296a3..c8904eab7b26 100644 --- a/tools/ng_cli_schema_generator.bzl +++ b/tools/ng_cli_schema_generator.bzl @@ -3,7 +3,6 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.io/license -# @external_begin def _cli_json_schema_interface_impl(ctx): args = [ ctx.files.src[0].path, @@ -36,11 +35,10 @@ cli_json_schema = rule( "_binary": attr.label( default = Label("//tools:ng_cli_schema"), executable = True, - cfg = "host", + cfg = "exec", ), }, outputs = { "json": "%{out}", }, ) -# @external_end diff --git a/tools/ng_cli_schema_generator.js b/tools/ng_cli_schema_generator.js index 8e7a309eb674..99c478eb7472 100644 --- a/tools/ng_cli_schema_generator.js +++ b/tools/ng_cli_schema_generator.js @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + const { readFileSync, writeFileSync, mkdirSync } = require('fs'); const { resolve, dirname } = require('path'); @@ -23,8 +24,8 @@ function generate(inPath, outPath) { const definitionKey = value .replace(/(\.json|src)/g, '') .split(/\\|\/|_|-|\./) - .filter(p => !!p) - .map(s => s.charAt(0).toUpperCase() + s.slice(1)) + .filter((p) => !!p) + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) .join(''); const nestedSchemaPath = resolve(dirname(inPath), value); @@ -38,10 +39,11 @@ function generate(inPath, outPath) { throw new Error(`Error while resolving $ref ${value} in ${nestedSchemaPath}.`); } case '$id': - case '$id': - case '$schema': case 'id': + case '$schema': case 'required': + case 'x-prompt': + case 'x-user-analytics': return undefined; default: return value; @@ -55,12 +57,12 @@ function generate(inPath, outPath) { return key === '' ? { - ...value, - definitions: { - ...value.definitions, - ...nestedDefinitions, + ...value, + definitions: { + ...value.definitions, + ...nestedDefinitions, + }, } - } : value; }); @@ -68,7 +70,22 @@ function generate(inPath, outPath) { outPath = resolve(buildWorkspaceDirectory, outPath); mkdirSync(dirname(outPath), { recursive: true }); - writeFileSync(outPath, JSON.stringify(schemaParsed, undefined, 2)); + writeFileSync( + outPath, + JSON.stringify( + schemaParsed, + (key, value) => { + if (key === 'x-deprecated') { + // Needed for IDEs, and will be replaced to 'deprecated' later on. This must be a boolean. + // https://json-schema.org/draft/2020-12/json-schema-validation.html#name-deprecated + return !!value; + } + + return value; + }, + 2, + ).replace(/"x-deprecated"/g, '"deprecated"'), + ); } if (require.main === module) { @@ -84,9 +101,9 @@ if (require.main === module) { generate(inPath, outPath); } catch (error) { console.error('An error happened:'); - console.error(err); + console.error(error); process.exit(127); } } -exports.generate = generate; \ No newline at end of file +exports.generate = generate; diff --git a/tools/package_json_release_filter.jq b/tools/package_json_release_filter.jq new file mode 100644 index 000000000000..019c5e19a681 --- /dev/null +++ b/tools/package_json_release_filter.jq @@ -0,0 +1,32 @@ +# Copyright Google Inc. All Rights Reserved. +# +# Use of this source code is governed by an MIT-style license that can be +# found in the LICENSE file at https://angular.io/license +# +# This filter combines a subproject package.json with the root package.json +# and performs substitutions to prepare it for release. It should be called +# with the --slurp argument and be passed the root pacakge.json followed by +# the subproject package.json. +# +# See jq docs for filter syntax: https://stedolan.github.io/jq/manual/. + +.[0] as $root +| .[1] as $proj + +# Get the fields from root package.json that should override the project +# package.json, i.e., every field except the following +| ($root + | del(.bin, .description, .dependencies, .name, .main, .peerDependencies, .optionalDependencies, .typings, .version, .private, .workspaces, .resolutions, .scripts, .["ng-update"]) +) as $root_overrides + +# Use the project package.json as a base and override other fields from root +| $proj + $root_overrides + +# Combine keywords from both +| .keywords = ($root.keywords + $proj.keywords | unique) + +# Remove devDependencies +| del(.devDependencies) + +# Add engines; versions substituted via pkg_npm ++ {"engines": {"node": "0.0.0-ENGINES-NODE", "npm": "0.0.0-ENGINES-NPM", "yarn": "0.0.0-ENGINES-YARN"}} \ No newline at end of file diff --git a/tools/quicktype_runner.js b/tools/quicktype_runner.js index d2e37b6fe48d..4464e53cdb06 100644 --- a/tools/quicktype_runner.js +++ b/tools/quicktype_runner.js @@ -1,10 +1,11 @@ /** * @license - * Copyright Google Inc. All Rights Reserved. + * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ + const fs = require('fs'); const path = require('path'); const { @@ -35,9 +36,6 @@ const header = ` // THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE // CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...). -// tslint:disable:no-global-tslint-disable -// tslint:disable - `; // Footer to add to all files. @@ -54,7 +52,7 @@ class FetchingJSONSchemaStore extends JSONSchemaStore { } async fetch(address) { - const URL = require("url"); + const URL = require('url'); const url = URL.parse(address); let content = null; if (url.protocol === 'ng-cli:') { @@ -62,7 +60,7 @@ class FetchingJSONSchemaStore extends JSONSchemaStore { content = fs.readFileSync(filePath, 'utf-8').trim(); } else if (url.hostname) { try { - const fetch = require("node-fetch"); + const fetch = require('node-fetch'); const response = await fetch(address); content = response.text(); } catch (e) { @@ -89,15 +87,14 @@ class FetchingJSONSchemaStore extends JSONSchemaStore { content = appendDeprecatedDescription(content); - return parseJSON(content, "JSON Schema", address); + return parseJSON(content, 'JSON Schema', address); } } - /** * Create the TS file from the schema, and overwrite the outPath (or log). - * @param {string} inPath - * @param {string} outPath + * @param {string} inPath + * @param {string} outPath */ async function main(inPath, outPath) { const content = await generate(inPath); @@ -112,7 +109,6 @@ async function main(inPath, outPath) { fs.writeFileSync(outPath, content, 'utf-8'); } - async function generate(inPath) { // Best description of how to use the API was found at // https://blog.quicktype.io/customizing-quicktype/ @@ -171,7 +167,7 @@ if (require.main === module) { main(argv[0], argv[1]) .then(() => process.exit(0)) - .catch(err => { + .catch((err) => { console.error('An error happened:'); console.error(err); process.exit(127); diff --git a/tools/rebase-pr.js b/tools/rebase-pr.js deleted file mode 100644 index e74b6156d9c0..000000000000 --- a/tools/rebase-pr.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - - // tslint:disable:no-console -// ** IMPORTANT ** -// This script cannot use external dependencies because it needs to run before they are installed. - -const util = require('util'); -const https = require('https'); -const child_process = require('child_process'); -const exec = util.promisify(child_process.exec); - -function determineTargetBranch(repository, prNumber) { - const pullsUrl = `https://api.github.com/repos/${repository}/pulls/${prNumber}`; - // GitHub requires a user agent: https://developer.github.com/v3/#user-agent-required - const options = { headers: { 'User-Agent': repository } }; - - return new Promise((resolve, reject) => { - https.get(pullsUrl, options, (res) => { - const { statusCode } = res; - const contentType = res.headers['content-type']; - - let error; - if (statusCode !== 200) { - error = new Error(`Request Failed.\nStatus Code: ${statusCode}.\nResponse: ${res}.\n' +`); - } else if (!/^application\/json/.test(contentType)) { - error = new Error('Invalid content-type.\n' + - `Expected application/json but received ${contentType}`); - } - if (error) { - reject(error); - res.resume(); - return; - } - - res.setEncoding('utf8'); - let rawData = ''; - res.on('data', (chunk) => { rawData += chunk; }); - res.on('end', () => { - try { - const parsedData = JSON.parse(rawData); - resolve(parsedData['base']['ref']); - } catch (e) { - reject(e); - } - }); - }).on('error', (e) => { - reject(e); - }); - }); -} - -if (process.argv.length != 4) { - console.error(`This script requires the GitHub repository and PR number as arguments.`); - console.error(`Example: node scripts/rebase-pr.js angular/angular 123`); - process.exitCode = 1; - return; -} - -const repository = process.argv[2]; -const prNumber = process.argv[3]; -let targetBranch; - - -return Promise.resolve() - .then(() => { - console.log(`Determining target branch for PR ${prNumber} on ${repository}.`); - return determineTargetBranch(repository, prNumber); - }) - .then(target => { - targetBranch = target; - console.log(`Target branch is ${targetBranch}.`); - }) - .then(() => { - console.log(`Fetching ${targetBranch} from origin.`); - return exec(`git fetch origin ${targetBranch}`); - }) - .then(target => { - console.log(`Rebasing current branch on ${targetBranch}.`); - return exec(`git rebase origin/${targetBranch}`); - }) - .then(() => console.log('Rebase successfull.')) - .catch(err => { - console.log('Failed to rebase on top or target branch.\n'); - console.error(err); - process.exitCode = 1; - }); \ No newline at end of file diff --git a/tools/snapshot_repo_filter.bzl b/tools/snapshot_repo_filter.bzl new file mode 100644 index 000000000000..8e4369806415 --- /dev/null +++ b/tools/snapshot_repo_filter.bzl @@ -0,0 +1,19 @@ +# Copyright Google Inc. All Rights Reserved. +# +# Use of this source code is governed by an MIT-style license that can be +# found in the LICENSE file at https://angular.io/license + +load("//:constants.bzl", "SNAPSHOT_REPOS") + +def _generate_snapshot_repo_filter(): + filter = "" + for (i, pkg_name) in enumerate(SNAPSHOT_REPOS.keys()): + filter += "{sep}(..|objects|select(has(\"{pkg_name}\")))[\"{pkg_name}\"] |= \"github:{snapshot_repo}#BUILD_SCM_HASH-PLACEHOLDER\"\n".format( + sep = "| " if i > 0 else "", + pkg_name = pkg_name, + snapshot_repo = SNAPSHOT_REPOS[pkg_name], + ) + return filter + +# jq filter that replaces package.json dependencies with snapshot repos +SNAPSHOT_REPO_JQ_FILTER = _generate_snapshot_repo_filter() diff --git a/tools/test/BUILD.bazel b/tools/test/BUILD.bazel new file mode 100644 index 000000000000..2e651ae3e654 --- /dev/null +++ b/tools/test/BUILD.bazel @@ -0,0 +1,32 @@ +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") +load("@aspect_bazel_lib//lib:jq.bzl", "jq") + +jq( + name = "final_package_json", + # This jq filter relies on the order of the inputs + # buildifier: do not sort + srcs = [ + "root_package.json", + "project_package.json", + ], + args = [ + "--slurp", + ], + filter_file = "//tools:package_json_release_filter.jq", +) + +# jq outputs CR on windows https://github.com/stedolan/jq/issues/92 +# strip the CRs to do a correct comparison on all platforms +genrule( + name = "final_package_json_cr_stripped", + srcs = [":final_package_json"], + outs = ["final_package_json_cr_stripped.json"], + cmd = "cat $(execpath :final_package_json) | sed \"s#\\r##\" > $@", +) + +# Test correctness of the filter that prepares each project's package.json file for release +diff_test( + name = "package_json_filter_test", + file1 = "expected_package.json", + file2 = ":final_package_json_cr_stripped", +) diff --git a/tools/test/expected_package.json b/tools/test/expected_package.json new file mode 100644 index 000000000000..52e6f6e1ca50 --- /dev/null +++ b/tools/test/expected_package.json @@ -0,0 +1,42 @@ +{ + "name": "project", + "version": "0.0.0-SNAPSHOT", + "description": "Project package.json", + "main": "project/index.js", + "bin": { + "projectfoo": "./bin/project-foo.js" + }, + "keywords": [ + "a", + "b", + "c" + ], + "scripts": { + "build": "node project-build-script" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/angular-cli.git" + }, + "author": "Angular Authors", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular-cli/issues" + }, + "homepage": "https://github.com/angular/angular-cli", + "dependencies": { + "@project/foo": "1.0.0", + "@project/bar": "2.0.0" + }, + "ng-update": { + "migrations": "@project/migration-collection.json", + "packageGroup": { + "@project/abc": "0.0.0" + } + }, + "engines": { + "node": "^14.20.0 || ^16.13.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } +} diff --git a/tools/test/project_package.json b/tools/test/project_package.json new file mode 100644 index 000000000000..894c9f4cf3f2 --- /dev/null +++ b/tools/test/project_package.json @@ -0,0 +1,39 @@ +{ + "name": "project", + "version": "0.0.0-SNAPSHOT", + "description": "Project package.json", + "main": "project/index.js", + "bin": { + "projectfoo": "./bin/project-foo.js" + }, + "keywords": [ + "b", + "c" + ], + "scripts": { + "build": "node project-build-script" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/angular-cli.git" + }, + "author": "Angular Authors", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular-cli/issues" + }, + "homepage": "https://github.com/angular/angular-cli", + "dependencies": { + "@project/foo": "1.0.0", + "@project/bar": "2.0.0" + }, + "devDependencies": { + "@project/devdep": "1.2.3" + }, + "ng-update": { + "migrations": "@project/migration-collection.json", + "packageGroup": { + "@project/abc": "0.0.0" + } + } +} diff --git a/tools/test/root_package.json b/tools/test/root_package.json new file mode 100644 index 000000000000..e0b263aef042 --- /dev/null +++ b/tools/test/root_package.json @@ -0,0 +1,37 @@ +{ + "name": "root", + "version": "1.0.0-next.1", + "private": true, + "description": "Root package.json", + "bin": { + "root-foo": "./bin/root-foo.js", + "root-bar": "./bin/root-bar.js" + }, + "keywords": [ + "a", + "b" + ], + "scripts": { + "build": "node root-build-script" + }, + "repository": { + "type": "git", + "url": "https://github.com/angular/angular-cli.git" + }, + "author": "Angular Authors", + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/angular-cli/issues" + }, + "homepage": "https://github.com/angular/angular-cli", + "workspaces": { + "packages": ["packages/root/foo/*", "packages/root/bar/*"] + }, + "resolutions": { + "root/foo/bar": "1.0.0" + }, + "devDependencies": { + "@root/foo": "1.0.0", + "@root/bar": "2.0.0" + } +} diff --git a/tools/toolchain_info.bzl b/tools/toolchain_info.bzl new file mode 100644 index 000000000000..505fbc713168 --- /dev/null +++ b/tools/toolchain_info.bzl @@ -0,0 +1,25 @@ +# look at the toolchains registered in the workspace file with nodejs_register_toolchains + +# the name can be anything the user wants this is just added to the target to create unique names +# the order will match against the order in the TOOLCHAIN_VERSION list. +TOOLCHAINS_NAMES = [ + "node14", + "node16", +] + +# this is the list of toolchains that should be used and are registered with nodejs_register_toolchains in the WORKSPACE file +TOOLCHAINS_VERSIONS = [ + select({ + "@bazel_tools//src/conditions:linux_x86_64": "@node14_linux_amd64//:node_toolchain", + "@bazel_tools//src/conditions:darwin": "@node14_darwin_amd64//:node_toolchain", + "@bazel_tools//src/conditions:windows": "@node14_windows_amd64//:node_toolchain", + }), + select({ + "@bazel_tools//src/conditions:linux_x86_64": "@node16_linux_amd64//:node_toolchain", + "@bazel_tools//src/conditions:darwin": "@node16_darwin_amd64//:node_toolchain", + "@bazel_tools//src/conditions:windows": "@node16_windows_amd64//:node_toolchain", + }), +] + +# A default toolchain for use when only one is necessary +DEFAULT_TOOLCHAIN_VERSION = TOOLCHAINS_VERSIONS[len(TOOLCHAINS_VERSIONS) - 1] diff --git a/tools/ts_json_schema.bzl b/tools/ts_json_schema.bzl index f0e9fa773fb5..00b53c0dd6d1 100644 --- a/tools/ts_json_schema.bzl +++ b/tools/ts_json_schema.bzl @@ -3,7 +3,6 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.io/license -# @external_begin def _ts_json_schema_interface_impl(ctx): args = [ ctx.files.src[0].path, @@ -35,14 +34,13 @@ _ts_json_schema_interface = rule( "_binary": attr.label( default = Label("//tools:quicktype_runner"), executable = True, - cfg = "host", + cfg = "exec", ), }, outputs = { "ts": "%{out}", }, ) -# @external_end # Generates a TS file that contains the interface for a JSON Schema file. Takes a single `src` # argument as input, an optional data field for reference files, and produces a @@ -52,11 +50,9 @@ _ts_json_schema_interface = rule( def ts_json_schema(name, src, data = []): out = src.replace(".json", ".ts") - # @external_begin _ts_json_schema_interface( name = name + ".interface", src = src, out = out, data = data, ) - # @external_end diff --git a/tools/yarn/check-yarn.js b/tools/yarn/check-yarn.js deleted file mode 100644 index 2e7d66ef63c4..000000000000 --- a/tools/yarn/check-yarn.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -'use strict'; - -if (process.env.npm_execpath.indexOf('yarn') === -1) { - throw new Error( - 'Please use Yarn instead of NPM to install dependencies. See: https://yarnpkg.com/lang/en/docs/install/'); -} diff --git a/tsconfig-build.json b/tsconfig-build.json new file mode 100644 index 000000000000..025d28ebb13b --- /dev/null +++ b/tsconfig-build.json @@ -0,0 +1,26 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node"] + }, + "exclude": [ + "packages/angular_devkit/build_angular/src/bazel-babel.d.ts", + "bazel-out/**/*", + "dist/**/*", + "dist-schema/**", + "goldens/**/*", + "**/node_modules/**/*", + "**/third_party/**/*", + "packages/angular_devkit/schematics_cli/blank/*-files/**/*", + "packages/angular_devkit/schematics_cli/schematic/files/**/*", + "packages/angular_devkit/build_angular/src/*/tests/**/*", + "packages/angular_devkit/build_angular/src/builders/*/tests/**/*", + "packages/angular_devkit/build_angular/src/testing/**/*", + "packages/angular_devkit/*/test/**/*", + "packages/schematics/*/*/*files/**/*", + "tests/**/*", + "tools/**/*", + ".ng-dev/**/*", + "**/*_spec.ts" + ] +} diff --git a/tsconfig-test.json b/tsconfig-test.json index 2744f98d1e3f..cae29f3a3cac 100644 --- a/tsconfig-test.json +++ b/tsconfig-test.json @@ -7,9 +7,6 @@ // Istanbul (not Constantinople) as well, and applying both source maps to get the original // source in devtools. "inlineSources": true, - "types": [ - "node", - "jasmine" - ] - }, + "types": ["node", "jasmine"] + } } diff --git a/tsconfig.json b/tsconfig.json index 7922da9866f2..198e5c64d657 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,13 @@ { "compilerOptions": { "declaration": true, + "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, + "experimentalDecorators": true, "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, "noUnusedParameters": false, "noUnusedLocals": false, "outDir": "./dist", @@ -12,44 +15,34 @@ "skipLibCheck": true, "strict": true, "target": "es2019", - "lib": [ - "es2019" - ], + "lib": ["es2020"], "baseUrl": "", - "rootDirs": [ - ".", - "./dist-schema/", - "./bazel-bin/" - ], - "typeRoots": [ - "./node_modules/@types" - ], - "types": [ - "node" - ], + "rootDirs": [".", "./dist-schema/", "./bazel-bin/"], + "typeRoots": ["./node_modules/@types"], + "types": ["node", "jasmine"], "paths": { - "@angular-devkit/core": [ "./packages/angular_devkit/core/src/index" ], - "@angular-devkit/core/node": [ "./packages/angular_devkit/core/node/index" ], - "@angular-devkit/core/node/testing": [ "./packages/angular_devkit/core/node/testing/index" ], - "@angular-devkit/schematics": [ "./packages/angular_devkit/schematics/src/index" ], - "@angular-devkit/schematics/tasks": [ "./packages/angular_devkit/schematics/tasks/index" ], - "@angular-devkit/schematics/tasks/node": [ "./packages/angular_devkit/schematics/tasks/node/index" ], - "@angular-devkit/schematics/tools": [ "./packages/angular_devkit/schematics/tools/index" ], - "@angular-devkit/schematics/testing": [ "./packages/angular_devkit/schematics/testing/index" ], - "@angular-devkit/build-optimizer": [ "./packages/angular_devkit/build_optimizer/src/index" ], - "@angular-devkit/architect": [ "./packages/angular_devkit/architect/src/index" ], - "@angular-devkit/architect/testing": [ "./packages/angular_devkit/architect/testing/index" ], - "@angular-devkit/build-angular": [ "./packages/angular_devkit/build_angular/src/index" ], - "@angular-devkit/build-webpack": [ "./packages/angular_devkit/build_webpack/src/index" ], - "@ngtools/webpack": [ "./packages/ngtools/webpack/src/index" ], - "@schematics/angular": [ "./packages/schematics/angular/index" ] + "@angular-devkit/core": ["./packages/angular_devkit/core/src/index"], + "@angular-devkit/core/node": ["./packages/angular_devkit/core/node/index"], + "@angular-devkit/core/node/testing": ["./packages/angular_devkit/core/node/testing/index"], + "@angular-devkit/schematics": ["./packages/angular_devkit/schematics/src/index"], + "@angular-devkit/schematics/tasks": ["./packages/angular_devkit/schematics/tasks/index"], + "@angular-devkit/schematics/tasks/node": [ + "./packages/angular_devkit/schematics/tasks/node/index" + ], + "@angular-devkit/schematics/tools": ["./packages/angular_devkit/schematics/tools/index"], + "@angular-devkit/schematics/testing": ["./packages/angular_devkit/schematics/testing/index"], + "@angular-devkit/architect": ["./packages/angular_devkit/architect/src/index"], + "@angular-devkit/architect/testing": ["./packages/angular_devkit/architect/testing/index"], + "@angular-devkit/build-angular": ["./packages/angular_devkit/build_angular/src/index"], + "@angular-devkit/build-webpack": ["./packages/angular_devkit/build_webpack/src/index"], + "@ngtools/webpack": ["./packages/ngtools/webpack/src/index"], + "@schematics/angular": ["./packages/schematics/angular/index"] }, "plugins": [ { "name": "@bazel/tsetse", - "disabledRules": [ - "must-type-assert-json-parse" - ] + // must-use-promises is handled by the eslint @typescript-eslint/no-floating-promises rule + "disabledRules": ["must-type-assert-json-parse", "must-use-promises"] } ] }, @@ -61,16 +54,15 @@ "bazel-out/**/*", "dist/**/*", "dist-schema/**", - "etc/api/**/*", - "etc/cli.angular.io/**/*", + "goldens/**/*", "**/node_modules/**/*", "**/third_party/**/*", - "packages/angular_devkit/schematics_cli/*/files/**/*", + "packages/angular_devkit/schematics_cli/blank/*-files/**/*", + "packages/angular_devkit/schematics_cli/schematic/files/**/*", "packages/angular_devkit/*/test/**/*", "packages/schematics/*/*/*files/**/*", "tests/**/*", "tools/**/*", - "integration/**/*", ".ng-dev/**/*" ] } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 9128c2c04b24..000000000000 --- a/tslint.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - // Consider using no-identical-functions and disabling in faulty tests - // Consider using cognitive-complexity and refactor - // Consider using no-useless-cast but it's annoying - "extends": [ - "tslint-no-circular-imports" - ], - "rulesDirectory": [ - "node_modules/tslint-sonarts/lib/rules" - ], - "linterOptions": { - "format": "codeFrame", - "exclude": [ - "dist-schema/**", - "**/third_party/**" - ] - }, - "rules": { - // ================================================================================================== - // tslint-sonarts rules. See https://github.com/SonarSource/SonarTS - // These rules are part of the bug detection section of tslint-sonarts - "no-big-function": true, - "no-all-duplicated-branches": true, - "no-case-with-or": true, - "no-collection-size-mischeck": true, - "no-element-overwrite": true, - "no-empty-destructuring": true, - "no-identical-conditions": true, - "no-ignored-initial-value": true, - "no-ignored-return": true, - "no-in-misuse": true, - "no-misleading-array-reverse": true, - "no-misspelled-operator": true, - "no-self-assignment": true, - "no-unthrown-error": true, - "no-use-of-empty-return-value": true, - "no-useless-increment": true, - "no-invalid-await": true, - "prefer-promise-shorthand": true, - - //These rules are part of the code smell detection section of tslint-sonarts - "no-dead-store": true, - "no-useless-intersection": true, - // ================================================================================================== - // base tslint rules - "arrow-return-shorthand": true, - "no-duplicate-imports": true, - "no-angle-bracket-type-assertion": true, - "no-conditional-assignment": true, - "no-non-null-assertion": true, - "no-unnecessary-qualifier": true, - "no-string-throw": true, - "encoding": true, - "no-floating-promises": true, - "no-implicit-dependencies": [ - true, - // Specs can depend on the package they are testing. - // This is a workaround, we should instead have a dedicated - // tslint config only for specs. - ["@angular-devkit/build-angular"] - ], - "no-import-side-effect": [true, {"ignore-module": "^(?!rxjs\/)"}], - "align": [ - true, - "elements", - "members", - "parameters", - "statements" - ], - "max-line-length": [true, 140], - "no-inferrable-types": true, - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "indent": [ - true, - "spaces" - ], - "eofline": true, - "import-spacing": true, - "newline-before-return": true, - "no-consecutive-blank-lines": [true, 2], - "no-duplicate-variable": true, - "no-eval": true, - "no-any": true, - "no-arg": true, - "no-debugger": true, - "no-console": true, - "no-internal-module": true, - "no-trailing-whitespace": true, - "no-unused-expression": true, - "no-var-keyword": true, - "one-line": [ - true, - "check-catch", - "check-else", - "check-finally", - "check-open-brace", - "check-whitespace" - ], - "ordered-imports": [ - true, - { - "import-sources-order": "lowercase-last", - "named-imports-order": "lowercase-last" - } - ], - "prefer-const": true, - "quotemark": [ - true, - "single", - "avoid-escape" - ], - "semicolon": [true, "always"], - "trailing-comma": [ - true, - { - "multiline": "always", - "singleline": "never" - } - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "curly": true, - "file-header": [ - true, - "Copyright Google Inc\\. All Rights Reserved\\." - ], - "variable-name": [ - true, - "ban-keywords", - "check-format", - "allow-leading-underscore", - "allow-pascal-case" - ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-module", - "check-preblock", - "check-operator", - "check-separator", - "check-type", - "check-typecast" - ] - } -} diff --git a/yarn.lock b/yarn.lock index 2fb58a8f85c0..ee599cd4907b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,757 +2,856 @@ # yarn lockfile v1 -"@angular/animations@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-12.0.0-next.8.tgz#25c5606f616e47255d8357e7758891761e487f1e" - integrity sha512-UGMnjPs62wwqJeU/T828Ma6g8cvL8lwouGYzy59TNQ4ouDSuEPbJtMNewlJsoA4Ex/IVq46N6eENKs6GT1IXWw== +"@ampproject/remapping@2.2.0", "@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@angular-devkit/architect@0.1500.0-rc.2": + version "0.1500.0-rc.2" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1500.0-rc.2.tgz#90ad1c55851491d2eb3c2c34e0b99130b0c8d504" + integrity sha512-lt1CkX3GFCCyF1rB21JvEwJjrhorO93AcAKxy1x/2xNQg/6BxMpdoptFaHeQVEcpCsq/i0pHTLZuCncyhVDUQA== + dependencies: + "@angular-devkit/core" "15.0.0-rc.2" + rxjs "6.6.7" + +"@angular-devkit/build-angular@15.0.0-rc.2": + version "15.0.0-rc.2" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-15.0.0-rc.2.tgz#1ab8f0431278e435d1c6238ad4aff933d50e88be" + integrity sha512-4RvkQF64de5zebVtun404FOKQtnjWuye3VwLlR9RjIqx0U3aIrmvDUmE15qG166A6eOA2+I2U1LifVh8Rh88mw== + dependencies: + "@ampproject/remapping" "2.2.0" + "@angular-devkit/architect" "0.1500.0-rc.2" + "@angular-devkit/build-webpack" "0.1500.0-rc.2" + "@angular-devkit/core" "15.0.0-rc.2" + "@babel/core" "7.19.6" + "@babel/generator" "7.20.1" + "@babel/helper-annotate-as-pure" "7.18.6" + "@babel/plugin-proposal-async-generator-functions" "7.20.1" + "@babel/plugin-transform-async-to-generator" "7.18.6" + "@babel/plugin-transform-runtime" "7.19.6" + "@babel/preset-env" "7.19.4" + "@babel/runtime" "7.20.1" + "@babel/template" "7.18.10" + "@discoveryjs/json-ext" "0.5.7" + "@ngtools/webpack" "15.0.0-rc.2" + ansi-colors "4.1.3" + autoprefixer "10.4.13" + babel-loader "9.0.1" + babel-plugin-istanbul "6.1.1" + browserslist "^4.9.1" + cacache "17.0.1" + chokidar "3.5.3" + copy-webpack-plugin "11.0.0" + critters "0.0.16" + css-loader "6.7.1" + esbuild-wasm "0.15.12" + glob "8.0.3" + https-proxy-agent "5.0.1" + inquirer "8.2.4" + jsonc-parser "3.2.0" + karma-source-map-support "1.4.0" + less "4.1.3" + less-loader "11.1.0" + license-webpack-plugin "4.0.2" + loader-utils "3.2.0" + mini-css-extract-plugin "2.6.1" + minimatch "5.1.0" + open "8.4.0" + ora "5.4.1" + parse5-html-rewriting-stream "6.0.1" + piscina "3.2.0" + postcss "8.4.18" + postcss-loader "7.0.1" + regenerator-runtime "0.13.10" + resolve-url-loader "5.0.0" + rxjs "6.6.7" + sass "1.55.0" + sass-loader "13.1.0" + semver "7.3.8" + source-map-loader "4.0.1" + source-map-support "0.5.21" + terser "5.15.1" + text-table "0.2.0" + tree-kill "1.2.2" + tslib "2.4.1" + webpack "5.74.0" + webpack-dev-middleware "5.3.3" + webpack-dev-server "4.11.1" + webpack-merge "5.8.0" + webpack-subresource-integrity "5.1.0" + optionalDependencies: + esbuild "0.15.12" + +"@angular-devkit/build-webpack@0.1500.0-rc.2": + version "0.1500.0-rc.2" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1500.0-rc.2.tgz#f0da40fe2e4f858e5a1552594d762010f42fd545" + integrity sha512-Shl5ch9CfDWH2vODI3Eas0dEwEhLxSIgTDIvTQO1BOTr/lJFHxUafOYxBBtTsDVvvVeJDNlRkmrNtyUsLKd+lA== dependencies: - tslib "^2.1.0" + "@angular-devkit/architect" "0.1500.0-rc.2" + rxjs "6.6.7" -"@angular/benchpress@0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@angular/benchpress/-/benchpress-0.2.1.tgz#f8b58d9acfda0d29959b87dcb8082b1c33735db5" - integrity sha512-ojHCP96ZunHBZpt08USSEdLJsuXnEEdJtfzl+9oTdMXbooKkzSVO7N6bVdjefbGRNAleAuSAo3gVrdPqumLznA== +"@angular-devkit/core@15.0.0-rc.2": + version "15.0.0-rc.2" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-15.0.0-rc.2.tgz#9aac72f792b9cf5a966fd02beaec941aa0b28988" + integrity sha512-sCEh1XY9Vr5qg9wp6UGIXtlTaEWxKuFkfaSkhtM+ZCdRU/SgYr18ndFMjlQzBY73hhS7hQnC31rX6GSGtG4DrA== dependencies: - "@angular/core" "^10.0.0-0 || ^11.0.0" - reflect-metadata "^0.1.13" + ajv "8.11.0" + ajv-formats "2.1.1" + jsonc-parser "3.2.0" + rxjs "6.6.7" + source-map "0.7.4" -"@angular/cdk@11.2.9": - version "11.2.9" - resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-11.2.9.tgz#30ba78a323a6dd19c03972ebf091056d75ba0ade" - integrity sha512-3gHobkgCQIJ4kJFkqexaKramI6k4OZ0/Vw5qvabGhpwWtICvrrfZyRejJzhecYKOcw+hiJNoDqP9TVNfkLme9g== +"@angular/animations@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-15.0.0-rc.3.tgz#b0c1bc24fc86ea01b0d6ee23aa00bbf905a1264d" + integrity sha512-YalrwFa01rzWh9JhufHoOY1cUMYW9JCq9x2av3OYjtIxw36srkeBe09CwjBeO3zRhIo5ieK45Ix0YgWCjhqg5A== dependencies: - tslib "^2.0.0" + tslib "^2.3.0" + +"@angular/benchpress@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@angular/benchpress/-/benchpress-0.3.0.tgz#0adf14156ff31dfee3ef607c0088e9ee6fb08777" + integrity sha512-ApxoY5lTj1S0QFLdq5ZdTfdkIds1m3tma9EJOZpNVHRU9eCj2D/5+VFb5tlWsv9NHQ2S0XXkJjauFOAdfzT8uw== + dependencies: + "@angular/core" "^13.0.0 || ^14.0.0-0" + reflect-metadata "^0.1.13" + +"@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#fb42478534df7d48ec23a6834fea94a776cb89a0": + version "0.0.0-7d103b83a07f132629592fc9918ce17d42a5e382" + resolved "https://github.com/angular/dev-infra-private-build-tooling-builds.git#fb42478534df7d48ec23a6834fea94a776cb89a0" + dependencies: + "@angular-devkit/build-angular" "15.0.0-rc.2" + "@angular/benchpress" "0.3.0" + "@babel/core" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@bazel/buildifier" "5.1.0" + "@bazel/concatjs" "5.7.1" + "@bazel/esbuild" "5.7.1" + "@bazel/protractor" "5.7.1" + "@bazel/runfiles" "5.7.1" + "@bazel/terser" "5.7.1" + "@bazel/typescript" "5.7.1" + "@microsoft/api-extractor" "7.31.0" + "@types/browser-sync" "^2.26.3" + "@types/node" "16.10.9" + "@types/selenium-webdriver" "^4.0.18" + "@types/send" "^0.17.1" + "@types/tmp" "^0.2.1" + "@types/uuid" "^8.3.1" + "@types/ws" "8.5.3" + "@types/yargs" "^17.0.0" + browser-sync "^2.27.7" + clang-format "1.8.0" + prettier "2.7.1" + protractor "^7.0.0" + selenium-webdriver "4.4.0" + send "^0.18.0" + source-map "^0.7.4" + tmp "^0.2.1" + "true-case-path" "^2.2.1" + tslib "^2.3.0" + typescript "~4.8.0" + uuid "^9.0.0" + yargs "^17.0.0" + +"@angular/cdk@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-14.2.7.tgz#65eb6fbbeed6120fad4e3913aa66f8b74c853ac3" + integrity sha512-/tEsYaUbDSnfEmKVvAMramIptmhI67O+9STjOV0i+74XR2NospeK0fkbywIANu1n3w6AHGMotvRWJrjmbCElFg== + dependencies: + tslib "^2.3.0" optionalDependencies: parse5 "^5.0.0" -"@angular/common@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-12.0.0-next.8.tgz#9515f8c69a7391cb80f663987e36ff2aa8d354c0" - integrity sha512-/Bx1DXgmspybTuAJqlUSOcPmkKL0/87K71MVq36WgweGsV1N++IMyOoGAJyLcnZe71QjVL2Er0od3D56EU+Hfw== +"@angular/common@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-15.0.0-rc.3.tgz#a53f6891846fea6ad7d5f0461c30464ea4461909" + integrity sha512-/b0qi7S/hoEZQrpD7gvWwzVOQYiXrAWMSnSoYd6dnKTroN+jt2XeLDHsbVxAPKO5yRhbH6409d2YFTD3ZaU0Vg== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" -"@angular/compiler-cli@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-12.0.0-next.8.tgz#df7ba9d9efda5d3b8c5e9447892ba653b542ce0b" - integrity sha512-cwXaSg+u6A8DgVfdvranO/RQptZzZjGPANjlsC6QDi5hdPkU6fI4wQbzm6KRMlww+mTbrNRmMNRlAI5sVx3y1w== +"@angular/compiler-cli@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-15.0.0-rc.3.tgz#43603809ec9ce290d5902acb6df992d428e7ee52" + integrity sha512-0qRtcRmSOLECtXzauAAJSgJ4S+ZwS2VxUd4pdLu7OlIEMD2KKjmZhb2dgCFg385kP2LCSuwDqOoU97NlbsM4zQ== dependencies: - "@babel/core" "^7.8.6" - "@babel/types" "^7.8.6" - canonical-path "1.0.0" + "@babel/core" "^7.17.2" chokidar "^3.0.0" convert-source-map "^1.5.1" - dependency-graph "^0.7.2" - magic-string "^0.25.0" - minimist "^1.2.0" + dependency-graph "^0.11.0" + magic-string "^0.26.0" reflect-metadata "^0.1.2" - semver "^6.3.0" - source-map "^0.6.1" + semver "^7.0.0" sourcemap-codec "^1.4.8" - tslib "^2.1.0" - yargs "^16.2.0" + tslib "^2.3.0" + yargs "^17.2.1" -"@angular/compiler@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-12.0.0-next.8.tgz#475f9fc66e8bd789200fe8ae7ba77b6ff5a32153" - integrity sha512-2SN8gte05JwHVd5b2ExP1lyIIND7TaJ0XipzaUOgsRwGVLWDhN6hS3XMb1BLeZlwl9YYLNVATUbhThC+ZSR4uw== +"@angular/compiler@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-15.0.0-rc.3.tgz#ff66b449653dc653cc571a4e929957396632aead" + integrity sha512-G4H7XBLhYl3Ra8BIFzWwvH8K3fIYQi2MZ76EAMIlsG6sdMogViyeC3HJ4pwO3fjG6gKXHwEMrHKa9ktxp+Q+mA== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" -"@angular/compiler@9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.0.0.tgz#87e0bef4c369b6cadae07e3a4295778fc93799d5" - integrity sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ== - -"@angular/core@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-12.0.0-next.8.tgz#f037aa48a4aedc57742bcfc627d1a1661d6c5138" - integrity sha512-+LDGHKrVUekNQsohG+afEv441x4cupftigzTjKjioPYq9TAZSBHQrPSTAL0Jwmm731ghkPEAeoRni4HMmDGO3Q== +"@angular/core@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-15.0.0-rc.3.tgz#b223ccd694164741b8b6a1d045ed0f5354237a59" + integrity sha512-jlJzF1gUR2hO6xVLmBMZEpgetTwJnV3DuZgFRzZKV+VIm8nD55V8J68/stlHZobYde564jCbq/to+FziVQdQaQ== dependencies: - tslib "^2.1.0" - -"@angular/core@9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.0.0.tgz#227dc53e1ac81824f998c6e76000b7efc522641e" - integrity sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w== + tslib "^2.3.0" -"@angular/core@^10.0.0-0 || ^11.0.0": - version "11.2.0" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-11.2.0.tgz#309ae61d55b21fca0b644a6571109741d64b2467" - integrity sha512-jnbnJTW2GwfkRoXG8J4zs5FMcahMZwo6jrZGe9FiXjCYG9cLEuOXy4h99Z1s/o0vc/VXyWgym7SmeEgv+urf8g== +"@angular/core@^13.0.0 || ^14.0.0-0": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-14.2.7.tgz#a39fbfc4520debf34996d25d8451f3d66b4ce7d2" + integrity sha512-9u2eeKS90YPh2b0pK5LKFSxKfLIzHnzkIKQFh6bEPGj43Fl2v8CwiVJu1CAKo1Or4qBY8zspSowM6S1kgGwfeg== dependencies: - tslib "^2.0.0" + tslib "^2.3.0" -"@angular/dev-infra-private@https://github.com/angular/dev-infra-private-builds.git#0d8382f30f70ab220961c2d731c782519025332e": - version "0.0.0" - uid "0d8382f30f70ab220961c2d731c782519025332e" - resolved "https://github.com/angular/dev-infra-private-builds.git#0d8382f30f70ab220961c2d731c782519025332e" - dependencies: - "@angular/benchpress" "0.2.1" - "@bazel/buildifier" "^4.0.1" - "@octokit/graphql" "^4.6.1" - "@octokit/rest" "16.28.7" - "@octokit/types" "^5.5.0" - brotli "^1.3.2" - chalk "^2.3.1" - clang-format "^1.4.0" - cli-progress "^3.7.0" - conventional-commits-parser "^3.2.1" - ejs "^3.1.6" - git-raw-commits "^2.0.10" - glob "7.1.6" - inquirer "^8.0.0" - minimatch "^3.0.4" - multimatch "^5.0.0" - node-fetch "^2.6.1" - node-uuid "1.4.8" - ora "^5.0.0" - protractor "^7.0.0" - selenium-webdriver "3.5.0" - semver "^7.3.5" - shelljs "^0.8.4" - ts-node "^9.1.1" - tslib "^2.1.0" - typed-graphqlify "^2.3.0" - typescript "~4.2.4" - yaml "^1.10.0" - yargs "^16.2.0" +"@angular/forms@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-15.0.0-rc.3.tgz#ae95913af337aca7f84f472cf27855db68892407" + integrity sha512-/wW8pdaGP7fOGg2HgiHFOutE8FtQn6hfX3ZrAORCCgrNGBS9WH8I5coISogTKd+FM7gQMz0F6TxREx/EAElfzw== + dependencies: + tslib "^2.3.0" -"@angular/forms@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-12.0.0-next.8.tgz#412aecd3d38690e3c7d2a1998b53a78208b2b908" - integrity sha512-JR0xD2Rr6y458xYT7V6XyL/rL4rsSFraRTT/zZr1UemIaFCkHdQrrGGm41W35laPXmwqGUxfFoaXM6H9PNWqPQ== +"@angular/localize@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-15.0.0-rc.3.tgz#dcf7cae1626602fbcba1e79982a09a04b48c5a84" + integrity sha512-6OFHkCH/WvMjAKxICYbbhwdyYADzJwCny1HVKgk3ezMUnBaVbOfWzMCIFJyhU7k8kdzjGmQx+FNPL2O6M98FMg== dependencies: - tslib "^2.1.0" + "@babel/core" "7.19.3" + glob "8.0.3" + yargs "^17.2.1" -"@angular/localize@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-12.0.0-next.8.tgz#7456dcf66d48e2ee4c56aca9ff91881349ae45ff" - integrity sha512-eVAVeZ/Xw/Xz+LyX8QGh3ynOck/+l/6fHCBRVT0qLLSUwqttNsFfxHigFnNmoIJ2nOhmtGPa1MmGNPzJp0fzbw== +"@angular/material@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-14.2.7.tgz#678c657197268eba6814c757152f8d178ff08866" + integrity sha512-WXHh8pEStpgkXZJmYOg2cI8BSHkV82ET4XTJCNPdveumaCn1UYnaNzsXD13kw5z+zmy8CufhFEzdXTrv/yt7KQ== dependencies: - "@babel/core" "7.8.3" - glob "7.1.2" - yargs "^16.2.0" + tslib "^2.3.0" -"@angular/material@11.2.9": - version "11.2.9" - resolved "https://registry.yarnpkg.com/@angular/material/-/material-11.2.9.tgz#f74535029f5c246f33649706cda75d29ccd84cb9" - integrity sha512-Pe1aScSX8fMOSs5hfF/V7NCJn9pt/60rFmwYxgtFser4BcJrO0Moz4eqxhNcR0PjiCQgKJpjjPxBept2+o5N5A== +"@angular/ng-dev@https://github.com/angular/dev-infra-private-ng-dev-builds.git#d1b5e1929c8b01f7621d65c54a52ac6a9adb22ad": + version "0.0.0-7d103b83a07f132629592fc9918ce17d42a5e382" + resolved "https://github.com/angular/dev-infra-private-ng-dev-builds.git#d1b5e1929c8b01f7621d65c54a52ac6a9adb22ad" dependencies: - tslib "^2.0.0" + "@yarnpkg/lockfile" "^1.1.0" + typescript "~4.8.0" -"@angular/platform-browser-dynamic@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.0.0-next.8.tgz#d76d43721fb27a967258343694e006f770683c9b" - integrity sha512-XO/IY+Xh1PzZtwED5uiHyKyJyB7g8OkdYhFjVXxK32Y3CiF6zN5dUgQDyCzUF7nOWydGLBogzwmmouigR999xg== +"@angular/platform-browser-dynamic@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-15.0.0-rc.3.tgz#775e6526a78b78bf239e9302a22318f7820fe16a" + integrity sha512-fSEHJHLR6YrRgCUaWGBEvUh0w86hVAbWXkG4NHKgJ2c1xCLIxst0/x3u7GOVNSbHoOQCN7bzMoCro2girX6Snw== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" -"@angular/platform-browser@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-12.0.0-next.8.tgz#229302e72d7cb0b057e49b10ec069f8bc862f718" - integrity sha512-bgiIguM3O//afPaBgl9/epBzbf/3TVKK9lDESx18LHtnoxZrFoWGWhj4xD7x4OlEUGlXse2VYU050y6F/pXx1Q== +"@angular/platform-browser@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-15.0.0-rc.3.tgz#55955bb91c4429d14cee919fb9b69ecd29243360" + integrity sha512-FRasOfU/fDqO76SeDy3pVP2YbtgOcbyL/r/vODDQ+CiKfDLHFmghfBJCIS+CmWrzOrd/JUlN7a7+AfqwbJRLyA== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" -"@angular/platform-server@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-12.0.0-next.8.tgz#46b609209bada2e730e5b34302cde1bc828fba8c" - integrity sha512-2Sut7xvHa9EAeRsTEKSMEHHzT7lBJ/0iPz/wtYIEpOKQ3QstuX2GTV1GqllN++CDrXHGsWgk4AtFnAEgkOieKQ== +"@angular/platform-server@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-15.0.0-rc.3.tgz#a9062604a80993630413f813fdf7e837c62e5e33" + integrity sha512-71e7Ku7NCl2cTGcqrdHdtd7PSJup/xHj1kez87Vxzr5/X9kGo444ZeEs/uqTFMnL3rnb7OqqZREK5dvlGEAvTw== dependencies: domino "^2.1.2" - tslib "^2.1.0" + tslib "^2.3.0" xhr2 "^0.2.0" -"@angular/router@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-12.0.0-next.8.tgz#f77526b662403e892b1d2cb97b6796d3e623bbc4" - integrity sha512-EnEfziJAstdqLbajuqySPyBxsOzc2JgtxyDsk+b5MkHBfECQvaT+hBywm2bYv7CJUqHIq5LzEbeTkXEwlVX9/g== +"@angular/router@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-15.0.0-rc.3.tgz#7fad7cd8fca8ea2e3c9cb137dca09da67c255056" + integrity sha512-tpzTyrDr2GRdWP0XyfWYMekFVXU9yTmKaM67UzdZGIDVMkZCsR3FBT6hYb1K4iG/v/rtu1TRL2Xawj3tRTTMTQ== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" -"@angular/service-worker@12.0.0-next.8": - version "12.0.0-next.8" - resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-12.0.0-next.8.tgz#d6267d7b106fde36f2063624ff65558636dadeb7" - integrity sha512-oa1d7vZUN/YrUJ1x3im+ucZJD+pS/KyDP2bCAav3kpSFo9aO8I5MleqU+zTCDWpgcIT50filme1a7novKAMIQA== +"@angular/service-worker@15.0.0-rc.3": + version "15.0.0-rc.3" + resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-15.0.0-rc.3.tgz#ab5706f48ba3767a5ab014aaac18096f47fc08f9" + integrity sha512-JdyvtlGjJF1FdwHjLE4JFyJIUmlLg/v7mfJdZQg1E0xQwucUt1d1hSDv297MOeaDZOKIV680gMK5Y3y8mwkDnw== dependencies: - tslib "^2.1.0" + tslib "^2.3.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - -"@babel/compat-data@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.0.tgz#7889eb7ee6518e2afa5d312b15fd7fd1fe9f3744" - integrity sha512-mKgFbYQ+23pjwNGBNPNWrBfa3g/EcmrPnwQpjWoNxq9xYf+M8wcLhMlz/wkWimLjzNzGnl3D+C2186gMzk0VuA== - -"@babel/compat-data@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.12.tgz#a8a5ccac19c200f9dd49624cac6e19d7be1236a1" - integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ== - -"@babel/compat-data@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.8.tgz#5b783b9808f15cef71547f1b691f34f8ff6003a6" - integrity sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog== - -"@babel/core@7.13.13": - version "7.13.13" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.13.tgz#bc44c4a2be2288ec4ddf56b66fc718019c76ac29" - integrity sha512-1xEs9jZAyKIouOoCmpsgk/I26PoKyvzQ2ixdRpRzfbcp1fL+ozw7TUgdDgwonbTovqRaTfRh50IXuw4QrWO0GA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.13.9" - "@babel/helper-compilation-targets" "^7.13.13" - "@babel/helper-module-transforms" "^7.13.12" - "@babel/helpers" "^7.13.10" - "@babel/parser" "^7.13.13" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.13" - "@babel/types" "^7.13.13" +"@assemblyscript/loader@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" + integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.3", "@babel/compat-data@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== + +"@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" + integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== + +"@babel/core@7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c" + integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.3" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.3" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.3" + "@babel/types" "^7.19.3" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.1.2" - lodash "^4.17.19" + json5 "^2.2.1" semver "^6.3.0" - source-map "^0.5.0" -"@babel/core@7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.3.tgz#30b0ebb4dd1585de6923a0b4d179e0b9f5d82941" - integrity sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.3" - "@babel/helpers" "^7.8.3" - "@babel/parser" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" +"@babel/core@7.19.6", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.17.2": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" convert-source-map "^1.7.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.0" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.7.5", "@babel/core@^7.8.6": - version "7.12.16" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.16.tgz#8c6ba456b23b680a6493ddcfcd9d3c3ad51cab7c" - integrity sha512-t/hHIB504wWceOeaOoONOhu+gX+hpjfeN6YRBT209X/4sibZQfSF1I0HFRRlBe97UZZosGx5XwUg1ZgNbelmNw== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.12.15" - "@babel/helper-module-transforms" "^7.12.13" - "@babel/helpers" "^7.12.13" - "@babel/parser" "^7.12.16" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.12.13" - "@babel/types" "^7.12.13" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/core@7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92" + integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.2" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-module-transforms" "^7.20.2" + "@babel/helpers" "^7.20.1" + "@babel/parser" "^7.20.2" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.2" convert-source-map "^1.7.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - semver "^5.4.1" - source-map "^0.5.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" -"@babel/generator@7.13.9", "@babel/generator@^7.13.9": - version "7.13.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" - integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== +"@babel/generator@7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.1.tgz#ef32ecd426222624cbd94871a7024639cf61a9fa" + integrity sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg== dependencies: - "@babel/types" "^7.13.0" + "@babel/types" "^7.20.0" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" - source-map "^0.5.0" -"@babel/generator@^7.12.13", "@babel/generator@^7.12.15", "@babel/generator@^7.8.3": - version "7.12.15" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.15.tgz#4617b5d0b25cc572474cc1aafee1edeaf9b5368f" - integrity sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ== +"@babel/generator@7.20.4": + version "7.20.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" + integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== dependencies: - "@babel/types" "^7.12.13" + "@babel/types" "^7.20.2" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" - source-map "^0.5.0" -"@babel/generator@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.0.tgz#bd00d4394ca22f220390c56a0b5b85568ec1ec0c" - integrity sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw== +"@babel/generator@^7.19.3", "@babel/generator@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" + integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA== dependencies: - "@babel/types" "^7.13.0" + "@babel/types" "^7.20.5" + "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" - source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" - integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== +"@babel/generator@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" + integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== dependencies: - "@babel/types" "^7.12.13" + "@babel/types" "^7.19.4" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc" - integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA== +"@babel/generator@^7.20.1", "@babel/generator@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.2.tgz#c2e89e22613a039285c1e7b749e2cd0b30b9a481" + integrity sha512-SD75PMIK6i9H8G/tfGvB4KKl4Nw6Ssos9nGgYwxbgyTP0iX/Z55DveoH86rmUB/YHTQQ+ZC0F7xxaY8l2OF44Q== dependencies: - "@babel/helper-explode-assignable-expression" "^7.12.13" - "@babel/types" "^7.12.13" + "@babel/types" "^7.20.2" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.0.tgz#c9cf29b82a76fd637f0faa35544c4ace60a155a1" - integrity sha512-SOWD0JK9+MMIhTQiUVd4ng8f3NXhPVQvTv7D3UN4wbp/6cAHnB2EmMaU1zZA2Hh1gwme+THBrVSqTFxHczTh0Q== +"@babel/helper-annotate-as-pure@7.18.6", "@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== dependencies: - "@babel/compat-data" "^7.13.0" - "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" - semver "7.0.0" + "@babel/types" "^7.18.6" -"@babel/helper-compilation-targets@^7.13.10": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz#1310a1678cb8427c07a753750da4f8ce442bdd0c" - integrity sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== dependencies: - "@babel/compat-data" "^7.13.8" - "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" - semver "^6.3.0" + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.13.13": - version "7.13.13" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz#2b2972a0926474853f41e4adbc69338f520600e5" - integrity sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" + integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== dependencies: - "@babel/compat-data" "^7.13.12" - "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" + "@babel/compat-data" "^7.19.3" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.8.tgz#02bdb22783439afb11b2f009814bdd88384bd468" - integrity sha512-pBljUGC1y3xKLn1nrx2eAhurLMA8OqBtBP/JwG4U8skN7kf8/aqwwxpV1N6T0e7r6+7uNitIa/fUxPFagSXp3A== +"@babel/helper-compilation-targets@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" + integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== dependencies: - "@babel/compat-data" "^7.13.8" - "@babel/helper-validator-option" "^7.12.17" - browserslist "^4.14.5" + "@babel/compat-data" "^7.20.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.0.tgz#28d04ad9cfbd1ed1d8b988c9ea7b945263365846" - integrity sha512-twwzhthM4/+6o9766AW2ZBHpIHPSGrPGk1+WfHiu13u/lBnggXGNYCpeAyVfNwGDKfkhEDp+WOD/xafoJ2iLjA== - dependencies: - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-member-expression-to-functions" "^7.13.0" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/helper-replace-supers" "^7.13.0" - "@babel/helper-split-export-declaration" "^7.12.13" - -"@babel/helper-create-regexp-features-plugin@^7.12.13": - version "7.12.16" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.16.tgz#3b31d13f39f930fad975e151163b7df7d4ffe9d3" - integrity sha512-jAcQ1biDYZBdaAxB4yg46/XirgX7jBDiMHDbwYQOgtViLBXGxJpZQ24jutmBqAIB/q+AwB6j+NbBXjKxEY8vqg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.12.13" - regexpu-core "^4.7.1" - -"@babel/helper-define-polyfill-provider@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.2.tgz#619f01afe1deda460676c25c463b42eaefdb71a2" - integrity sha512-hWeolZJivTNGHXHzJjQz/NwDaG4mGXf22ZroOP8bQYgvHNzaQ5tylsVbAcAS2oDjXBwpu8qH2I/654QFS2rDpw== - dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" +"@babel/helper-create-class-features-plugin@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + +"@babel/helper-define-polyfill-provider@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" + integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" debug "^4.1.1" lodash.debounce "^4.0.8" resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-explode-assignable-expression@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.13.tgz#0e46990da9e271502f77507efa4c9918d3d8634a" - integrity sha512-5loeRNvMo9mx1dA/d6yNi+YiKziJZFylZnCo1nmFF4qPU4yJ14abhWESuSMQSlQxWdxdOFzxXjk/PpfudTtYyw== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-function-name@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" - integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== - dependencies: - "@babel/helper-get-function-arity" "^7.12.13" - "@babel/template" "^7.12.13" - "@babel/types" "^7.12.13" - -"@babel/helper-get-function-arity@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" - integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-hoist-variables@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8" - integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g== - dependencies: - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - -"@babel/helper-member-expression-to-functions@^7.12.13": - version "7.12.16" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.16.tgz#41e0916b99f8d5f43da4f05d85f4930fa3d62b22" - integrity sha512-zYoZC1uvebBFmj1wFAlXwt35JLEgecefATtKp20xalwEK8vHAixLBXTGxNrVGEmTT+gzOThUgr8UEdgtalc1BQ== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-member-expression-to-functions@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz#6aa4bb678e0f8c22f58cdb79451d30494461b091" - integrity sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ== - dependencies: - "@babel/types" "^7.13.0" - -"@babel/helper-member-expression-to-functions@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" - integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-module-imports@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0" - integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-module-imports@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" - integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-module-transforms@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz#01afb052dcad2044289b7b20beb3fa8bd0265bea" - integrity sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA== - dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-replace-supers" "^7.12.13" - "@babel/helper-simple-access" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/helper-validator-identifier" "^7.12.11" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.12.13" - "@babel/types" "^7.12.13" - lodash "^4.17.19" - -"@babel/helper-module-transforms@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz#42eb4bd8eea68bab46751212c357bfed8b40f6f1" - integrity sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw== - dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-replace-supers" "^7.13.0" - "@babel/helper-simple-access" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/helper-validator-identifier" "^7.12.11" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - lodash "^4.17.19" - -"@babel/helper-module-transforms@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.12.tgz#600e58350490828d82282631a1422268e982ba96" - integrity sha512-7zVQqMO3V+K4JOOj40kxiCrMf6xlQAkewBB0eu2b03OO/Q21ZutOzjpfD79A5gtE/2OWi1nv625MrDlGlkbknQ== - dependencies: - "@babel/helper-module-imports" "^7.13.12" - "@babel/helper-replace-supers" "^7.13.12" - "@babel/helper-simple-access" "^7.13.12" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/helper-validator-identifier" "^7.12.11" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.12" - -"@babel/helper-optimise-call-expression@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" - integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz#174254d0f2424d8aefb4dd48057511247b0a9eeb" - integrity sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA== - -"@babel/helper-plugin-utils@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" - integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== - -"@babel/helper-remap-async-to-generator@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209" - integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.12.13" - "@babel/helper-wrap-function" "^7.13.0" - "@babel/types" "^7.13.0" - -"@babel/helper-replace-supers@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz#00ec4fb6862546bd3d0aff9aac56074277173121" - integrity sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.12.13" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/traverse" "^7.12.13" - "@babel/types" "^7.12.13" - -"@babel/helper-replace-supers@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz#6034b7b51943094cb41627848cb219cb02be1d24" - integrity sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.13.0" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - -"@babel/helper-replace-supers@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804" - integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.13.12" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.12" - -"@babel/helper-simple-access@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz#8478bcc5cacf6aa1672b251c1d2dde5ccd61a6c4" - integrity sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-simple-access@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" - integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== - dependencies: - "@babel/types" "^7.13.12" - -"@babel/helper-skip-transparent-expression-wrappers@^7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" - integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA== - dependencies: - "@babel/types" "^7.12.1" - -"@babel/helper-split-export-declaration@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" - integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== - dependencies: - "@babel/types" "^7.12.13" - -"@babel/helper-validator-identifier@^7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" - integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== - -"@babel/helper-validator-option@^7.12.17": - version "7.12.17" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" - integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== - -"@babel/helper-wrap-function@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4" - integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA== - dependencies: - "@babel/helper-function-name" "^7.12.13" - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" + +"@babel/helper-module-transforms@^7.19.0", "@babel/helper-module-transforms@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" + integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.2" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-plugin-utils@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9", "@babel/helper-replace-supers@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" + +"@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== + dependencies: + "@babel/types" "^7.19.4" + +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-wrap-function@^7.18.9": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" + integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== + dependencies: + "@babel/helper-function-name" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + +"@babel/helpers@^7.19.0": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763" + integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + +"@babel/helpers@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" + integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.4" + "@babel/types" "^7.19.4" + +"@babel/helpers@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" + integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.0" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" -"@babel/helpers@^7.12.13", "@babel/helpers@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.13.tgz#3c75e993632e4dadc0274eae219c73eb7645ba47" - integrity sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ== - dependencies: - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.12.13" - "@babel/types" "^7.12.13" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" + integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== + +"@babel/parser@^7.19.3", "@babel/parser@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" + integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== + +"@babel/parser@^7.20.1", "@babel/parser@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.2.tgz#9aeb9b92f64412b5f81064d46f6a1ac0881337f4" + integrity sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + +"@babel/plugin-proposal-async-generator-functions@7.20.1", "@babel/plugin-proposal-async-generator-functions@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" + integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/helpers@^7.13.10": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" - integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== +"@babel/plugin-proposal-async-generator-functions@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7" + integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== dependencies: - "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/highlight@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" - integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww== +"@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.16", "@babel/parser@^7.8.3": - version "7.12.16" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4" - integrity sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw== - -"@babel/parser@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.0.tgz#49b9b6ee213e5634fa80361dae139effef893f78" - integrity sha512-w80kxEMFhE3wjMOQkfdTvv0CSdRSJZptIlLhU4eU/coNJeWjduspUFz+IRnBbAq6m5XYBFMoT1TNkk9K9yf10g== - -"@babel/parser@^7.13.13": - version "7.13.13" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.13.tgz#42f03862f4aed50461e543270916b47dd501f0df" - integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw== - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a" - integrity sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ== - dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.13.12" - -"@babel/plugin-proposal-async-generator-functions@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1" - integrity sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA== - dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-remap-async-to-generator" "^7.13.0" - "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-class-properties@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz#146376000b94efd001e57a40a88a525afaab9f37" - integrity sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg== +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-dynamic-import@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d" - integrity sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ== +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-namespace-from@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz#393be47a4acd03fa2af6e3cde9b06e33de1b446d" - integrity sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw== +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b" - integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q== +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a" - integrity sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A== +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3" - integrity sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz#bd9da3188e787b5120b4f9d465a8261ce67ed1db" - integrity sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w== +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a" - integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g== +"@babel/plugin-proposal-object-rest-spread@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" + integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== + dependencies: + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.8" + +"@babel/plugin-proposal-object-rest-spread@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" + integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== dependencies: - "@babel/compat-data" "^7.13.8" - "@babel/helper-compilation-targets" "^7.13.8" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.13.0" + "@babel/plugin-transform-parameters" "^7.20.1" -"@babel/plugin-proposal-optional-catch-binding@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107" - integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA== +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz#ba9feb601d422e0adea6760c2bd6bbb7bfec4866" - integrity sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ== +"@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz#04bd4c6d40f6e6bbfa2f57e2d8094bad900ef787" - integrity sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q== +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba" - integrity sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg== +"@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" + integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -768,6 +867,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -782,6 +888,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-import-assertions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4" + integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-import-assertions@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" @@ -831,300 +951,350 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178" - integrity sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ== +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-arrow-functions@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae" - integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg== +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-async-to-generator@7.13.0", "@babel/plugin-transform-async-to-generator@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f" - integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg== +"@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-remap-async-to-generator" "^7.13.0" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoped-functions@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4" - integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg== +"@babel/plugin-transform-async-to-generator@7.18.6", "@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61" - integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ== +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-classes@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b" - integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.12.13" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-optimise-call-expression" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-replace-supers" "^7.13.0" - "@babel/helper-split-export-declaration" "^7.12.13" - globals "^11.1.0" +"@babel/plugin-transform-block-scoping@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" + integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-computed-properties@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed" - integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg== +"@babel/plugin-transform-block-scoping@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" + integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-destructuring@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963" - integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA== +"@babel/plugin-transform-classes@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" -"@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad" - integrity sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ== +"@babel/plugin-transform-classes@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" + integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-duplicate-keys@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de" - integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ== +"@babel/plugin-transform-destructuring@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" + integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-exponentiation-operator@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1" - integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA== +"@babel/plugin-transform-destructuring@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" + integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-for-of@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062" - integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg== +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-function-name@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051" - integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ== +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== dependencies: - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-literals@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9" - integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ== +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-member-expression-literals@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40" - integrity sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg== +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-amd@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3" - integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ== - dependencies: - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-commonjs@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b" - integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw== - dependencies: - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-simple-access" "^7.12.13" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-systemjs@^7.13.8": - version "7.13.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3" - integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A== - dependencies: - "@babel/helper-hoist-variables" "^7.13.0" - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-validator-identifier" "^7.12.11" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-umd@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b" - integrity sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw== - dependencies: - "@babel/helper-module-transforms" "^7.13.0" - "@babel/helper-plugin-utils" "^7.13.0" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9" - integrity sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA== +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-new-target@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c" - integrity sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ== +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-object-super@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7" - integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ== +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - "@babel/helper-replace-supers" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-parameters@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007" - integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw== +"@babel/plugin-transform-modules-amd@^7.18.6", "@babel/plugin-transform-modules-amd@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" + integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-property-literals@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81" - integrity sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A== +"@babel/plugin-transform-modules-commonjs@^7.18.6", "@babel/plugin-transform-modules-commonjs@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" + integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-simple-access" "^7.19.4" -"@babel/plugin-transform-regenerator@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz#b628bcc9c85260ac1aeb05b45bde25210194a2f5" - integrity sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA== +"@babel/plugin-transform-modules-systemjs@^7.19.0", "@babel/plugin-transform-modules-systemjs@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" + integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== dependencies: - regenerator-transform "^0.14.2" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-identifier" "^7.19.1" -"@babel/plugin-transform-reserved-words@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695" - integrity sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg== +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@7.13.10": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1" - integrity sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" + integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - babel-plugin-polyfill-corejs2 "^0.1.4" - babel-plugin-polyfill-corejs3 "^0.1.3" - babel-plugin-polyfill-regenerator "^0.1.2" - semver "^6.3.0" + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-shorthand-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" - integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw== +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd" - integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg== +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-sticky-regex@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f" - integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg== +"@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-template-literals@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d" - integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw== +"@babel/plugin-transform-parameters@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz#9a5aa370fdcce36f110455e9369db7afca0f9eeb" + integrity sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ== dependencies: - "@babel/helper-plugin-utils" "^7.13.0" + "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-typeof-symbol@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f" - integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ== +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-unicode-escapes@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz#840ced3b816d3b5127dd1d12dcedc5dead1a5e74" - integrity sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw== +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== dependencies: - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" + regenerator-transform "^0.15.0" -"@babel/plugin-transform-unicode-regex@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac" - integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA== +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-runtime@7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" + integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + semver "^6.3.0" -"@babel/preset-env@7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.12.tgz#6dff470478290582ac282fb77780eadf32480237" - integrity sha512-JzElc6jk3Ko6zuZgBtjOd01pf9yYDEIH8BcqVuYIuOkzOwDesoa/Nz4gIo4lBG6K861KTV9TvIgmFuT6ytOaAA== - dependencies: - "@babel/compat-data" "^7.13.12" - "@babel/helper-compilation-targets" "^7.13.10" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-validator-option" "^7.12.17" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12" - "@babel/plugin-proposal-async-generator-functions" "^7.13.8" - "@babel/plugin-proposal-class-properties" "^7.13.0" - "@babel/plugin-proposal-dynamic-import" "^7.13.8" - "@babel/plugin-proposal-export-namespace-from" "^7.12.13" - "@babel/plugin-proposal-json-strings" "^7.13.8" - "@babel/plugin-proposal-logical-assignment-operators" "^7.13.8" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" - "@babel/plugin-proposal-numeric-separator" "^7.12.13" - "@babel/plugin-proposal-object-rest-spread" "^7.13.8" - "@babel/plugin-proposal-optional-catch-binding" "^7.13.8" - "@babel/plugin-proposal-optional-chaining" "^7.13.12" - "@babel/plugin-proposal-private-methods" "^7.13.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.12.13" +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-env@7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" + integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== + dependencies: + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.19.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.19.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" "@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" @@ -1132,51 +1302,133 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.12.13" - "@babel/plugin-transform-arrow-functions" "^7.13.0" - "@babel/plugin-transform-async-to-generator" "^7.13.0" - "@babel/plugin-transform-block-scoped-functions" "^7.12.13" - "@babel/plugin-transform-block-scoping" "^7.12.13" - "@babel/plugin-transform-classes" "^7.13.0" - "@babel/plugin-transform-computed-properties" "^7.13.0" - "@babel/plugin-transform-destructuring" "^7.13.0" - "@babel/plugin-transform-dotall-regex" "^7.12.13" - "@babel/plugin-transform-duplicate-keys" "^7.12.13" - "@babel/plugin-transform-exponentiation-operator" "^7.12.13" - "@babel/plugin-transform-for-of" "^7.13.0" - "@babel/plugin-transform-function-name" "^7.12.13" - "@babel/plugin-transform-literals" "^7.12.13" - "@babel/plugin-transform-member-expression-literals" "^7.12.13" - "@babel/plugin-transform-modules-amd" "^7.13.0" - "@babel/plugin-transform-modules-commonjs" "^7.13.8" - "@babel/plugin-transform-modules-systemjs" "^7.13.8" - "@babel/plugin-transform-modules-umd" "^7.13.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13" - "@babel/plugin-transform-new-target" "^7.12.13" - "@babel/plugin-transform-object-super" "^7.12.13" - "@babel/plugin-transform-parameters" "^7.13.0" - "@babel/plugin-transform-property-literals" "^7.12.13" - "@babel/plugin-transform-regenerator" "^7.12.13" - "@babel/plugin-transform-reserved-words" "^7.12.13" - "@babel/plugin-transform-shorthand-properties" "^7.12.13" - "@babel/plugin-transform-spread" "^7.13.0" - "@babel/plugin-transform-sticky-regex" "^7.12.13" - "@babel/plugin-transform-template-literals" "^7.13.0" - "@babel/plugin-transform-typeof-symbol" "^7.12.13" - "@babel/plugin-transform-unicode-escapes" "^7.12.13" - "@babel/plugin-transform-unicode-regex" "^7.12.13" - "@babel/preset-modules" "^0.1.4" - "@babel/types" "^7.13.12" - babel-plugin-polyfill-corejs2 "^0.1.4" - babel-plugin-polyfill-corejs3 "^0.1.3" - babel-plugin-polyfill-regenerator "^0.1.2" - core-js-compat "^3.9.0" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.19.4" + "@babel/plugin-transform-classes" "^7.19.0" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.19.4" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.0" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.19.4" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" semver "^6.3.0" -"@babel/preset-modules@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" - integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== +"@babel/preset-env@7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" + integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.20.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.20.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@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" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.20.2" + "@babel/plugin-transform-classes" "^7.20.2" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.20.2" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.19.6" + "@babel/plugin-transform-modules-commonjs" "^7.19.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.6" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.20.2" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" @@ -1184,218 +1436,542 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@7.13.10": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" - integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== +"@babel/runtime@7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" + integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.10" "@babel/runtime@^7.8.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" - integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@7.12.13", "@babel/template@^7.12.13", "@babel/template@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" - integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/parser" "^7.12.13" - "@babel/types" "^7.12.13" - -"@babel/traverse@^7.12.13", "@babel/traverse@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.13.tgz#689f0e4b4c08587ad26622832632735fb8c4e0c0" - integrity sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.12.13" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.12.13" - "@babel/types" "^7.12.13" +"@babel/template@7.18.10", "@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" + integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.6" + "@babel/types" "^7.19.4" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.19" - -"@babel/traverse@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" - integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.13.0" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.13.0" - "@babel/types" "^7.13.0" + +"@babel/traverse@^7.19.3", "@babel/traverse@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133" + integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.5" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.5" + "@babel/types" "^7.20.5" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.19" - -"@babel/traverse@^7.13.13": - version "7.13.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.13.tgz#39aa9c21aab69f74d948a486dd28a2dbdbf5114d" - integrity sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg== - dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.13.9" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.13.13" - "@babel/types" "^7.13.13" + +"@babel/traverse@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" + integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.1" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.1" + "@babel/types" "^7.20.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.13.tgz#8be1aa8f2c876da11a9cf650c0ecf656913ad611" - integrity sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.3.0", "@babel/types@^7.4.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - lodash "^4.17.19" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" - integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== +"@babel/types@^7.19.3", "@babel/types@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" + integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - lodash "^4.17.19" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.13.12": - version "7.13.12" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.12.tgz#edbf99208ef48852acdff1c8a681a1e4ade580cd" - integrity sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA== +"@babel/types@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.0.tgz#52c94cf8a7e24e89d2a194c25c35b17a64871479" + integrity sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - lodash "^4.17.19" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.13.13": - version "7.13.13" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.13.tgz#dcd8b815b38f537a3697ce84c8e3cc62197df96f" - integrity sha512-kt+EpC6qDfIaqlP+DIbIJOclYy/A1YXs9dAf/ljbi+39Bcbc073H6jKVpXEr/EoIh5anGn5xq/yRVzKl+uIc9w== +"@babel/types@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" + integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - lodash "^4.17.19" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@bazel/bazelisk@1.7.5": - version "1.7.5" - resolved "https://registry.yarnpkg.com/@bazel/bazelisk/-/bazelisk-1.7.5.tgz#dd1a52e3d23464f72de55aa3dc4777847fa85373" - integrity sha512-JHwP9JhfZUSoj4sku471Bjw4uE773U2Agujnx0CdPkeRk25khy1l3VyjaPaHB+z1fmMnM6ED3M7tetQUsovUQg== +"@bazel/bazelisk@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@bazel/bazelisk/-/bazelisk-1.12.1.tgz#346531286564aa29eee03a62362d210f3433e7bf" + integrity sha512-TGCwVeIiVeQUP6yLpxAg8yluFOC+tBQnWw5l8lqwMxKhRtOA+WaH1CJKAXeCBAaS2MxohhkXq44zj/7AM+t2jg== -"@bazel/buildifier@4.0.1", "@bazel/buildifier@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@bazel/buildifier/-/buildifier-4.0.1.tgz#52cfbad5cbb86e9183a29dde2370cd465730ea0d" - integrity sha512-BTmtvJbeeEVrqRApI1gr5hvPgYcHLpdGJ5EXNXEWO692ztMPSj5fB/dH0xUlaW45jn6LimYx8ymqTMhj3538og== +"@bazel/buildifier@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@bazel/buildifier/-/buildifier-5.1.0.tgz#ae0b93c5d14b2b080d5a492a8bfee231101b5385" + integrity sha512-gO0+//hkH+iE3AQ02mYttJAcWiE+rapP8IxmstDhwSqs+CmZJJI8Q1vAaIvMyJUT3NIf7lGljRNpzclkCPk89w== -"@bazel/jasmine@3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-3.2.2.tgz#e4c088d8267f83c4c718ac46e5923f549d47fef6" - integrity sha512-fSz0fRdk9IgyN/9Ietzasdp2QAhMGz/E4LKdiu+WyZrPjwd06zQ4ypb3DzzT1g+M/7NXJqV8X6ahTGQMrm/Rpw== +"@bazel/concatjs@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/concatjs/-/concatjs-5.7.1.tgz#fe76bf0064382d7640651b210a97d5af35e50e39" + integrity sha512-h6PHntgP8PY5DOjl73zt4zeXcbRYX1Zk3vwpP/QYUk0APlwCHE3ZU+u7whrsV3LPAhlPg1c+t9cmHBxfmb2q7w== + dependencies: + protobufjs "6.8.8" + source-map-support "0.5.9" + tsutils "3.21.0" + +"@bazel/esbuild@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/esbuild/-/esbuild-5.7.1.tgz#3e72ad6d71ab868429a7f02f914bba8958c2e05e" + integrity sha512-mNi5AaXQ5h6kwIkgXCtwZIvzhf+iEgj54PS4riHyWmk3qwFa+XqgLK+b//9tdjNVtisrIoyiZ0+J2Q6dU5YQsA== + +"@bazel/jasmine@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-5.7.1.tgz#114797c8c2871255d32e2c154f7e354028efbdcc" + integrity sha512-OZ3u8CuC1HBv7Ph3Io46A1sRA9pSGHrbrFE/VbZGzPPLd2epyi4EvKnhrrRXpw1/jZ+0z5DXeLbnk6uJOvIuRw== dependencies: c8 "~7.5.0" - jasmine-reporters "~2.3.2" + jasmine-reporters "~2.5.0" -"@bazel/typescript@3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-3.2.2.tgz#b53ee35dee68952abd711a2d472485748c7521c1" - integrity sha512-fTPtX3kZAQ50fWJaBvHtHf4w0fqMCrOhK9A8c5GgbqRrIWF8GG/+Tbt4OPwsG0N3+JDYOsHATacJI0qkjIeG1g== +"@bazel/protractor@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-5.7.1.tgz#4f9e95ea7b54e02910efdc52807bfaf226cb303e" + integrity sha512-zskjl1yBVNTUPcdxmvi13h2+KT9YY/1+7SSaG/PkutmZVeJhp9JL2s43Rdp2DQhjVaWhTDWNHyRur6dSMImmVg== + +"@bazel/runfiles@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/runfiles/-/runfiles-5.7.1.tgz#1929078bebafcea7a813a0ea8db8720dcf67a6dd" + integrity sha512-lMovXi/ENs+I8OWcUwAiV+0ZAv5pZJfKM70i2KdVZ5vU3Roc+pgPntH77ArEcPfFh0bJjuiSAgidr6KAMdTZiQ== + +"@bazel/terser@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-5.7.1.tgz#2f247472d65fd53350f1366009b8c5245e23f705" + integrity sha512-WAy2LG7lU6M4CLHBe7UWAFpILCb0k4KpLnBp8uiQFCovSYgmhB7kThWGNwkx/dgmr8mI+aSZ8tBcv0CQc+C0ZQ== + +"@bazel/typescript@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-5.7.1.tgz#e585bcdc54a4ccb23d99c3e1206abf4853cf0682" + integrity sha512-MAnAtFxA2znadm81+rbYXcyWX1DEF/urzZ1F4LBq+w27EQ4PGyqIqCM5om7JcoSZJwjjMoBJc3SflRsMrZZ6+g== dependencies: - protobufjs "6.8.8" + "@bazel/worker" "5.7.1" semver "5.6.0" source-map-support "0.5.9" - tsutils "2.27.2" + tsutils "3.21.0" + +"@bazel/worker@5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/worker/-/worker-5.7.1.tgz#2c4a9bd0e0ef75e496aec9599ff64a87307e7dad" + integrity sha512-UndmQVRqK0t0NMNl8I1P5XmxzdPvMA0X6jufszpfwy5gyzjOxeiOIzmC0ALCOx78CuJqOB/8WOI1pwTRmhd0tg== + dependencies: + google-protobuf "^3.6.1" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@csstools/convert-colors@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" - integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== - -"@discoveryjs/json-ext@0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" - integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@jsdevtools/coverage-istanbul-loader@3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz#2a4bc65d0271df8d4435982db4af35d81754ee26" - integrity sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: - convert-source-map "^1.7.0" - istanbul-lib-instrument "^4.0.3" - loader-utils "^2.0.0" - merge-source-map "^1.1.0" - schema-utils "^2.7.0" + "@jridgewell/trace-mapping" "0.3.9" -"@mark.probst/unicode-properties@~1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@mark.probst/unicode-properties/-/unicode-properties-1.1.0.tgz#5caafeab4737df93163d6d288007df33f9939b80" - integrity sha512-7AQsO0hMmpqDledV7AhBuSYqYPFsKP9PaltMecX9nlnsyFxqtsqUg9/pvB2L/jxvskrDrNkdKYz2KTbQznCtng== +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@esbuild/android-arm@0.15.12": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.12.tgz#e548b10a5e55b9e10537a049ebf0bc72c453b769" + integrity sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA== + +"@esbuild/android-arm@0.15.13": + version "0.15.13" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.13.tgz#ce11237a13ee76d5eae3908e47ba4ddd380af86a" + integrity sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw== + +"@esbuild/linux-loong64@0.15.12": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.12.tgz#475b33a2631a3d8ca8aa95ee127f9a61d95bf9c1" + integrity sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw== + +"@esbuild/linux-loong64@0.15.13": + version "0.15.13" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz#64e8825bf0ce769dac94ee39d92ebe6272020dfc" + integrity sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag== + +"@eslint/eslintrc@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" + integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== dependencies: - brfs "^1.4.0" - unicode-trie "^0.3.0" + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.4.0" + globals "^13.15.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" + +"@gar/promisify@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@humanwhocodes/config-array@^0.11.6": + version "0.11.6" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.6.tgz#6a51d603a3aaf8d4cf45b42b3f2ac9318a4adc4b" + integrity sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg== dependencies: - "@nodelib/fs.stat" "2.0.4" - run-parallel "^1.1.9" + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": +"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@mark.probst/unicode-properties@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mark.probst/unicode-properties/-/unicode-properties-1.1.0.tgz#5caafeab4737df93163d6d288007df33f9939b80" + integrity sha512-7AQsO0hMmpqDledV7AhBuSYqYPFsKP9PaltMecX9nlnsyFxqtsqUg9/pvB2L/jxvskrDrNkdKYz2KTbQznCtng== + dependencies: + brfs "^1.4.0" + unicode-trie "^0.3.0" + +"@microsoft/api-extractor-model@7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.24.0.tgz#df71615f7c7d2c4f520c8b179d03a85efcdaf452" + integrity sha512-lFzF5h+quTyVB7eaKJkqrbQRDGSkrHzXyF8iMVvHdlaNrodGeyhtQeBFDuRVvBXTW2ILBiOV6ZWwUM1eGKcD+A== + dependencies: + "@microsoft/tsdoc" "0.14.1" + "@microsoft/tsdoc-config" "~0.16.1" + "@rushstack/node-core-library" "3.51.1" + +"@microsoft/api-extractor@7.31.0": + version "7.31.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.31.0.tgz#a4dd2af2e176a330652a19f9254f77d4fdcea06f" + integrity sha512-1gVDvm/eKmntBn5X5Rc+XDREm9gfxQ/BQfGFf7Rf4uWvJc4Q4GxidC3lBODYDOcikjG983bzbo0xTu5BS8J93Q== + dependencies: + "@microsoft/api-extractor-model" "7.24.0" + "@microsoft/tsdoc" "0.14.1" + "@microsoft/tsdoc-config" "~0.16.1" + "@rushstack/node-core-library" "3.51.1" + "@rushstack/rig-package" "0.3.14" + "@rushstack/ts-command-line" "4.12.2" + colors "~1.2.1" + lodash "~4.17.15" + resolve "~1.17.0" + semver "~7.3.0" + source-map "~0.6.1" + typescript "~4.7.4" + +"@microsoft/tsdoc-config@~0.16.1": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz#b786bb4ead00d54f53839a458ce626c8548d3adf" + integrity sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw== + dependencies: + "@microsoft/tsdoc" "0.14.2" + ajv "~6.12.6" + jju "~1.4.0" + resolve "~1.19.0" + +"@microsoft/tsdoc@0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz#155ef21065427901994e765da8a0ba0eaae8b8bd" + integrity sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw== + +"@microsoft/tsdoc@0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb" + integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== + +"@ngtools/webpack@15.0.0-rc.2": + version "15.0.0-rc.2" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-15.0.0-rc.2.tgz#92c3e20da96f738fa41142d896bff3247c1d1a42" + integrity sha512-YZ+vYVb7iXZyETYuS3X8xGi3I6WpgX07TAG/89LJOnqoOEvk1uk8sIzzKcXEOZkGI+0nFlFWj0sPGQXrUvAR1Q== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: - "@nodelib/fs.scandir" "2.1.4" + "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@npmcli/ci-detect@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.3.0.tgz#6c1d2c625fb6ef1b9dea85ad0a5afcbef85ef22a" - integrity sha512-oN3y7FAROHhrAt7Rr7PnTSwrHrZVRTS2ZbyxeQwSSYD0ifwM3YNgQqbaRmjcWoPyq77MjchusjJDspbzMmip1Q== +"@npmcli/arborist@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-5.6.2.tgz#552b554f34777e5dcc8e68ad86cdaeebc0788790" + integrity sha512-Lyj2g+foWKzrwW2bT/RGO982VR9vb5tlvfD88n4PwWJRrDttQbJoIdcQzN9b+NIBhI1/8iEhC5b8far9U0fQxA== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^2.0.3" + "@npmcli/metavuln-calculator" "^3.0.1" + "@npmcli/move-file" "^2.0.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/package-json" "^2.0.0" + "@npmcli/query" "^1.2.0" + "@npmcli/run-script" "^4.1.3" + bin-links "^3.0.3" + cacache "^16.1.3" + common-ancestor-path "^1.0.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + minimatch "^5.1.0" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + nopt "^6.0.0" + npm-install-checks "^5.0.0" + npm-package-arg "^9.0.0" + npm-pick-manifest "^7.0.2" + npm-registry-fetch "^13.0.0" + npmlog "^6.0.2" + pacote "^13.6.1" + parse-conflict-json "^2.0.1" + proc-log "^2.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.7" + ssri "^9.0.0" + treeverse "^2.0.0" + walk-up-path "^1.0.0" -"@npmcli/git@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-2.0.6.tgz#47b97e96b2eede3f38379262fa3bdfa6eae57bf2" - integrity sha512-a1MnTfeRPBaKbFY07fd+6HugY1WAkKJzdiJvlRub/9o5xz2F/JtPacZZapx5zRJUQFIzSL677vmTSxEcDMrDbg== +"@npmcli/ci-detect@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-2.0.0.tgz#e63c91bcd4185ac1e85720a34fc48e164ece5b89" + integrity sha512-8yQtQ9ArHh/TzdUDKQwEvwCgpDuhSWTDAbiKMl3854PcT+Dk4UmWaiawuFTLy9n5twzXOBXVflWe+90/ffXQrA== + +"@npmcli/config@^4.2.1": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-4.2.2.tgz#2e3334dda84f48d059309c53d152e66b05ca24b7" + integrity sha512-5GNcLd+0c4bYBnFop53+26CO5GQP0R9YcxlernohpHDWdIgzUg9I0+GEMk3sNHnLntATVU39d283A4OO+W402w== + dependencies: + "@npmcli/map-workspaces" "^2.0.2" + ini "^3.0.0" + mkdirp-infer-owner "^2.0.0" + nopt "^6.0.0" + proc-log "^2.0.0" + read-package-json-fast "^2.0.3" + semver "^7.3.5" + walk-up-path "^1.0.0" + +"@npmcli/disparity-colors@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/disparity-colors/-/disparity-colors-2.0.0.tgz#cb518166ee21573b96241a3613fef70acb2a60ba" + integrity sha512-FFXGrIjhvd2qSZ8iS0yDvbI7nbjdyT2VNO7wotosjYZM2p2r8PN3B7Om3M5NO9KqW/OVzfzLB3L0V5Vo5QXC7A== dependencies: - "@npmcli/promise-spawn" "^1.1.0" - lru-cache "^6.0.0" - mkdirp "^1.0.3" - npm-pick-manifest "^6.0.0" + ansi-styles "^4.3.0" + +"@npmcli/fs@^2.1.0", "@npmcli/fs@^2.1.1": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/fs@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.0.0.tgz#00d13fd40d9144fb0ca40faf04f755625856ccd2" + integrity sha512-GdeVD+dnBxzMslTFvnctLX5yIqV4ZNZBWNbo1OejQ++bZpnFNQ1AjOn9Sboi+LzheQbCBU1ts1mhEVduHrcZOQ== + dependencies: + semver "^7.3.5" + +"@npmcli/fs@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" + integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== + dependencies: + semver "^7.3.5" + +"@npmcli/git@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-3.0.2.tgz#5c5de6b4d70474cf2d09af149ce42e4e1dacb931" + integrity sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w== + dependencies: + "@npmcli/promise-spawn" "^3.0.0" + lru-cache "^7.4.4" + mkdirp "^1.0.4" + npm-pick-manifest "^7.0.0" + proc-log "^2.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^2.0.2" + +"@npmcli/git@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-4.0.1.tgz#b369e10e9961809e1dc71b429e290653336e022d" + integrity sha512-sfaCFyZO7Zsxia2TNHW8TeHFIUnK63896EZFA5K0vCReOMFi9aELB5RZyFveRLaBE/pT1BS6RxbTWZGjulNgSg== + dependencies: + "@npmcli/promise-spawn" "^4.0.0" + lru-cache "^7.4.4" + mkdirp "^1.0.4" + npm-pick-manifest "^8.0.0" + proc-log "^3.0.0" promise-inflight "^1.0.1" promise-retry "^2.0.1" - semver "^7.3.2" - unique-filename "^1.1.1" + semver "^7.3.5" which "^2.0.2" -"@npmcli/installed-package-contents@^1.0.6": +"@npmcli/installed-package-contents@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== @@ -1403,137 +1979,128 @@ npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== +"@npmcli/installed-package-contents@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-2.0.1.tgz#3cad3141c95613426820128757a3549bef1b346b" + integrity sha512-GIykAFdOVK31Q1/zAtT5MbxqQL2vyl9mvFJv+OGu01zxbhL3p0xc8gJjdNGX1mWmUT43aEKVO2L6V/2j4TOsAA== dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" + npm-bundled "^3.0.0" + npm-normalize-package-bin "^3.0.0" -"@npmcli/node-gyp@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.2.tgz#3cdc1f30e9736dbc417373ed803b42b1a0a29ede" - integrity sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg== - -"@npmcli/promise-spawn@^1.1.0", "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" - integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== +"@npmcli/map-workspaces@^2.0.2", "@npmcli/map-workspaces@^2.0.3": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-2.0.4.tgz#9e5e8ab655215a262aefabf139782b894e0504fc" + integrity sha512-bMo0aAfwhVwqoVM5UzX1DJnlvVvzDCHae821jv48L1EsrYwfOZChlqWYXEtto/+BkBXetPbEWgau++/brh4oVg== dependencies: - infer-owner "^1.0.4" + "@npmcli/name-from-folder" "^1.0.1" + glob "^8.0.1" + minimatch "^5.0.1" + read-package-json-fast "^2.0.3" -"@npmcli/run-script@^1.8.2": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.3.tgz#07f440ed492400bb1114369bc37315eeaaae2bb3" - integrity sha512-ELPGWAVU/xyU+A+H3pEPj0QOvYwLTX71RArXcClFzeiyJ/b/McsZ+d0QxpznvfFtZzxGN/gz/1cvlqICR4/suQ== +"@npmcli/metavuln-calculator@^3.0.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.1.tgz#9359bd72b400f8353f6a28a25c8457b562602622" + integrity sha512-n69ygIaqAedecLeVH3KnO39M6ZHiJ2dEv5A7DGvcqCB8q17BGUgW8QaanIkbWUo2aYGZqJaOORTLAlIvKjNDKA== dependencies: - "@npmcli/node-gyp" "^1.0.2" - "@npmcli/promise-spawn" "^1.3.2" - infer-owner "^1.0.4" - node-gyp "^7.1.0" - puka "^1.0.1" - read-package-json-fast "^2.0.1" + cacache "^16.0.0" + json-parse-even-better-errors "^2.3.1" + pacote "^13.0.3" + semver "^7.3.5" -"@octokit/endpoint@^6.0.1": - version "6.0.11" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1" - integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ== +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== dependencies: - "@octokit/types" "^6.0.3" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" + mkdirp "^1.0.4" + rimraf "^3.0.2" -"@octokit/graphql@^4.6.1": - version "4.6.1" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.1.tgz#f975486a46c94b7dbe58a0ca751935edc7e32cc9" - integrity sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA== +"@npmcli/move-file@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-3.0.0.tgz#3e1be5c880c9ba2d9c6453709d3ffd2d7a4cf192" + integrity sha512-mOUBUIXsqAQBfn87vGIjBAve6JmD9PkP9Vdq2SayDqQh2Ol60hnXaBSvT4V6IQiho1otw6SipnVV1fulvOiyKQ== dependencies: - "@octokit/request" "^5.3.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" + mkdirp "^1.0.4" + rimraf "^3.0.2" -"@octokit/openapi-types@^4.0.3": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-4.0.4.tgz#96fcce11e929802898646205ac567e5df592f82b" - integrity sha512-31zY8JIuz3h6RAFOnyA8FbOwhILILiBu1qD81RyZZWY7oMBhIdBn6MaAmnnptLhB4jk0g50nkQkUVP4kUzppcA== +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== -"@octokit/request-error@^1.0.2": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" - integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA== - dependencies: - "@octokit/types" "^2.0.0" - deprecation "^2.0.0" - once "^1.4.0" +"@npmcli/node-gyp@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== -"@octokit/request-error@^2.0.0": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" - integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg== +"@npmcli/node-gyp@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz#101b2d0490ef1aa20ed460e4c0813f0db560545a" + integrity sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA== + +"@npmcli/package-json@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-2.0.0.tgz#3bbcf4677e21055adbe673d9f08c9f9cde942e4a" + integrity sha512-42jnZ6yl16GzjWSH7vtrmWyJDGVa/LXPdpN2rcUWolFjc9ON2N3uz0qdBbQACfmhuJZ2lbKYtmK5qx68ZPLHMA== dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" + json-parse-even-better-errors "^2.3.1" -"@octokit/request@^5.0.0", "@octokit/request@^5.3.0": - version "5.4.14" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.14.tgz#ec5f96f78333bb2af390afa5ff66f114b063bc96" - integrity sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.7.1" - deprecation "^2.0.0" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - once "^1.4.0" - universal-user-agent "^6.0.0" - -"@octokit/rest@16.28.7": - version "16.28.7" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.28.7.tgz#a2c2db5b318da84144beba82d19c1a9dbdb1a1fa" - integrity sha512-cznFSLEhh22XD3XeqJw51OLSfyL2fcFKUO+v2Ep9MTAFfFLS1cK1Zwd1yEgQJmJoDnj4/vv3+fGGZweG+xsbIA== - dependencies: - "@octokit/request" "^5.0.0" - "@octokit/request-error" "^1.0.2" - atob-lite "^2.0.0" - before-after-hook "^2.0.0" - btoa-lite "^1.0.0" - deprecation "^2.0.0" - lodash.get "^4.4.2" - lodash.set "^4.3.2" - lodash.uniq "^4.5.0" - octokit-pagination-methods "^1.1.0" - once "^1.4.0" - universal-user-agent "^3.0.0" - url-template "^2.0.8" +"@npmcli/promise-spawn@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== + dependencies: + infer-owner "^1.0.4" -"@octokit/types@^2.0.0": - version "2.16.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" - integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q== +"@npmcli/promise-spawn@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-4.0.0.tgz#d1c0b1078f0b342220a3c2b56852d468dd6f02b2" + integrity sha512-LM/GRZSwkxar1jgd58yW5WspFWrFefh8a/KVy+sbOMa0pCwqlXWxXEjQRQzbtWExyhwPb2XSK/4mJnLeiVOYng== dependencies: - "@types/node" ">= 8" + infer-owner "^1.0.4" -"@octokit/types@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" - integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== +"@npmcli/promise-spawn@^6.0.0", "@npmcli/promise-spawn@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-6.0.1.tgz#2bf718579ad0ca2c5bd364c6a9de3e2fa6be2b00" + integrity sha512-+hcUpxgx0vEpDJI9Cn+lkTdKLoqKBXFCVps5H7FujEU2vLOp6KwqjLlxbnz8Wzgm8oEqW/u5FeNAXSFjLdCD0A== dependencies: - "@types/node" ">= 8" + which "^3.0.0" + +"@npmcli/query@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-1.2.0.tgz#46468d583cf013aa92102970700f9555314aabe4" + integrity sha512-uWglsUM3PjBLgTSmZ3/vygeGdvWEIZ3wTUnzGFbprC/RtvQSaT+GAXu1DXmSFj2bD3oOZdcRm1xdzsV2z1YWdw== + dependencies: + npm-package-arg "^9.1.0" + postcss-selector-parser "^6.0.10" + semver "^7.3.7" + +"@npmcli/run-script@^4.1.0", "@npmcli/run-script@^4.1.3", "@npmcli/run-script@^4.2.0", "@npmcli/run-script@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-4.2.1.tgz#c07c5c71bc1c70a5f2a06b0d4da976641609b946" + integrity sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + which "^2.0.2" -"@octokit/types@^6.0.3", "@octokit/types@^6.7.1": - version "6.8.5" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.8.5.tgz#797dfdad8c75718e97dc687d4c9fc49200ca8d17" - integrity sha512-ZsQawftZoi0kSF2pCsdgLURbOjtVcHnBOXiSxBKSNF56CRjARt5rb/g8WJgqB8vv4lgUEHrv06EdDKYQ22vA9Q== +"@npmcli/run-script@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.0.tgz#f89e322c729e26ae29db6cc8cc76559074aac208" + integrity sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ== dependencies: - "@octokit/openapi-types" "^4.0.3" + "@npmcli/node-gyp" "^3.0.0" + "@npmcli/promise-spawn" "^6.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^3.0.0" + which "^3.0.0" "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" @@ -1548,12 +2115,12 @@ "@protobufjs/eventemitter@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" @@ -1561,61 +2128,48 @@ "@protobufjs/float@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"@rollup/plugin-commonjs@^17.0.0": - version "17.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d" - integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== - dependencies: - "@rollup/pluginutils" "^3.1.0" - commondir "^1.0.1" - estree-walker "^2.0.1" - glob "^7.1.6" - is-reference "^1.2.1" - magic-string "^0.25.7" - resolve "^1.17.0" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@rollup/plugin-json@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" - integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw== +"@rollup/plugin-json@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-5.0.0.tgz#1e3e18302637760353c83a41d1f3c4e760afb20d" + integrity sha512-LsWDA5wJs/ggzakVuKQhZo7HPRcQZgBa3jWIVxQSFxaRToUGNi8ZBh3+k/gQ+1eInVYJgn4WBRCUkmoDrmmGzw== dependencies: - "@rollup/pluginutils" "^3.0.8" + "@rollup/pluginutils" "^4.2.1" -"@rollup/plugin-node-resolve@^11.1.0": - version "11.2.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714" - integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA== +"@rollup/plugin-node-resolve@^15.0.0": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.0.tgz#44ded58b36702de27bf36bbf19ca420bbd1d0c27" + integrity sha512-iwJbzfTzlzDDQcGmkS7EkCKwe2kSkdBrjX87Fy/KrNjr6UNnLpod0t6X66e502LRe5JJCA4FFqrEscWPnZAkig== dependencies: - "@rollup/pluginutils" "^3.1.0" - "@types/resolve" "1.17.1" - builtin-modules "^3.1.0" + "@rollup/pluginutils" "^4.2.1" + "@types/resolve" "1.20.2" deepmerge "^4.2.2" + is-builtin-module "^3.2.0" is-module "^1.0.0" - resolve "^1.19.0" + resolve "^1.22.1" -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": +"@rollup/pluginutils@^3.0.9": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -1624,40 +2178,90 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@sindresorhus/is@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" - integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== - -"@szmarczak/http-timer@^4.0.0": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" - integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== +"@rollup/pluginutils@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== dependencies: - defer-to-connect "^2.0.0" + estree-walker "^2.0.1" + picomatch "^2.2.2" + +"@rushstack/node-core-library@3.51.1": + version "3.51.1" + resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.51.1.tgz#e123053c4924722cc9614c0091fda5ed7bbc6c9d" + integrity sha512-xLoUztvGpaT5CphDexDPt2WbBx8D68VS5tYOkwfr98p90y0f/wepgXlTA/q5MUeZGGucASiXKp5ysdD+GPYf9A== + dependencies: + "@types/node" "12.20.24" + colors "~1.2.1" + fs-extra "~7.0.1" + import-lazy "~4.0.0" + jju "~1.4.0" + resolve "~1.17.0" + semver "~7.3.0" + z-schema "~5.0.2" + +"@rushstack/rig-package@0.3.14": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.3.14.tgz#f2611b59245fd7cc29c6982566b2fbb4a4192bc5" + integrity sha512-Ic9EN3kWJCK6iOxEDtwED9nrM146zCDrQaUxbeGOF+q/VLZ/HNHPw+aLqrqmTl0ZT66Sf75Qk6OG+rySjTorvQ== + dependencies: + resolve "~1.17.0" + strip-json-comments "~3.1.1" + +"@rushstack/ts-command-line@4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.12.2.tgz#59b7450c5d75190778cce8b159c7d7043c32cc4e" + integrity sha512-poBtnumLuWmwmhCEkVAgynWgtnF9Kygekxyp4qtQUSbBrkuyPQTL85c8Cva1YfoUpOdOXxezMAkUt0n5SNKGqw== + dependencies: + "@types/argparse" "1.0.38" + argparse "~1.0.9" + colors "~1.2.1" + string-argv "~0.3.1" + +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trysound/sax@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.1.1.tgz#3348564048e7a2d7398c935d466c0414ebb6a669" - integrity sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@types/autoprefixer@^9.0.0": - version "9.7.2" - resolved "https://registry.yarnpkg.com/@types/autoprefixer/-/autoprefixer-9.7.2.tgz#64b3251c9675feef5a631b7dd34cfea50a8fdbcc" - integrity sha512-QX7U7YW3zX3ex6MECtWO9folTGsXeP4b8bSjTq3I1ODM+H+sFHwGKuof+T+qBcDClGlCGtDb3SVfiTVfmcxw4g== - dependencies: - "@types/browserslist" "*" - postcss "7.x.x" +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== -"@types/babel__core@7.1.14": - version "7.1.14" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.14.tgz#faaeefc4185ec71c389f4501ee5ec84b170cc402" - integrity sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g== +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/argparse@1.0.38": + version "1.0.38" + resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" + integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== + +"@types/babel__core@7.1.20": + version "7.1.20" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.20.tgz#e168cdd612c92a2d335029ed62ac94c95b362359" + integrity sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -1666,36 +2270,53 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" - integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" -"@types/babel__template@*", "@types/babel__template@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.0.tgz#0c888dd70b3ee9eebb6e4f200e809da0076262be" - integrity sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A== +"@types/babel__template@*", "@types/babel__template@7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*": - version "7.11.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.11.0.tgz#b9a1efa635201ba9bc850323a8793ee2d36c04a0" - integrity sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" + integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== dependencies: "@babel/types" "^7.3.0" "@types/body-parser@*": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" - integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: "@types/connect" "*" "@types/node" "*" -"@types/browserslist@*": +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/browser-sync@^2.26.3": + version "2.26.3" + resolved "https://registry.yarnpkg.com/@types/browser-sync/-/browser-sync-2.26.3.tgz#d8a2c316144b55d5cb11328b7cb63dc1778de524" + integrity sha512-HIiI438D8q/DXFhdc2JELRMPtuHmR+0q+QNwP/mQoItHvPi7LK+bkZC7amKrSpnB2t4ct8BRd32LtOfd6TMNIw== + dependencies: + "@types/micromatch" "^2" + "@types/node" "*" + "@types/serve-static" "*" + chokidar "^3.0.0" + +"@types/browserslist@^4.15.0": version "4.15.0" resolved "https://registry.yarnpkg.com/@types/browserslist/-/browserslist-4.15.0.tgz#ba0265b33003a2581df1fc5f483321a30205f2d2" integrity sha512-h9LyKErRGZqMsHh9bd+FE8yCIal4S0DxKTOeui56VgVXqa66TKiuaIUxCAI7c1O0LjaUzOTcsMyOpO9GetozRA== @@ -1703,162 +2324,113 @@ browserslist "*" "@types/cacache@^15.0.0": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@types/cacache/-/cacache-15.0.0.tgz#b91c7f35f4aa11233cef8a69c4e24ee5996cdf40" - integrity sha512-SNTTXhHY+2Z1wkxUARvyswbWluo5R5M7spcMXSaOqPbNs1dWJopMa2QaaEh3kvEcOK4LCTZ25PU9LJ3EJmDUSQ== + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/cacache/-/cacache-15.0.1.tgz#3d1943cc80ade160c9ae98bd5c1ebcc538f9cd57" + integrity sha512-JhL2GFJuHMx4RMg4z0XfXB4ZkKdyiOaOLpjoYMXcyKfrkF3IBXNZBj6/Peo9zX/7PPHyfI63NWVD589cI2YTzg== dependencies: "@types/node" "*" -"@types/cacheable-request@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" - integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "*" - "@types/node" "*" - "@types/responselike" "*" - -"@types/caniuse-lite@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/caniuse-lite/-/caniuse-lite-1.0.0.tgz#6506ed4b3f8d19def130d19419062960e86cc3bc" - integrity sha512-g28510gzJpFL0xqvuGAlI+dfIr3qvUcZQVFc7f7u2VlWVqI1oBkWhGLYh3fXfoflR7HRnU4w0NRux0pPJQ7VVg== - -"@types/component-emitter@^1.2.10": - version "1.2.10" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" - integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== - -"@types/connect-history-api-fallback@*": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.3.tgz#4772b79b8b53185f0f4c9deab09236baf76ee3b4" - integrity sha512-7SxFCd+FLlxCfwVwbyPxbR4khL9aNikJhrorw8nUIOqeuooc9gifBuDQOJw5kzN7i6i3vLn9G8Wde/4QDihpYw== +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== dependencies: "@types/express-serve-static-core" "*" "@types/node" "*" "@types/connect@*": - version "3.4.34" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" - integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" -"@types/cookie@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" - integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== - -"@types/copy-webpack-plugin@^6.0.0": - version "6.4.0" - resolved "https://registry.yarnpkg.com/@types/copy-webpack-plugin/-/copy-webpack-plugin-6.4.0.tgz#225f86bc60a62052df39a110f7cbf7bc5156a0c1" - integrity sha512-f5mQG5c7xH3zLGrEmKgzLLFSGNB7Y4+4a+a1X4DvjgfbTEWEZUNNXUqGs5tBVCtb5qKPzm2z+6ixX3xirWmOCg== - dependencies: - "@types/webpack" "*" - -"@types/cors@^2.8.8": - version "2.8.10" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" - integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== - -"@types/cssnano@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.0.tgz#f1bb29d6d0813861a3d87e02946b2988d0110d4e" - integrity sha512-BC/2ibKZfPIaBLBNzkitdW1IvvX/LKW6/QXGc4Su/tAJ7mQ3f2CKBuGCCKaqGAnoKwzfuC7G/recpkARwdOwuA== - dependencies: - postcss "5 - 7" +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== -"@types/debug@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" - integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== +"@types/cors@^2.8.12": + version "2.8.12" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/eslint-scope@^3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" - integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== +"@types/eslint-scope@^3.7.3": + version "3.7.4" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" + integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "7.2.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" - integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw== + version "8.4.7" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.7.tgz#0f05a2677d1a394ff70c21a964a32d3efa05f966" + integrity sha512-ehM7cCt2RSFs42mb+lcmhFT9ouIlV92PuaeRGn8N8c98oMjG4Z5pJHA9b1QiCcuqnbPSHcyfiD3mlhqMaHsQIw== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.46": - version "0.0.46" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" - integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== +"@types/estree@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": - version "4.17.18" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" - integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@^4.16.0": - version "4.17.11" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" - integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== +"@types/express@*", "@types/express@^4.16.0", "@types/express@^4.17.13": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.18" "@types/qs" "*" "@types/serve-static" "*" -"@types/find-cache-dir@^3.0.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.0.tgz#eaaf331699dccf52c47926e4d4f8f3ed8db33f3c" - integrity sha512-+JeT9qb2Jwzw72WdjU+TSvD5O1QRPWCeRpDJV+guiIq+2hwR0DFGw+nZNbTFjMIVe6Bf4GgAKeB/6Ytx6+MbeQ== - -"@types/glob@*", "@types/glob@^7.1.1": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== +"@types/glob@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.0.0.tgz#321607e9cbaec54f687a0792b2d1d370739455d2" + integrity sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA== dependencies: "@types/minimatch" "*" "@types/node" "*" -"@types/http-cache-semantics@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" - integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== - -"@types/http-proxy-middleware@*": - version "0.19.3" - resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03" - integrity sha512-lnBTx6HCOUeIJMLbI/LaL5EmdKLhczJY5oeXZpX/cXE4rRqb3RmV7VcMpiEfYkmTjipv3h7IAyIINe4plEv7cA== +"@types/http-proxy@^1.17.4", "@types/http-proxy@^1.17.8": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" + integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== dependencies: - "@types/connect" "*" - "@types/http-proxy" "*" "@types/node" "*" -"@types/http-proxy@*", "@types/http-proxy@^1.17.4": - version "1.17.5" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.5.tgz#c203c5e6e9dc6820d27a40eb1e511c70a220423d" - integrity sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q== - dependencies: - "@types/node" "*" +"@types/ini@^1.3.31": + version "1.3.31" + resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.31.tgz#c78541a187bd88d5c73e990711c9d85214800d1b" + integrity sha512-8ecxxaG4AlVEM1k9+BsziMw8UsX0qy3jYI1ad/71RrDZ+rdL6aZB0wLfAuflQiDhkD5o4yJ0uPK3OSUic3fG0w== -"@types/inquirer@^7.3.0": - version "7.3.1" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d" - integrity sha512-osD38QVIfcdgsPCT0V3lD7eH0OFurX71Jft18bZrsVQWVRt6TuxRzlr0GJLrxoHZR2V5ph7/qP8se/dcnI7o0g== +"@types/inquirer@^8.0.0": + version "8.2.4" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.4.tgz#f7f0c76c65870c7bbc80a112c9c77ffcf7f158c1" + integrity sha512-Pxxx3i3AyK7vKAj3LRM/vF7ETcHKiLJ/u5CnNgbz/eYj/vB3xGAYtRxI5IKtq0hpe5iFHD22BKV3n6WHUu0k4Q== dependencies: "@types/through" "*" - rxjs "^6.4.0" "@types/is-windows@^1.0.0": version "1.0.0" @@ -1866,100 +2438,136 @@ integrity sha512-tJ1rq04tGKuIJoWIH0Gyuwv4RQ3+tIu7wQrC0MV47raQ44kIzXSSFKfrxFUOWVRvesoF7mrTqigXmqoZJsXwTg== "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/jasmine@~4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.3.0.tgz#1dfdfb226820911addb1b5a9031422be72c53aac" + integrity sha512-u1jWakf8CWvLfSEZyxmzkgBzOEvXH/szpT0e6G8BTkx5Eu0BhDn7sbc5dz0JBN/6Wwm9rBe+JAsk9tJRyH9ZkA== -"@types/jasmine@~3.6.0": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.6.3.tgz#824df555b8a5114f91619e78d8f59624d6f23050" - integrity sha512-5QKAG8WfC9XrOgYLXPrxv1G2IIUE6zDyzTWamhNWJO0LqPRUbZ0q0zGHDhDJ7MpFloUuyME/jpBIdPjq3/P3jA== +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" - integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/karma@^6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@types/karma/-/karma-6.3.0.tgz#535928aeee6e957464acf17d0a9e928b3aa0c27f" - integrity sha512-NQ6AzL/0UhFKrK69R9aPV1a88MBau7F47Tr3Qwg8IlKALDVfRk9w+8r3VfOso0vrFT/IkztjfAPar090laUMdg== - dependencies: - "@types/node" "*" - log4js "^6.2.1" - -"@types/keyv@*", "@types/keyv@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" - integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + version "6.3.3" + resolved "https://registry.yarnpkg.com/@types/karma/-/karma-6.3.3.tgz#fc48cc53d13ec9beeea3a2a47a2036ed7647ba29" + integrity sha512-nRMec4mTCt+tkpRqh5/pAxmnjzEgAaalIq7mdfLFH88gSRC8+bxejLiSjHMMT/vHIhJHqg4GPIGCnCFbwvDRww== dependencies: "@types/node" "*" + log4js "^6.4.1" "@types/loader-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/loader-utils/-/loader-utils-2.0.1.tgz#4073425aca25762099823f7b922e86599c2b85ec" - integrity sha512-X3jTNi/I2AEd2WrHdSqRppPkYzWkRMNGxJzeMwS0o3hVi8ZB6JCnf/XyQmqpUuCidld5lC/1VxVgTktEweRK+w== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/loader-utils/-/loader-utils-2.0.3.tgz#fbc2337358f8f4a7dc532ac0a3646c74275edf2d" + integrity sha512-sDXXzZnTLXgdso54/iOpAFSDgqhVXabCvwGAt77Agadh/Xk0QYgOk520r3tpOouI098gyqGIFywx8Op1voc3vQ== dependencies: "@types/node" "*" - "@types/webpack" "*" + "@types/webpack" "^4" "@types/long@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/micromatch@^2": + version "2.3.31" + resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-2.3.31.tgz#d13641cb6965294ed1b1d2ad561a331d6306962b" + integrity sha512-17WSoNz/GKLSfcomM8cMoJJQG2cDKvsoDFTtbwjEMxcizGb0HT6EBRi8qR7NW+XSaVdxHzq/WV/TUOm5f/ksag== + dependencies: + "@types/parse-glob" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/minimatch@*", "@types/minimatch@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/minimatch@3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" - integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== - -"@types/minimist@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" - integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== +"@types/minimatch@*", "@types/minimatch@5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== -"@types/node-fetch@^2.1.6": - version "2.5.8" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" - integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== +"@types/node-fetch@*", "@types/node-fetch@^2.1.6": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== dependencies: "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8", "@types/node@^14.14.10": - version "14.14.28" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.28.tgz#cade4b64f8438f588951a6b35843ce536853f25b" - integrity sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g== +"@types/node@*", "@types/node@>=10.0.0": + version "18.11.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.3.tgz#78a6d7ec962b596fc2d2ec102c4dd3ef073fea6a" + integrity sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A== + +"@types/node@12.20.24": + version "12.20.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c" + integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ== + +"@types/node@16.10.9": + version "16.10.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.9.tgz#8f1cdd517972f76a3b928298f4c0747cd6fef25a" + integrity sha512-H9ReOt+yqIJPCutkTYjFjlyK6WEMQYT9hLZMlWtOjFQY2ItppsWZ6RJf8Aw+jz5qTYceuHvFgPIaKOHtLAEWBw== "@types/node@^10.1.0": - version "10.17.52" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.52.tgz#dc960d4e256331b3c697b7a573ee98b882febee5" - integrity sha512-bKnO8Rcj03i6JTzweabq96k29uVNcXGB0bkwjVQTFagDgxxNged18281AZ0nTMHl+aFpPPWyPrk4Z3+NtW/z5w== + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@~12.12.6": - version "12.12.70" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.70.tgz#adf70b179c3ee17620215ee4cb5c68c95f7c37ec" - integrity sha512-i5y7HTbvhonZQE+GnUM2rz1Bi8QkzxdQmEv1LKOv4nWyaQk/gdeiTApuQR3PDJHX7WomAbpx2wlWSEpxXGZ/UQ== +"@types/node@^14.15.0": + version "14.18.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014" + integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow== -"@types/normalize-package-data@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" - integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/npm-package-arg@*", "@types/npm-package-arg@^6.1.0": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/npm-package-arg/-/npm-package-arg-6.1.1.tgz#9e2d8adc04d39824a3d9f36f738010a3f7da3c1a" + integrity sha512-452/1Kp9IdM/oR10AyqAgZOxUt7eLbm+EMJ194L6oarMYdZNiFIFAOJ7IIr0OrZXTySgfHjJezh2oiyk2kc3ag== -"@types/npm-package-arg@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@types/npm-package-arg/-/npm-package-arg-6.1.0.tgz#88bdfce72f6a3d5fa1053c8d44d655e7850642e4" - integrity sha512-vbt5fb0y1svMhu++1lwtKmZL76d0uPChFlw7kEzyUmTwfmpHRcFb8i0R8ElT69q/L+QLgK2hgECivIAvaEDwag== +"@types/npm-registry-fetch@*": + version "8.0.4" + resolved "https://registry.yarnpkg.com/@types/npm-registry-fetch/-/npm-registry-fetch-8.0.4.tgz#77b2737cde22314ccda1dfdb9568fd7769e95b90" + integrity sha512-R9yEj6+NDmXLpKNS19cIaMyaHfV0aHjy/1qbo8K9jiHyjyaYg0CEmuOV/L0Q91DZDi3SuxlYY+2XYwh9TbB+eQ== + dependencies: + "@types/node" "*" + "@types/node-fetch" "*" + "@types/npm-package-arg" "*" + "@types/npmlog" "*" + "@types/ssri" "*" + +"@types/npmlog@*": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.4.tgz#30eb872153c7ead3e8688c476054ddca004115f6" + integrity sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ== + +"@types/pacote@^11.1.3": + version "11.1.5" + resolved "https://registry.yarnpkg.com/@types/pacote/-/pacote-11.1.5.tgz#3efc5eb49069206a678f5483a7e008af06788a66" + integrity sha512-kMsfmhP2G45ngnpvH0LKd1celWnjgdiws1FHu3vMmYuoElGdqnd0ydf1ucZzeXamYnLe0NvSzGP2gYiETOEiQA== + dependencies: + "@types/node" "*" + "@types/npm-registry-fetch" "*" + "@types/npmlog" "*" + "@types/ssri" "*" + +"@types/parse-glob@*": + version "3.0.29" + resolved "https://registry.yarnpkg.com/@types/parse-glob/-/parse-glob-3.0.29.tgz#6a40ec7ebd2418ee69ee397e48e42169268a10bf" + integrity sha512-OFwMPH5eJOhtwR92GMjTNWukaKTdWQC12cBgRvrTQl5CwhruSq6734wi1CTSh5Qjm/pMJWaKOOPKZOp6FpIkXQ== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1973,104 +2581,103 @@ dependencies: "@types/parse5-sax-parser" "*" -"@types/parse5-sax-parser@*": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@types/parse5-sax-parser/-/parse5-sax-parser-5.0.1.tgz#f1e26e82bb09e48cb0c16ff6d1e88aea1e538fd5" - integrity sha512-wBEwg10aACLggnb44CwzAA27M1Jrc/8TR16zA61/rKO5XZoi7JSfLjdpXbshsm7wOlM6hpfvwygh40rzM2RsQQ== +"@types/parse5-sax-parser@*", "@types/parse5-sax-parser@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/parse5-sax-parser/-/parse5-sax-parser-5.0.2.tgz#4cdca0f8bc0ce71b17e27b96e7ca9b5f79e861ff" + integrity sha512-EQtGoduLbdMmS4N27g6wcXdCCJ70dWYemfogWuumYg+JmzRqwYvTRAbGOYFortSHtS/qRzRCFwcP3ixy62RsdA== dependencies: "@types/node" "*" "@types/parse5" "*" "@types/parse5@*": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299" - integrity sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA== + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-7.0.0.tgz#8b412a0a4461c84d6280a372bfa8c57a418a06bd" + integrity sha512-f2SeAxumolBmhuR62vNGTsSAvdz/Oj0k682xNrcKJ4dmRnTPODB74j6CPoNPzBPTHsu7Y7W7u93Mgp8Ovo8vWw== + dependencies: + parse5 "*" "@types/pidusage@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/pidusage/-/pidusage-2.0.1.tgz#45eb309be947dcfa177957ef662ce2a0a2311d48" - integrity sha512-tYYcz/+5v/EGYT83C0pIXrJGOiVBLksQvxgJboG4nGqx/gZTvq0Ro4SkAjECqMk7L4Ww58VWB4j48qeYh4/YJg== - -"@types/postcss-preset-env@^6.7.1": - version "6.7.1" - resolved "https://registry.yarnpkg.com/@types/postcss-preset-env/-/postcss-preset-env-6.7.1.tgz#fdc39e818893a5a035265daba76e498c530d2e3d" - integrity sha512-Ya5/cZSbmQ6juE7HQk0UsDNJptJzEFyCq+b0z6Rl1GKfZgIPCxecaf9uS5i2yYmSbojUKIc48XoSBgKVv/ka1g== - dependencies: - "@types/autoprefixer" "^9.0.0" - postcss "^7.0.32" + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/pidusage/-/pidusage-2.0.2.tgz#3f8c4b19ba7ea438a733d093661e92b60e5f88ee" + integrity sha512-lHgpGZjXDfjggZDLkgp4zQTYkvXq4S7RxjBjrDcPe1MBU72hESWxubutx8+AM4QkJdRxAhrQyxSA6pzHKJKlsQ== -"@types/progress@2.0.3", "@types/progress@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/progress/-/progress-2.0.3.tgz#7ccbd9c6d4d601319126c469e73b5bb90dfc8ccc" - integrity sha512-bPOsfCZ4tsTlKiBjBhKnM8jpY5nmIll166IPD58D92hR7G7kZDfx5iB9wGF4NfZrdKolebjeAr3GouYkSGoJ/A== +"@types/progress@2.0.5", "@types/progress@^2.0.3": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/progress/-/progress-2.0.5.tgz#6e0febf3a82cc0ffdc1cebb4e56d6949fd108775" + integrity sha512-ZYYVc/kSMkhH9W/4dNK/sLNra3cnkfT2nJyOAIDY+C2u6w72wa0s1aXAezVtbTsnN8HID1uhXCrLwDE2ZXpplg== dependencies: "@types/node" "*" "@types/q@^0.0.32": version "0.0.32" resolved "https://registry.yarnpkg.com/@types/q/-/q-0.0.32.tgz#bd284e57c84f1325da702babfc82a5328190c0c5" - integrity sha1-vShOV8hPEyXacCur/IKlMoGQwMU= - -"@types/q@^1.5.1": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" - integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + integrity sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug== "@types/qs@*": - version "6.9.5" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" - integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== "@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/resolve@1.17.1": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" - integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== - dependencies: - "@types/node" "*" +"@types/resolve@1.20.2", "@types/resolve@^1.17.1": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== -"@types/resolve@^1.17.1": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.19.0.tgz#75c729aedd9a0de60650facb16eb33239d3c407a" - integrity sha512-NikKq+D2KJzmaWbcz4JLVT34ybtCC4c3jV6XRGWOQvOkXka9QblEmC+sPf3vRa3lmcURkVbCk2V2BDyCO7jbXg== +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/selenium-webdriver@^3.0.0": + version "3.0.20" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.20.tgz#448771a0608ebf1c86cb5885914da6311e323c3a" + integrity sha512-6d8Q5fqS9DWOXEhMDiF6/2FjyHdmP/jSTAUyeQR7QwrFeNmYyzmvGxD5aLIHL445HjWgibs0eAig+KPnbaesXA== + +"@types/selenium-webdriver@^4.0.18": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.6.tgz#a23b15abc8023db5cd56dad736cbb8d830d38c71" + integrity sha512-qK1UbVws7APzKmL2pP1ypVXCdMrJko32anuz5Fl6qrU/1AWM/mVVY7cOM7FhsOVrIqLXo+YoKJf6+LLNtXgbSw== dependencies: - "@types/node" "*" + "@types/ws" "*" -"@types/responselike@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" - integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== - dependencies: - "@types/node" "*" +"@types/semver@^7.3.12": + version "7.3.12" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.12.tgz#920447fdd78d76b19de0438b7f60df3c4a80bf1c" + integrity sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A== -"@types/rimraf@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f" - integrity sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ== +"@types/send@^0.17.1": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== dependencies: - "@types/glob" "*" + "@types/mime" "^1" "@types/node" "*" -"@types/selenium-webdriver@^3.0.0": - version "3.0.17" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz#50bea0c3c2acc31c959c5b1e747798b3b3d06d4b" - integrity sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw== +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" -"@types/semver@^7.0.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" - integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" -"@types/serve-static@*": - version "1.13.9" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" - integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== dependencies: - "@types/mime" "^1" "@types/node" "*" "@types/source-list-map@*": @@ -2078,10 +2685,30 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/ssri@*": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/ssri/-/ssri-7.1.1.tgz#2a2c94abf0d3a8c3b07bb4ff08142dd571407bb5" + integrity sha512-DPP/jkDaqGiyU75MyMURxLWyYLwKSjnAuGe9ZCsLp9QZOpXmDfuevk769F0BS86TmRuD5krnp06qw9nSoNO+0g== + dependencies: + "@types/node" "*" + +"@types/tapable@^1": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== + +"@types/tar@^6.1.2": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.3.tgz#46a2ce7617950c4852dfd7e9cd41aa8161b9d750" + integrity sha512-YzDOr5kdAeqS8dcO6NTTHTMJ44MUCBDoLEIyPtwEn7PssKqUYL49R1iCVJPeiPzPlKi6DbH33eZkpeJ27e4vHg== + dependencies: + "@types/node" "*" + minipass "^3.3.5" + "@types/text-table@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@types/text-table/-/text-table-0.2.1.tgz#39c4d4a058a82f677392dfd09976e83d9b4c9264" - integrity sha512-dchbFCWfVgUSWEvhOkXGS7zjm+K7jCUvGrQkAHPk2Fmslfofp4HQTH2pqnQ3Pw5GPYv0zWa2AQjKtsfZThuemQ== + version "0.2.2" + resolved "https://registry.yarnpkg.com/@types/text-table/-/text-table-0.2.2.tgz#774c90cfcfbc8b4b0ebb00fecbe861dc8b1e8e26" + integrity sha512-dGoI5Af7To0R2XE8wJuc6vwlavWARsCh3UKJPjWs1YEqGUqfgBI/j/4GX0yf19/DsDPPf0YAXWAp8psNeIehLg== "@types/through@*": version "0.0.30" @@ -2090,216 +2717,332 @@ dependencies: "@types/node" "*" -"@types/uuid@^8.0.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" - integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== +"@types/tmp@^0.2.1": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" + integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA== -"@types/webpack-dev-server@^3.1.7": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.1.tgz#f8f4dac1da226d530bd15a1d5dc34b23ba766ccb" - integrity sha512-rIb+LtUkKnh7+oIJm3WiMJONd71Q0lZuqGLcSqhZ5qjN9gV/CNmZe7Bai+brnBPZ/KVYOsr+4bFLiNZwjBicLw== +"@types/uglify-js@*": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.0.tgz#95271e7abe0bf7094c60284f76ee43232aef43b9" + integrity sha512-3HO6rm0y+/cqvOyA8xcYLweF0TKXlAxmQASjbOi49Co51A1N4nR4bEwBgRoD9kNM+rqFGArjKr654SLp2CoGmQ== dependencies: - "@types/connect-history-api-fallback" "*" - "@types/express" "*" - "@types/http-proxy-middleware" "*" - "@types/serve-static" "*" - "@types/webpack" "*" + source-map "^0.6.1" -"@types/webpack-sources@^0.1.5": - version "0.1.8" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.8.tgz#078d75410435993ec8a0a2855e88706f3f751f81" - integrity sha512-JHB2/xZlXOjzjBB6fMOpH1eQAfsrpqVVIbneE0Rok16WXwFaznaI5vfg75U5WgGJm7V9W1c4xeRQDjX/zwvghA== +"@types/uuid@^8.3.1": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/webpack-sources@*": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" + integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== dependencies: "@types/node" "*" "@types/source-list-map" "*" - source-map "^0.6.1" + source-map "^0.7.3" -"@types/webpack@*": - version "5.28.0" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03" - integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w== +"@types/webpack@^4": + version "4.41.33" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.33.tgz#16164845a5be6a306bcbe554a8e67f9cac215ffc" + integrity sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g== dependencies: "@types/node" "*" - tapable "^2.2.0" - webpack "^5" + "@types/tapable" "^1" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + anymatch "^3.0.0" + source-map "^0.6.0" -"@types/yauzl@^2.9.1": - version "2.9.1" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" - integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== +"@types/ws@*", "@types/ws@8.5.3", "@types/ws@^8.5.1": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== dependencies: "@types/node" "*" -"@verdaccio/commons-api@10.0.0", "@verdaccio/commons-api@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@verdaccio/commons-api/-/commons-api-10.0.0.tgz#2d7de8722f94181f1a71891fe91198a7c14e6dea" - integrity sha512-UC8wrRI9FvqjfDeB1RijF7aVI0JJhCOI8RkEDibCT/JD8zVngphrNmgSWcjo8Es3lRiu7NugWXDSuggCCeCfUg== - dependencies: - http-errors "1.8.0" - http-status-codes "1.4.0" +"@types/yargs-parser@*", "@types/yargs-parser@^21.0.0": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@verdaccio/file-locking@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@verdaccio/file-locking/-/file-locking-10.0.0.tgz#3d476a6ba28207c795d49828438e7335166c1cfc" - integrity sha512-2tQUbJF3tQ3CY9grAlpovaF/zu8G56CBYMaeHwMBHo9rAmsJI9i7LfliHGS6Jygbs8vd0cOCPT7vl2CL9T8upw== +"@types/yargs@^17.0.0", "@types/yargs@^17.0.8": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== dependencies: - lockfile "1.0.4" + "@types/yargs-parser" "*" -"@verdaccio/local-storage@10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@verdaccio/local-storage/-/local-storage-10.0.1.tgz#0d30fc43b4be4a832c6b64ad94d483d30c6cc8ac" - integrity sha512-dnrBZeuftSFjtQhp7Y+abbWPtM1luxEhP5+6152d8dMnNmomTMZWJcr/eUwKkw6o9d39d4NA4Pimm9bXEBGrdA== - dependencies: - "@verdaccio/commons-api" "^10.0.0" - "@verdaccio/file-locking" "^10.0.0" - "@verdaccio/streams" "^10.0.0" - async "3.2.0" - debug "4.3.1" - lodash "4.17.21" - lowdb "1.0.0" - mkdirp "1.0.4" +"@types/yarnpkg__lockfile@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.5.tgz#9639020e1fb65120a2f4387db8f1e8b63efdf229" + integrity sha512-8NYnGOctzsI4W0ApsP/BIHD/LnxpJ6XaGf2AZmz4EyDYJMxtprN4279dLNI1CPZcwC9H18qYcaFv4bXi0wmokg== -"@verdaccio/readme@10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@verdaccio/readme/-/readme-10.0.0.tgz#f9627c32b309ace311318b98b2c42226823f6cd7" - integrity sha512-OD3dMnRC8SvhgytEzczMBleN+K/3lMqyWw/epeXvolCpCd7mW/Dl5zSR25GiHh/2h3eTKP/HMs4km8gS1MMLgA== +"@types/yauzl@^2.9.1": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== dependencies: - dompurify "^2.2.6" - jsdom "15.2.1" - marked "^2.0.1" - -"@verdaccio/streams@10.0.0", "@verdaccio/streams@^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@verdaccio/streams/-/streams-10.0.0.tgz#8b06e1d6f06e906ebda0f1d4089cdb651a533541" - integrity sha512-PqxxY11HhweN6z1lwfn9ydLCdnOkCPpthMZs+SGCDz8Rt6gOyrjJVslV7o4uobDipjD9+hUPpJHDeO33Qt24uw== + "@types/node" "*" -"@verdaccio/ui-theme@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@verdaccio/ui-theme/-/ui-theme-3.0.1.tgz#6c8460c46d691bc33d1dc4fbf5c0f07b85a1858a" - integrity sha512-5L8knU5yYYZ5nc3zOGIZArtuc8t+znbtMYMcvzweE2JsLF/gMCsRhpGUB69OBJYtJtut3phzrLxdranYExacmw== +"@typescript-eslint/eslint-plugin@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.1.tgz#696b9cc21dfd4749c1c8ad1307f76a36a00aa0e3" + integrity sha512-LyR6x784JCiJ1j6sH5Y0K6cdExqCCm8DJUTcwG5ThNXJj/G8o5E56u5EdG4SLy+bZAwZBswC+GYn3eGdttBVCg== + dependencies: + "@typescript-eslint/scope-manager" "5.42.1" + "@typescript-eslint/type-utils" "5.42.1" + "@typescript-eslint/utils" "5.42.1" + debug "^4.3.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.1.tgz#3e66156f2f74b11690b45950d8f5f28a62751d35" + integrity sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q== + dependencies: + "@typescript-eslint/scope-manager" "5.42.1" + "@typescript-eslint/types" "5.42.1" + "@typescript-eslint/typescript-estree" "5.42.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.1.tgz#05e5e1351485637d466464237e5259b49f609b18" + integrity sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ== + dependencies: + "@typescript-eslint/types" "5.42.1" + "@typescript-eslint/visitor-keys" "5.42.1" + +"@typescript-eslint/type-utils@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.42.1.tgz#21328feb2d4b193c5852b35aabd241ccc1449daa" + integrity sha512-WWiMChneex5w4xPIX56SSnQQo0tEOy5ZV2dqmj8Z371LJ0E+aymWD25JQ/l4FOuuX+Q49A7pzh/CGIQflxMVXg== + dependencies: + "@typescript-eslint/typescript-estree" "5.42.1" + "@typescript-eslint/utils" "5.42.1" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.1.tgz#0d4283c30e9b70d2aa2391c36294413de9106df2" + integrity sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA== + +"@typescript-eslint/typescript-estree@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.1.tgz#f9a223ecb547a781d37e07a5ac6ba9ff681eaef0" + integrity sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw== + dependencies: + "@typescript-eslint/types" "5.42.1" + "@typescript-eslint/visitor-keys" "5.42.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.42.1.tgz#2789b1cd990f0c07aaa3e462dbe0f18d736d5071" + integrity sha512-Gxvf12xSp3iYZd/fLqiQRD4uKZjDNR01bQ+j8zvhPjpsZ4HmvEFL/tC4amGNyxN9Rq+iqvpHLhlqx6KTxz9ZyQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.42.1" + "@typescript-eslint/types" "5.42.1" + "@typescript-eslint/typescript-estree" "5.42.1" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" -"@webassemblyjs/ast@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" - integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== +"@typescript-eslint/visitor-keys@5.42.1": + version "5.42.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.1.tgz#df10839adf6605e1cdb79174cf21e46df9be4872" + integrity sha512-LOQtSF4z+hejmpUvitPlc4hA7ERGoj2BVkesOcG91HCn8edLGUXbTrErmutmPbl8Bo9HjAvOO/zBKQHExXNA2A== dependencies: - "@webassemblyjs/helper-numbers" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - -"@webassemblyjs/floating-point-hex-parser@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" - integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + "@typescript-eslint/types" "5.42.1" + eslint-visitor-keys "^3.3.0" -"@webassemblyjs/helper-api-error@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" - integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== - -"@webassemblyjs/helper-buffer@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" - integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== - -"@webassemblyjs/helper-numbers@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" - integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== +"@verdaccio/commons-api@10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@verdaccio/commons-api/-/commons-api-10.2.0.tgz#3b684c31749837b0574375bb2e10644ecea9fcca" + integrity sha512-F/YZANu4DmpcEV0jronzI7v2fGVWkQ5Mwi+bVmV+ACJ+EzR0c9Jbhtbe5QyLUuzR97t8R5E/Xe53O0cc2LukdQ== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.0" - "@webassemblyjs/helper-api-error" "1.11.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" - integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== + http-errors "2.0.0" + http-status-codes "2.2.0" -"@webassemblyjs/helper-wasm-section@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" - integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== +"@verdaccio/file-locking@10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@verdaccio/file-locking/-/file-locking-10.3.0.tgz#a4342665c549163817c267bfa451e32ed3009767" + integrity sha512-FE5D5H4wy/nhgR/d2J5e1Na9kScj2wMjlLPBHz7XF4XZAVSRdm45+kL3ZmrfA6b2HTADP/uH7H05/cnAYW8bhw== dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" + lockfile "1.0.4" -"@webassemblyjs/ieee754@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" - integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== +"@verdaccio/local-storage@10.3.1": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@verdaccio/local-storage/-/local-storage-10.3.1.tgz#8cbdc6390a0eb532577ae217729cb0a4e062f299" + integrity sha512-f3oArjXPOAwUAA2dsBhfL/rSouqJ2sfml8k97RtnBPKOzisb28bgyAQW0mqwQvN4MTK5S/2xudmobFpvJAIatg== dependencies: - "@xtuc/ieee754" "^1.2.0" + "@verdaccio/commons-api" "10.2.0" + "@verdaccio/file-locking" "10.3.0" + "@verdaccio/streams" "10.2.0" + async "3.2.4" + debug "4.3.4" + lodash "4.17.21" + lowdb "1.0.0" + mkdirp "1.0.4" -"@webassemblyjs/leb128@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" - integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== - dependencies: +"@verdaccio/readme@10.4.2": + version "10.4.2" + resolved "https://registry.yarnpkg.com/@verdaccio/readme/-/readme-10.4.2.tgz#144c6a9a569684220f8d37ce05ee0efd6fd86214" + integrity sha512-b5ABzEBee+up0apyExg9y/aGLoMbkSwnMOY2JWIsNnJf7EiXs1phJIzEEFaQGoor/yZQuPD0HqKUl40175srDQ== + dependencies: + dompurify "2.4.0" + jsdom "16.7.0" + marked "4.2.2" + +"@verdaccio/streams@10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@verdaccio/streams/-/streams-10.2.0.tgz#e01d2bfdcfe8aa2389f31bc6b72a602628bd025b" + integrity sha512-FaIzCnDg0x0Js5kSQn1Le3YzDHl7XxrJ0QdIw5LrDUmLsH3VXNi4/NMlSHnw5RiTTMs4UbEf98V3RJRB8exqJA== + +"@verdaccio/ui-theme@6.0.0-6-next.50": + version "6.0.0-6-next.50" + resolved "https://registry.yarnpkg.com/@verdaccio/ui-theme/-/ui-theme-6.0.0-6-next.50.tgz#95bce41e1a04a8db283712e0d41d59c1f846a1e6" + integrity sha512-hHku5x9weS0neJ5Qb+akibPoSCmlZqRtbmLqdGPdZANmcF1r6WLPu8zxPOwV5+FLofCN81JcIq8aAmvsrdWULg== + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" - integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== -"@webassemblyjs/wasm-edit@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" - integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/helper-wasm-section" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - "@webassemblyjs/wasm-opt" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - "@webassemblyjs/wast-printer" "1.11.0" - -"@webassemblyjs/wasm-gen@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" - integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/ieee754" "1.11.0" - "@webassemblyjs/leb128" "1.11.0" - "@webassemblyjs/utf8" "1.11.0" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" -"@webassemblyjs/wasm-opt@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" - integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" + "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/wasm-parser@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" - integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-api-error" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/ieee754" "1.11.0" - "@webassemblyjs/leb128" "1.11.0" - "@webassemblyjs/utf8" "1.11.0" + "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" - integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" +"@xmldom/xmldom@^0.7.3": + version "0.7.6" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.6.tgz#6f55073fa73e65776bd85826958b98c8cd1457b5" + integrity sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -2310,12 +3053,12 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@yarnpkg/lockfile@1.1.0": +"@yarnpkg/lockfile@1.1.0", "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -JSONStream@1.3.5, JSONStream@^1.0.4: +JSONStream@1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== @@ -2323,61 +3066,66 @@ JSONStream@1.3.5, JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.0, abab@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== +abab@^2.0.3, abab@^2.0.5, abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -abbrev@1: +abbrev@1, abbrev@^1.0.0, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" + mime-types "~2.1.34" + negotiator "0.6.3" -acorn-globals@^4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" - integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== dependencies: - acorn "^6.0.1" - acorn-walk "^6.0.1" + acorn "^7.1.1" + acorn-walk "^7.1.1" -acorn-walk@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" - integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn@^6.0.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^7.1.0, acorn@^7.1.1: +acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4: - version "8.0.5" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7" - integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg== +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== -add-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" - integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= - -adjust-sourcemap-loader@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz#5ae12fb5b7b1c585e80bbb5a63ec163a1a45e61e" - integrity sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw== +adjust-sourcemap-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" + integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== dependencies: loader-utils "^2.0.0" regex-parser "^2.2.11" @@ -2387,7 +3135,7 @@ adm-zip@^0.4.9: resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -2401,10 +3149,10 @@ agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agentkeepalive@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" - integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== +agentkeepalive@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== dependencies: debug "^4.1.0" depd "^1.1.2" @@ -2418,34 +3166,36 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-formats@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.0.2.tgz#69875cb99d76c74be46e9c7a4444bc232354eba0" - integrity sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw== +ajv-formats@2.1.1, ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: ajv "^8.0.0" -ajv-keywords@^3.1.0, ajv-keywords@^3.5.2: +ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@8.1.0, ajv@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.1.0.tgz#45d5d3d36c7cdd808930cc3e603cf6200dbeb736" - integrity sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ== +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@8.11.0, ajv@^8.0.0, ajv@^8.11.0, ajv@^8.8.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@~6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2455,127 +3205,89 @@ ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^7.0.3: - version "7.2.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.4.tgz#8e239d4d56cf884bccca8cca362f508446dc160f" - integrity sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -alphanum-sort@^1.0.0, alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-colors@4.1.1, ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-colors@^3.0.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" - integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== +ansi-colors@4.1.3, ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - type-fest "^0.11.0" + type-fest "^0.21.3" -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@^3.0.0, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -apache-md5@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/apache-md5/-/apache-md5-1.1.2.tgz#ee49736b639b4f108b6e9e626c6da99306b41692" - integrity sha1-7klza2ObTxCLbp5ibG2pkwa0FpI= +apache-md5@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/apache-md5/-/apache-md5-1.1.8.tgz#ea79c6feb03abfed42b2830dde06f75df5e3bbd9" + integrity sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA== -app-root-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" - integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +archy@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== dependencies: delegates "^1.0.0" - readable-stream "^2.0.6" + readable-stream "^3.6.0" arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: +argparse@^1.0.7, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -2587,68 +3299,36 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" - integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= - dependencies: - ast-types-flow "0.0.7" - commander "^2.11.0" - -arity-n@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" - integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U= - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-differ@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" - integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== - -array-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" - integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= - -array-find-index@^1.0.1, array-find-index@^1.0.2: +array-find-index@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-flatten@^2.1.0: +array-flatten@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-ify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" - integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= +array-includes@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + get-intrinsic "^1.1.1" + is-string "^1.0.7" array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== dependencies: array-uniq "^1.0.1" @@ -2660,91 +3340,61 @@ array-union@^2.1.0: array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.5: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" -arrify@^1.0.0, arrify@^1.0.1: +arrify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -arrify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== asap@^2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types-flow@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - -async@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== +async-each-series@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/async-each-series/-/async-each-series-0.1.1.tgz#7617c1917401fd8ca4a28aadce3dbae98afeb432" + integrity sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ== -async@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= +async@3.2.4, async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== -async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== +async@^2.6.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atob-lite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" - integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== atob@^2.1.2: version "2.1.2" @@ -2756,100 +3406,104 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== -autoprefixer@^10.2.4: - version "10.2.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.4.tgz#c0e7cf24fcc6a1ae5d6250c623f0cb8beef2f7e1" - integrity sha512-DCCdUQiMD+P/as8m3XkeTUkUKuuRqLGcwD0nll7wevhqoJfMRpJlkFd1+MQh1pvupjiQuip42lc/VFvfUTMSKw== +autoprefixer@10.4.13: + version "10.4.13" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.13.tgz#b5136b59930209a321e9fa3dca2e7c4d223e83a8" + integrity sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg== dependencies: - browserslist "^4.16.1" - caniuse-lite "^1.0.30001181" - colorette "^1.2.1" - fraction.js "^4.0.13" + browserslist "^4.21.4" + caniuse-lite "^1.0.30001426" + fraction.js "^4.2.0" normalize-range "^0.1.2" - postcss-value-parser "^4.1.0" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" -autoprefixer@^9.6.1: - version "9.8.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" - integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== +autoprefixer@^10.4.12: + version "10.4.12" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.12.tgz#183f30bf0b0722af54ee5ef257f7d4320bb33129" + integrity sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q== dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" - colorette "^1.2.1" + browserslist "^4.21.4" + caniuse-lite "^1.0.30001407" + fraction.js "^4.2.0" normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: version "1.11.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axobject-query@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" - integrity sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww== +axios@0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - ast-types-flow "0.0.7" + follow-redirects "^1.14.0" -babel-loader@8.2.2: - version "8.2.2" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" - integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== +babel-loader@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.0.1.tgz#d473f30a6ffc2f2abca610c01775c40fc5c2a970" + integrity sha512-szYjslOXFlj/po5KfrVmiuBAcI6GVHFuAgC96Qd6mMPHdwl4lmAJkYtvjQ1RxxPjgdkKjd3LQgXDE4jxEutNuw== dependencies: - find-cache-dir "^3.3.1" - loader-utils "^1.4.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" + find-cache-dir "^3.3.2" + schema-utils "^4.0.0" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== +babel-loader@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.0.tgz#839e9ae88aea930864ef9ec0f356dfca96ecf238" + integrity sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA== dependencies: - object.assign "^4.1.0" + find-cache-dir "^3.3.2" + schema-utils "^4.0.0" -babel-plugin-polyfill-corejs2@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.5.tgz#8fc4779965311393594a1b9ad3adefab3860c8fe" - integrity sha512-5IzdFIjYWqlOFVr/hMYUpc+5fbfuvJTAISwIY58jhH++ZtawtNlcJnxAixlk8ahVwHCz1ipW/kpXYliEBp66wg== +babel-plugin-istanbul@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-polyfill-corejs2@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" + integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== dependencies: - "@babel/compat-data" "^7.13.0" - "@babel/helper-define-polyfill-provider" "^0.1.2" + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.4.tgz#2ae290200e953bade30907b7a3bebcb696e6c59d" - integrity sha512-ysSzFn/qM8bvcDAn4mC7pKk85Y5dVaoa9h4u0mHxOEpDzabsseONhUpR7kHxpUinfj1bjU7mUZqD23rMZBoeSg== +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.1.2" - core-js-compat "^3.8.1" + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" -babel-plugin-polyfill-regenerator@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.3.tgz#350f857225fc640ae1ec78d1536afcbb457db841" - integrity sha512-hRjTJQiOYt/wBKEc+8V8p9OJ9799blAJcuKzn1JXh3pApHoWl1Emxh2BHc6MC7Qt6bbr3uDpNxaYQnATLIudEg== +babel-plugin-polyfill-regenerator@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" + integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.1.2" + "@babel/helper-define-polyfill-provider" "^0.3.3" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-arraybuffer@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" - integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.1.2, base64-js@^1.3.1: +base64-js@^1.2.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -2859,63 +3513,45 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== dependencies: tweetnacl "^0.14.3" bcryptjs@2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" - integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= - -before-after-hook@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.1.tgz#99ae36992b5cfab4a83f6bee74ab27835f28f405" - integrity sha512-5ekuQOvO04MDj7kYZJaMab2S8SPjGJbotVNyv7QYFCOAwrGZs/YnoDNlh1U+m5hl7H2D/+n0taaAV/tfyd3KMA== + integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +bin-links@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-3.0.3.tgz#3842711ef3db2cd9f16a5f404a996a12db355a6e" + integrity sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA== + dependencies: + cmd-shim "^5.0.0" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^2.0.0" + read-cmd-shim "^3.0.0" + rimraf "^3.0.0" + write-file-atomic "^4.0.0" -binary-extensions@^2.0.0: +binary-extensions@^2.0.0, binary-extensions@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -2932,43 +3568,43 @@ blocking-proxy@^1.0.0: dependencies: minimist "^1.2.0" -body-parser@1.19.0, body-parser@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== +body-parser@1.20.1, body-parser@^1.19.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: - bytes "3.1.0" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= +bonjour-service@^1.0.11: + version "1.0.14" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.14.tgz#c346f5bc84e87802d08f8d5a60b93f758e514ee7" + integrity sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ== dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" + array-flatten "^2.1.2" dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== bootstrap@^4.0.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.0.tgz#97b9f29ac98f98dfa43bf7468262d84392552fd7" - integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw== + version "4.6.2" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.2.tgz#8e0cd61611728a5bf65a3a2b8d6ff6c77d5d7479" + integrity sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ== brace-expansion@^1.1.7: version "1.1.11" @@ -2978,23 +3614,14 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" + balanced-match "^1.0.0" -braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -3011,13 +3638,6 @@ brfs@^1.4.0: static-module "^2.2.0" through2 "^2.0.0" -brotli@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.2.tgz#525a9cad4fcba96475d7d388f6aecb13eed52f46" - integrity sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y= - dependencies: - base64-js "^1.1.2" - browser-or-node@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-1.3.0.tgz#f2a4e8568f60263050a6714b2cc236bb976647a7" @@ -3028,16 +3648,74 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@*, browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.1, browserslist@^4.16.3, browserslist@^4.6.4, browserslist@^4.9.1: - version "4.16.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" - integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== +browser-sync-client@^2.27.10: + version "2.27.10" + resolved "https://registry.yarnpkg.com/browser-sync-client/-/browser-sync-client-2.27.10.tgz#f06233ea66bd873b96664f001cbc49035022634d" + integrity sha512-KCFKA1YDj6cNul0VsA28apohtBsdk5Wv8T82ClOZPZMZWxPj4Ny5AUbrj9UlAb/k6pdxE5HABrWDhP9+cjt4HQ== dependencies: - caniuse-lite "^1.0.30001181" - colorette "^1.2.1" - electron-to-chromium "^1.3.649" - escalade "^3.1.1" - node-releases "^1.1.70" + etag "1.8.1" + fresh "0.5.2" + mitt "^1.1.3" + rxjs "^5.5.6" + typescript "^4.6.2" + +browser-sync-ui@^2.27.10: + version "2.27.10" + resolved "https://registry.yarnpkg.com/browser-sync-ui/-/browser-sync-ui-2.27.10.tgz#59dd6e436e17b743c99094ff5129306ab7ab5b79" + integrity sha512-elbJILq4Uo6OQv6gsvS3Y9vRAJlWu+h8j0JDkF0X/ua+3S6SVbbiWnZc8sNOFlG7yvVGIwBED3eaYQ0iBo1Dtw== + dependencies: + async-each-series "0.1.1" + connect-history-api-fallback "^1" + immutable "^3" + server-destroy "1.0.1" + socket.io-client "^4.4.1" + stream-throttle "^0.1.3" + +browser-sync@^2.27.7: + version "2.27.10" + resolved "https://registry.yarnpkg.com/browser-sync/-/browser-sync-2.27.10.tgz#3568d4f66afb0f68fee4a10902ecbbe8b2f680dd" + integrity sha512-xKm+6KJmJu6RuMWWbFkKwOCSqQOxYe3nOrFkKI5Tr/ZzjPxyU3pFShKK3tWnazBo/3lYQzN7fzjixG8fwJh1Xw== + dependencies: + browser-sync-client "^2.27.10" + browser-sync-ui "^2.27.10" + bs-recipes "1.3.4" + bs-snippet-injector "^2.0.1" + chokidar "^3.5.1" + connect "3.6.6" + connect-history-api-fallback "^1" + dev-ip "^1.0.1" + easy-extender "^2.3.4" + eazy-logger "3.1.0" + etag "^1.8.1" + fresh "^0.5.2" + fs-extra "3.0.1" + http-proxy "^1.18.1" + immutable "^3" + localtunnel "^2.0.1" + micromatch "^4.0.2" + opn "5.3.0" + portscanner "2.2.0" + qs "6.2.3" + raw-body "^2.3.2" + resp-modifier "6.0.2" + rx "4.1.0" + send "0.16.2" + serve-index "1.9.1" + serve-static "1.13.2" + server-destroy "1.0.1" + socket.io "^4.4.1" + ua-parser-js "1.0.2" + yargs "^17.3.1" + +browserslist@*, browserslist@4.21.4, browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.9.1: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" browserstack@^1.5.1: version "1.6.1" @@ -3046,35 +3724,35 @@ browserstack@^1.5.1: dependencies: https-proxy-agent "^2.2.1" -btoa-lite@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" - integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +bs-recipes@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/bs-recipes/-/bs-recipes-1.3.4.tgz#0d2d4d48a718c8c044769fdc4f89592dc8b69585" + integrity sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw== + +bs-snippet-injector@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/bs-snippet-injector/-/bs-snippet-injector-2.0.1.tgz#61b5393f11f52559ed120693100343b6edb04dd5" + integrity sha512-4u8IgB+L9L+S5hknOj3ddNSb42436gsnGm1AuM15B7CdbkpQTyVWgIM5/JUBiKiRwGOR86uo0Lu/OsX+SAlJmw== buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" - integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + integrity sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA== buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer@^5.2.1, buffer@^5.5.0: version "5.7.1" @@ -3084,30 +3762,27 @@ buffer@^5.2.1, buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - -builtin-modules@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" - integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= +builtins@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== c8@~7.5.0: version "7.5.0" @@ -3128,87 +3803,68 @@ c8@~7.5.0: yargs "^16.0.0" yargs-parser "^20.0.0" -cacache@15.0.6: - version "15.0.6" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.6.tgz#65a8c580fda15b59150fb76bf3f3a8e45d583099" - integrity sha512-g1WYDMct/jzW+JdWEyjaX2zoBkZ6ZT9VpOyp2I/VMtDsNLffNat3kqPFfi1eDRSK9/SuKGyORDHcQMcPF8sQ/w== +cacache@17.0.1, cacache@^17.0.0: + version "17.0.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.1.tgz#bcba581a1b311b552b21f71d6205c67551a8d6d5" + integrity sha512-HRnDSZUXB5hdCQc2wuB8eBQPe1a9PVU2Ow8zMTi82NGJZmBGNTSjEGzetlndKlqpVYBa4esdaJ2LH6/uOB4sFQ== dependencies: - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" + "@npmcli/fs" "^3.0.0" + "@npmcli/move-file" "^3.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" minipass-collect "^1.0.2" minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" + minipass-pipeline "^1.2.4" p-map "^4.0.0" promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + +cacache@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.2.tgz#ff2bd029bf45099b3fe711f56fbf138b846c8d6d" + integrity sha512-rYUs2x4OjSgCQND7nTrh21AHIBFgd7s/ctAYvU3a8u+nK+R5YaX/SFPDYz4Azz7SGL6+6L9ZZWI4Kawpb7grzQ== + dependencies: + "@npmcli/fs" "^3.1.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" -cacache@^15.0.5: - version "15.0.5" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" - integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== +cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3: + version "16.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== dependencies: - "@npmcli/move-file" "^1.0.1" + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" + fs-minipass "^2.1.0" + glob "^8.0.1" infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" + lru-cache "^7.7.1" + minipass "^3.1.6" minipass-collect "^1.0.2" minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" p-map "^4.0.0" promise-inflight "^1.0.1" rimraf "^3.0.2" - ssri "^8.0.0" - tar "^6.0.2" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cacheable-lookup@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38" - integrity sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg== - dependencies: - "@types/keyv" "^3.1.1" - keyv "^4.0.0" - -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^2.0.0" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" @@ -3218,91 +3874,35 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181: - version "1.0.30001187" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001187.tgz#5706942631f83baa5a0218b7dfa6ced29f845438" - integrity sha512-w7/EP1JRZ9552CyrThUnay2RkZ1DXxKe/Q2swTC4+LElLh9RRYrL1Z+27LlakB8kzY0fSmHw9mc7XYDUKAKWMA== +caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407: + version "1.0.30001423" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz#57176d460aa8cd85ee1a72016b961eb9aca55d91" + integrity sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ== -canonical-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d" - integrity sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg== +caniuse-lite@^1.0.30001426: + version "1.0.30001426" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz#58da20446ccd0cb1dfebd11d2350c907ee7c2eaa" + integrity sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -3310,7 +3910,7 @@ chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -3319,10 +3919,10 @@ chalk@^2.0.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -3332,39 +3932,20 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.4.2, chokidar@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== +chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.1, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" - optionalDependencies: - fsevents "~2.3.1" - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" + readdirp "~3.6.0" optionalDependencies: - fsevents "^1.2.7" + fsevents "~2.3.2" chownr@^1.1.1: version "1.1.4" @@ -3377,41 +3958,39 @@ chownr@^2.0.0: integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -circular-dependency-plugin@5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" - integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== +cidr-regex@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-3.1.1.tgz#ba1972c57c66f61875f18fd7dd487469770b571d" + integrity sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw== + dependencies: + ip-regex "^4.1.0" -clang-format@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.5.0.tgz#1bd4c47b66a1a02556b192b93f5505e7ccec84fb" - integrity sha512-C1LucFX7E+ABVYcPEbBHM4PYQ2+WInXsqsLpFlQ9cmRfSbk7A7b1I06h/nE4bQ3MsyEkb31jY2gC0Dtc76b4IA== +clang-format@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.8.0.tgz#7779df1c5ce1bc8aac1b0b02b4e479191ef21d46" + integrity sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw== dependencies: - async "^1.5.2" + async "^3.2.3" glob "^7.0.0" resolve "^1.1.6" -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-columns@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646" + integrity sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ== + dependencies: + string-width "^4.2.3" + strip-ansi "^6.0.1" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -3419,39 +3998,31 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-progress@^3.7.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.9.0.tgz#25db83447deb812e62d05bac1af9aec5387ef3d4" - integrity sha512-g7rLWfhAo/7pF+a/STFH/xPyosaL1zgADhI0OM83hl3c7S43iGvJWEAV2QuDOnQ8i6EMBj/u4+NTd0d5L+4JfA== +cli-spinners@^2.5.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" + integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== + +cli-table3@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" + integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== dependencies: - colors "^1.1.2" string-width "^4.2.0" - -cli-spinners@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.5.0.tgz#12763e47251bf951cb75c201dfa58ff1bcb2d047" - integrity sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ== + optionalDependencies: + "@colors/colors" "1.5.0" cli-width@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -clipanion@3.0.0-rc.11: - version "3.0.0-rc.11" - resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-3.0.0-rc.11.tgz#390f93b91b9a6e2018c1cc8670b6a3b043203897" - integrity sha512-e0GyZXjprnxRuls/pHH0cqAgKtpiceQy8PztEZP9fdnkMxuo05D5en9fx60Iiqqjge8pmrMfsiglueS8SWmlgg== - dependencies: - typanion "^3.3.0" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== +clipanion@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-3.1.0.tgz#3e217dd6476bb9236638b07eb4673f7309839819" + integrity sha512-v025Hz+IDQ15FpOyK8p02h5bFznMu6rLFsJSyOPR+7WrbSnZ1Ek6pblPukV7K5tC/dsWfncQPIrJ4iUy2PXkbw== dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + typanion "^3.3.1" cliui@^6.0.0: version "6.0.0" @@ -3471,6 +4042,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -3480,66 +4060,24 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== +cmd-shim@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -codelyzer@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-6.0.1.tgz#c0e9668e847255b37c759e68fb2700b11e277d0f" - integrity sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g== - dependencies: - "@angular/compiler" "9.0.0" - "@angular/core" "9.0.0" - app-root-path "^3.0.0" - aria-query "^3.0.0" - axobject-query "2.0.2" - css-selector-tokenizer "^0.7.1" - cssauron "^1.4.0" - damerau-levenshtein "^1.0.4" - rxjs "^6.5.3" - semver-dsl "^1.0.1" - source-map "^0.5.7" - sprintf-js "^1.1.2" - tslib "^1.10.0" - zone.js "~0.10.3" + mkdirp-infer-owner "^2.0.0" collection-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/collection-utils/-/collection-utils-1.0.1.tgz#31d14336488674f27aefc0a7c5eccacf6df78044" integrity sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg== -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -3556,44 +4094,41 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0, color@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" - integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.4" - -colorette@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" - integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^2.0.10: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== -colors@1.4.0, colors@^1.1.2, colors@^1.4.0: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== +colors@~1.2.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== + +columnify@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== + dependencies: + strip-ansi "^6.0.1" + wcwidth "^1.0.0" + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3601,50 +4136,25 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.11.0, commander@^2.12.1, commander@^2.20.0: +commander@^2.2.0, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff" - integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg== +commander@^9.4.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" + integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== -commander@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -common-tags@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" - integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= - -compare-func@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" - integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== - dependencies: - array-ify "^1.0.0" - dot-prop "^5.1.0" - -component-emitter@^1.2.1, component-emitter@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -compose-function@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" - integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8= - dependencies: - arity-n "^1.0.4" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== compressible@~2.0.16: version "2.0.18" @@ -3669,9 +4179,9 @@ compression@1.7.4, compression@^1.7.4: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.2, concat-stream@~1.6.0: +concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3681,11 +4191,26 @@ concat-stream@^1.5.2, concat-stream@~1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -connect-history-api-fallback@^1.6.0: +connect-history-api-fallback@^1: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +connect@3.6.6: + version "3.6.6" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.6.tgz#09eff6c55af7236e137135a72574858b6786f524" + integrity sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.0" + parseurl "~1.3.2" + utils-merge "1.0.1" + connect@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" @@ -3696,209 +4221,42 @@ connect@^3.7.0: parseurl "~1.3.3" utils-merge "1.0.1" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: - safe-buffer "5.1.2" + safe-buffer "5.2.1" content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -conventional-changelog-angular@^5.0.12: - version "5.0.12" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" - integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== - dependencies: - compare-func "^2.0.0" - q "^1.5.1" - -conventional-changelog-atom@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz#a759ec61c22d1c1196925fca88fe3ae89fd7d8de" - integrity sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw== - dependencies: - q "^1.5.1" - -conventional-changelog-codemirror@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz#398e9530f08ce34ec4640af98eeaf3022eb1f7dc" - integrity sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw== - dependencies: - q "^1.5.1" - -conventional-changelog-conventionalcommits@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.5.0.tgz#a02e0b06d11d342fdc0f00c91d78265ed0bc0a62" - integrity sha512-buge9xDvjjOxJlyxUnar/+6i/aVEVGA7EEh4OafBCXPlLUQPGbRUBhBUveWRxzvR8TEjhKEP4BdepnpG2FSZXw== - dependencies: - compare-func "^2.0.0" - lodash "^4.17.15" - q "^1.5.1" - -conventional-changelog-core@^4.2.1: - version "4.2.2" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.2.tgz#f0897df6d53b5d63dec36b9442bd45354f8b3ce5" - integrity sha512-7pDpRUiobQDNkwHyJG7k9f6maPo9tfPzkSWbRq97GGiZqisElhnvUZSvyQH20ogfOjntB5aadvv6NNcKL1sReg== - dependencies: - add-stream "^1.0.0" - conventional-changelog-writer "^4.0.18" - conventional-commits-parser "^3.2.0" - dateformat "^3.0.0" - get-pkg-repo "^1.0.0" - git-raw-commits "^2.0.8" - git-remote-origin-url "^2.0.0" - git-semver-tags "^4.1.1" - lodash "^4.17.15" - normalize-package-data "^3.0.0" - q "^1.5.1" - read-pkg "^3.0.0" - read-pkg-up "^3.0.0" - shelljs "^0.8.3" - through2 "^4.0.0" - -conventional-changelog-ember@^2.0.9: - version "2.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz#619b37ec708be9e74a220f4dcf79212ae1c92962" - integrity sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A== - dependencies: - q "^1.5.1" - -conventional-changelog-eslint@^3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz#689bd0a470e02f7baafe21a495880deea18b7cdb" - integrity sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA== - dependencies: - q "^1.5.1" - -conventional-changelog-express@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz#420c9d92a347b72a91544750bffa9387665a6ee8" - integrity sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ== - dependencies: - q "^1.5.1" - -conventional-changelog-jquery@^3.0.11: - version "3.0.11" - resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz#d142207400f51c9e5bb588596598e24bba8994bf" - integrity sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw== - dependencies: - q "^1.5.1" - -conventional-changelog-jshint@^2.0.9: - version "2.0.9" - resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz#f2d7f23e6acd4927a238555d92c09b50fe3852ff" - integrity sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA== - dependencies: - compare-func "^2.0.0" - q "^1.5.1" - -conventional-changelog-preset-loader@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" - integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== - -conventional-changelog-writer@^4.0.18: - version "4.1.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" - integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== - dependencies: - compare-func "^2.0.0" - conventional-commits-filter "^2.0.7" - dateformat "^3.0.0" - handlebars "^4.7.6" - json-stringify-safe "^5.0.1" - lodash "^4.17.15" - meow "^8.0.0" - semver "^6.0.0" - split "^1.0.0" - through2 "^4.0.0" - -conventional-changelog@^3.0.0: - version "3.1.24" - resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-3.1.24.tgz#ebd180b0fd1b2e1f0095c4b04fd088698348a464" - integrity sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg== - dependencies: - conventional-changelog-angular "^5.0.12" - conventional-changelog-atom "^2.0.8" - conventional-changelog-codemirror "^2.0.8" - conventional-changelog-conventionalcommits "^4.5.0" - conventional-changelog-core "^4.2.1" - conventional-changelog-ember "^2.0.9" - conventional-changelog-eslint "^3.0.9" - conventional-changelog-express "^2.0.6" - conventional-changelog-jquery "^3.0.11" - conventional-changelog-jshint "^2.0.9" - conventional-changelog-preset-loader "^2.3.4" - -conventional-commits-filter@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" - integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== - dependencies: - lodash.ismatch "^4.4.0" - modify-values "^1.0.0" - -conventional-commits-parser@^3.0.0, conventional-commits-parser@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz#9e261b139ca4b7b29bcebbc54460da36894004ca" - integrity sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ== - dependencies: - JSONStream "^1.0.4" - is-text-path "^1.0.1" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^2.0.0" - through2 "^4.0.0" - trim-off-newlines "^1.0.0" - -conventional-commits-parser@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz#ba44f0b3b6588da2ee9fd8da508ebff50d116ce2" - integrity sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA== - dependencies: - JSONStream "^1.0.4" - is-text-path "^1.0.1" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" - trim-off-newlines "^1.0.0" - -convert-source-map@1.7.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== - dependencies: - safe-buffer "~5.1.1" - -convert-source-map@^0.3.3: - version "0.3.5" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" - integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= +convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== cookie@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== cookies@0.8.0: version "0.8.0" @@ -3909,47 +4267,40 @@ cookies@0.8.0: keygrip "~1.1.0" copy-anything@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" - integrity sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ== + version "2.0.6" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== dependencies: - is-what "^3.12.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + is-what "^3.14.1" -copy-webpack-plugin@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-8.1.1.tgz#3f697e162764925c2f0d235f380676125508fd26" - integrity sha512-rYM2uzRxrLRpcyPqGceRBDpxxUV8vcDqIKxAUKfcnFpcrPxT5+XvhTxv7XLjo5AvEJFPdAE3zCogG2JVahqgSQ== +copy-webpack-plugin@11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== dependencies: - fast-glob "^3.2.5" - glob-parent "^5.1.1" - globby "^11.0.3" + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" normalize-path "^3.0.0" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" -core-js-compat@^3.8.1, core-js-compat@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.0.tgz#29da39385f16b71e1915565aa0385c4e0963ad56" - integrity sha512-YK6fwFjCOKWwGnjFUR3c544YsnA/7DoLL0ysncuOJ4pwbriAtOpvM2bygdlcXbvQCQZ7bBU9CL4t7tGl7ETRpQ== +core-js-compat@^3.25.1: + version "3.25.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.5.tgz#0016e8158c904f7b059486639e6e82116eafa7d9" + integrity sha512-ovcyhs2DEBUIE0MGEKHP4olCUW/XYte3Vroyxuh38rD1wAO4dHohsovUC4eAOuzFxE6b+RXvBU3UZ9o0YhUTkA== dependencies: - browserslist "^4.16.3" - semver "7.0.0" - -core-js@3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.1.tgz#e683963978b6806dcc6c0a4a8bd4ab0bdaf3f21a" - integrity sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA== + browserslist "^4.21.4" -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cors@2.8.5, cors@~2.8.5: version "2.8.5" @@ -3959,20 +4310,10 @@ cors@2.8.5, cors@~2.8.5: object-assign "^4" vary "^1" -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -3985,29 +4326,33 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -critters@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.10.tgz#edd0e962fc5af6c4adb6dbf1a71bae2d3f917000" - integrity sha512-p5VKhP1803+f+0Jq5P03w1SbiHtpAKm+1EpJHkiPxQPq0Vu9QLZHviJ02GRrWi0dlcJqrmzMWInbwp4d22RsGw== +critters@0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.16.tgz#ffa2c5561a65b43c53b940036237ce72dcebfe93" + integrity sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A== dependencies: chalk "^4.1.0" - css "^3.0.0" + css-select "^4.2.0" parse5 "^6.0.1" parse5-htmlparser2-tree-adapter "^6.0.1" + postcss "^8.3.7" pretty-bytes "^5.3.0" -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" + cross-spawn "^7.0.1" + +cross-fetch@3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" -cross-spawn@^7.0.0: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4016,336 +4361,81 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -css-blank-pseudo@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" - integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== - dependencies: - postcss "^7.0.5" - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - -css-color-names@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67" - integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA== - -css-declaration-sorter@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.0.0.tgz#eb21f75860078627e9e3cc6f5535ccfcea445817" - integrity sha512-S0TE4E0ha5+tBHdLWPc5n+S8E4dFBS5xScPvgHkLNZwWvX4ISoFGhGeerLC9uS1cKA/sC+K2wHq6qEbcagT/fg== - dependencies: - timsort "^0.3.0" - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-has-pseudo@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" - integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^5.0.0-rc.4" - -css-loader@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.1.tgz#15fbd5b6ac4c1b170a098f804c5abd0722f2aa73" - integrity sha512-YCyRzlt/jgG1xanXZDG/DHqAueOtXFHeusP9TS478oP1J++JSKOyEgGW1GHVoCj/rkS+GWOlBwqQJBr9yajQ9w== +css-loader@6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== dependencies: - camelcase "^6.2.0" - cssesc "^3.0.0" icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.8" + postcss "^8.4.7" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" - semver "^7.3.4" + postcss-value-parser "^4.2.0" + semver "^7.3.5" -css-parse@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" - integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= +css-loader@6.7.3: + version "6.7.3" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd" + integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ== dependencies: - css "^2.0.0" + icss-utils "^5.1.0" + postcss "^8.4.19" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.8" -css-prefers-color-scheme@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" - integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== +css-select@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== dependencies: - postcss "^7.0.5" + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -css-select@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8" - integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA== - dependencies: - boolbase "^1.0.0" - css-what "^4.0.0" - domhandler "^4.0.0" - domutils "^2.4.3" - nth-check "^2.0.0" +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== -css-selector-tokenizer@^0.7.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" - integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== - dependencies: - cssesc "^3.0.0" - fastparse "^1.1.2" +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" + cssom "~0.3.6" -css-tree@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5" - integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" +cuint@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" + integrity sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw== -css-what@^3.2.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" - integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== - -css-what@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233" - integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A== - -css@^2.0.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" - integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== - dependencies: - inherits "^2.0.3" - source-map "^0.6.1" - source-map-resolve "^0.5.2" - urix "^0.1.0" - -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - -cssauron@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cssauron/-/cssauron-1.4.0.tgz#a6602dff7e04a8306dc0db9a551e92e8b5662ad8" - integrity sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg= - dependencies: - through X.X.X - -cssdb@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" - integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== - -cssesc@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" - postcss-unique-selectors "^4.0.1" - -cssnano-preset-default@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.0.0.tgz#94c03ecc1cb47ecdc23c0aea3ca05170ebbb7e33" - integrity sha512-zsLppqF7PxY6Tk+ghVx8djf4o1jIOu2GNufqy9lMxldt7gGpSy3FQ6jn7FCd5DZWCaBa7A/1/HVh8CK3BdFSJg== - dependencies: - css-declaration-sorter "6.0.0" - cssnano-utils "^2.0.0" - postcss-calc "^8.0.0" - postcss-colormin "^5.0.0" - postcss-convert-values "^5.0.0" - postcss-discard-comments "^5.0.0" - postcss-discard-duplicates "^5.0.0" - postcss-discard-empty "^5.0.0" - postcss-discard-overridden "^5.0.0" - postcss-merge-longhand "^5.0.0" - postcss-merge-rules "^5.0.0" - postcss-minify-font-values "^5.0.0" - postcss-minify-gradients "^5.0.0" - postcss-minify-params "^5.0.0" - postcss-minify-selectors "^5.0.0" - postcss-normalize-charset "^5.0.0" - postcss-normalize-display-values "^5.0.0" - postcss-normalize-positions "^5.0.0" - postcss-normalize-repeat-style "^5.0.0" - postcss-normalize-string "^5.0.0" - postcss-normalize-timing-functions "^5.0.0" - postcss-normalize-unicode "^5.0.0" - postcss-normalize-url "^5.0.0" - postcss-normalize-whitespace "^5.0.0" - postcss-ordered-values "^5.0.0" - postcss-reduce-initial "^5.0.0" - postcss-reduce-transforms "^5.0.0" - postcss-svgo "^5.0.0" - postcss-unique-selectors "^5.0.0" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== - dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.0.tgz#b04baaa312aa3dd5a854b7f61d76b9d94be07f74" - integrity sha512-xvxmTszdrvSyTACdPe8VU5J6p4sm3egpgw54dILvNqt5eBUv6TFjACLhSxtRuEsxYrgy8uDy269YjScO5aKbGA== - -cssnano@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.1.tgz#ed4822c4a9212f22f6820717859c52a6b7f9cf5c" - integrity sha512-5WubEmKcK2cqw43DUAayRBiIlTdX7iX3ZowrWDVxSVcW3hyohVnbJ4K4mbnWtJp5rfJnUwHg5H4mDAGzmuCM3g== - dependencies: - cosmiconfig "^7.0.0" - cssnano-preset-default "^5.0.0" - is-resolvable "^1.1.0" - -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2, csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -cssom@^0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - -cuint@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" - integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= +custom-event@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" + integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== d@1, d@^1.0.1: version "1.0.1" @@ -4355,190 +4445,121 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" -damerau-levenshtein@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" - integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== - -dargs@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" - integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== dependencies: assert-plus "^1.0.0" -data-urls@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" - integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== dependencies: - abab "^2.0.0" - whatwg-mimetype "^2.2.0" - whatwg-url "^7.0.0" - -date-format@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" - integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== - -date-format@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" - integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" -dateformat@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" - integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +date-format@^4.0.14: + version "4.0.14" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" + integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== -dayjs@1.10.4: - version "1.10.4" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" - integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== +dayjs@1.11.6: + version "1.11.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" + integrity sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@4.3.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@~4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" -debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= - -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== -decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decimal.js@^10.2.1: + version "10.4.2" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e" + integrity sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA== decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -decompress-response@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f" - integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw== - dependencies: - mimic-response "^2.0.0" - -deep-equal@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" + execa "^5.0.0" defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== dependencies: clone "^1.0.2" -defer-to-connect@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" - integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== - define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" del@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" - integrity sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag= + integrity sha512-Z4fzpbIRjOu7lO5jCETSWoqUDVe0IPOlfugBsF6suen2LKDlVb4QZpKEM9P+buNJ4KI1eN7I083w/pbKUpsrWQ== dependencies: globby "^5.0.0" is-path-cwd "^1.0.0" @@ -4548,68 +4569,60 @@ del@^2.2.0: pinkie-promise "^2.0.0" rimraf "^2.2.8" -del@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" - integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== - dependencies: - "@types/glob" "^7.1.1" - globby "^6.1.0" - is-path-cwd "^2.0.0" - is-path-in-cwd "^2.0.0" - p-map "^2.0.0" - pify "^4.0.1" - rimraf "^2.6.3" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -depd@^1.1.2, depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== -depd@~2.0.0: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -dependency-graph@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.7.2.tgz#91db9de6eb72699209d88aea4c1fd5221cac1c49" - integrity sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ== +depd@^1.1.2, depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +dependency-graph@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" + integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== -deprecation@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + integrity sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg== detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +dev-ip@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0" + integrity sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A== -devtools-protocol@0.0.854822: - version "0.0.854822" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.854822.tgz#eac3a5260a6b3b4e729a09fdc0c77b0d322e777b" - integrity sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg== +devtools-protocol@0.0.1045489: + version "0.0.1045489" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz#f959ad560b05acd72d55644bc3fb8168a83abf28" + integrity sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ== dezalgo@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== dependencies: asap "^2.0.0" wrappy "1" @@ -4617,18 +4630,18 @@ dezalgo@^1.0.0: di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= - -diff@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -4636,79 +4649,72 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== +dns-packet@^5.2.2: + version "5.4.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" + integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" + "@leichtgewicht/ip-codec" "^2.0.1" -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: - buffer-indexof "^1.0.0" + esutils "^2.0.2" dom-serialize@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs= + integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== dependencies: custom-event "~1.0.0" ent "~2.2.0" extend "^3.0.0" void-elements "^2.0.0" -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - dom-serializer@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be" - integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q== + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== dependencies: domelementtype "^2.0.1" - domhandler "^4.0.0" + domhandler "^4.2.0" entities "^2.0.0" -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" - integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== - -domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domexception@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" - integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== dependencies: - webidl-conversions "^4.0.2" + webidl-conversions "^5.0.0" -domhandler@^4.0.0, domhandler@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43" - integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ== +domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== dependencies: domelementtype "^2.2.0" @@ -4717,51 +4723,45 @@ domino@^2.1.2: resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe" integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ== -dompurify@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.6.tgz#54945dc5c0b45ce5ae228705777e8e59d7b2edc4" - integrity sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ== - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" +dompurify@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.0.tgz#c9c88390f024c2823332615c9e20a453cf3825dd" + integrity sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA== -domutils@^2.4.3: - version "2.5.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.2.tgz#37ef8ba087dff1a17175e7092e8a042e4b050e6c" - integrity sha512-MHTthCb1zj8f1GVfRpeZUbohQf/HdBos0oX5gZcQFepOZPLLRyj6Wn7XS7EMnY7CVpwv8863u2vyE83Hfu28HQ== +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== dependencies: dom-serializer "^1.0.1" domelementtype "^2.2.0" - domhandler "^4.1.0" - -dot-prop@^5.1.0, dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" + domhandler "^4.2.0" duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== dependencies: readable-stream "^2.0.2" -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +easy-extender@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/easy-extender/-/easy-extender-2.3.4.tgz#298789b64f9aaba62169c77a2b3b64b4c9589b8f" + integrity sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q== + dependencies: + lodash "^4.17.10" + +eazy-logger@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eazy-logger/-/eazy-logger-3.1.0.tgz#b169eb56df714608fa114f164c8a2956bec9f0f3" + integrity sha512-/snsn2JqBtUSSstEl4R0RKjkisGHAhvYj89i7r3ytNUKW12y178KDZwXLXIgwDqLW6E/VRMT9qfld7wvFae8bQ== + dependencies: + tfunk "^4.0.0" ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== dependencies: jsbn "~0.1.0" safer-buffer "^2.1.0" @@ -4776,46 +4776,29 @@ ecdsa-sig-formatter@1.0.11: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -ejs@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== - dependencies: - jake "^10.6.1" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.3.649: - version "1.3.665" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.665.tgz#6d0937376f6a919c0f289202c4be77790a6175e5" - integrity sha512-LIjx1JheOz7LM8DMEQ2tPnbBzJ4nVG1MKutsbEMLnJfwfVdPIsyagqfLp56bOWhdBrYGXWHaTayYkllIU2TauA== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -encodeurl@~1.0.2: +encodeurl@~1.0.1, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -encoding@^0.1.11, encoding@^0.1.12: +encoding@^0.1.11, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== @@ -4829,30 +4812,42 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -engine.io-parser@~4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e" - integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg== +engine.io-client@~6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.3.tgz#a8cbdab003162529db85e9de31575097f6d29458" + integrity sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw== dependencies: - base64-arraybuffer "0.1.4" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.0.3" + ws "~8.2.3" + xmlhttprequest-ssl "~2.0.0" -engine.io@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.1.tgz#9a8f8a5ac5a5ea316183c489bf7f5b6cf91ace5b" - integrity sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w== +engine.io-parser@~5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0" + integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== + +engine.io@~6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0" + integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg== dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" accepts "~1.3.4" base64id "2.0.0" cookie "~0.4.1" cors "~2.8.5" debug "~4.3.1" - engine.io-parser "~4.0.0" - ws "~7.4.2" + engine.io-parser "~5.0.3" + ws "~8.2.3" -enhanced-resolve@5.7.0, enhanced-resolve@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" - integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== +enhanced-resolve@^5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" + integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4860,83 +4855,88 @@ enhanced-resolve@5.7.0, enhanced-resolve@^5.7.0: ent@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= + integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" + integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== + env-paths@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" - integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -envinfo@7.7.4: - version "7.7.4" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.4.tgz#c6311cdd38a0e86808c1c9343f667e4267c4a320" - integrity sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ== +envinfo@7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== err-code@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== -errno@^0.1.1, errno@^0.1.3: +errno@^0.1.1: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.2: - version "1.17.7" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: + version "1.20.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.4.tgz#1d103f9f8d78d4cf0713edcd6d0ed1a46eed5861" + integrity sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.3" + get-symbol-description "^1.0.0" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.2" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.1: - version "1.18.0-next.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.2.tgz#088101a55f0541f595e7e057199e27ddc8f3a5c2" - integrity sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw== + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.0.2" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.1" - is-regex "^1.1.1" - object-inspect "^1.9.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.3" - string.prototype.trimstart "^1.0.3" - -es-module-lexer@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e" - integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA== es-to-primitive@^1.2.1: version "1.2.1" @@ -4948,18 +4948,18 @@ es-to-primitive@^1.2.1: is-symbol "^1.0.2" es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" -es6-iterator@2.0.3, es6-iterator@^2.0.3, es6-iterator@~2.0.3: +es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== dependencies: d "1" es5-ext "^0.10.35" @@ -4973,11 +4973,11 @@ es6-promise@^4.0.3: es6-promisify@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== dependencies: es6-promise "^4.0.3" -es6-symbol@^3.1.1, es6-symbol@~3.1.3: +es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== @@ -4995,6 +4995,272 @@ es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" +esbuild-android-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.12.tgz#5e8151d5f0a748c71a7fbea8cee844ccf008e6fc" + integrity sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q== + +esbuild-android-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz#5f25864055dbd62e250f360b38b4c382224063af" + integrity sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g== + +esbuild-android-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.12.tgz#5ee72a6baa444bc96ffcb472a3ba4aba2cc80666" + integrity sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA== + +esbuild-android-arm64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz#d8820f999314efbe8e0f050653a99ff2da632b0f" + integrity sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w== + +esbuild-darwin-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.12.tgz#70047007e093fa1b3ba7ef86f9b3fa63db51fe25" + integrity sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q== + +esbuild-darwin-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz#99ae7fdaa43947b06cd9d1a1c3c2c9f245d81fd0" + integrity sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg== + +esbuild-darwin-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.12.tgz#41c951f23d9a70539bcca552bae6e5196696ae04" + integrity sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw== + +esbuild-darwin-arm64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz#bafa1814354ad1a47adcad73de416130ef7f55e3" + integrity sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A== + +esbuild-freebsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.12.tgz#a761b5afd12bbedb7d56c612e9cfa4d2711f33f0" + integrity sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw== + +esbuild-freebsd-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz#84ef85535c5cc38b627d1c5115623b088d1de161" + integrity sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA== + +esbuild-freebsd-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.12.tgz#6b0839d4d58deabc6cbd96276eb8cbf94f7f335e" + integrity sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g== + +esbuild-freebsd-arm64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz#033f21de434ec8e0c478054b119af8056763c2d8" + integrity sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q== + +esbuild-linux-32@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.12.tgz#bd50bfe22514d434d97d5150977496e2631345b4" + integrity sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA== + +esbuild-linux-32@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz#54290ea8035cba0faf1791ce9ae6693005512535" + integrity sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w== + +esbuild-linux-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.12.tgz#074bb2b194bf658245f8490f29c01ffcdfa8c931" + integrity sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA== + +esbuild-linux-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz#4264249281ea388ead948614b57fb1ddf7779a2c" + integrity sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A== + +esbuild-linux-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.12.tgz#3bf789c4396dc032875a122988efd6f3733f28f5" + integrity sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ== + +esbuild-linux-arm64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz#9323c333924f97a02bdd2ae8912b36298acb312d" + integrity sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ== + +esbuild-linux-arm@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.12.tgz#b91b5a8d470053f6c2c9c8a5e67ec10a71fe4a67" + integrity sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A== + +esbuild-linux-arm@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz#b407f47b3ae721fe4e00e19e9f19289bef87a111" + integrity sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ== + +esbuild-linux-mips64le@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.12.tgz#2fb54099ada3c950a7536dfcba46172c61e580e2" + integrity sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A== + +esbuild-linux-mips64le@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz#bdf905aae5c0bcaa8f83567fe4c4c1bdc1f14447" + integrity sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A== + +esbuild-linux-ppc64le@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.12.tgz#9e3b8c09825fb27886249dfb3142a750df29a1b7" + integrity sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg== + +esbuild-linux-ppc64le@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz#2911eae1c90ff58a3bd3259cb557235df25aa3b4" + integrity sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA== + +esbuild-linux-riscv64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.12.tgz#923d0f5b6e12ee0d1fe116b08e4ae4478fe40693" + integrity sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA== + +esbuild-linux-riscv64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz#1837c660be12b1d20d2a29c7189ea703f93e9265" + integrity sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow== + +esbuild-linux-s390x@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.12.tgz#3b1620220482b96266a0c6d9d471d451a1eab86f" + integrity sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww== + +esbuild-linux-s390x@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz#d52880ece229d1bd10b2d936b792914ffb07c7fc" + integrity sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag== + +esbuild-netbsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.12.tgz#276730f80da646859b1af5a740e7802d8cd73e42" + integrity sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w== + +esbuild-netbsd-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz#de14da46f1d20352b43e15d97a80a8788275e6ed" + integrity sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ== + +esbuild-openbsd-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.12.tgz#bd0eea1dd2ca0722ed489d88c26714034429f8ae" + integrity sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw== + +esbuild-openbsd-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz#45e8a5fd74d92ad8f732c43582369c7990f5a0ac" + integrity sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w== + +esbuild-sunos-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.12.tgz#5e56bf9eef3b2d92360d6d29dcde7722acbecc9e" + integrity sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg== + +esbuild-sunos-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz#f646ac3da7aac521ee0fdbc192750c87da697806" + integrity sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw== + +esbuild-wasm@0.15.12, esbuild-wasm@^0.15.9: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.15.12.tgz#66fa2f18459f59f0da74876dacfdf71dc6f06e4b" + integrity sha512-mm6ouQxg27wHHIBc7ii8x4v2eu67O14wfa7OM0gfwmrHVzuQCLd6hATK/tvR73Pif/WS+/OvhcV4hY0REfHeUw== + +esbuild-wasm@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.15.13.tgz#c4d7f771679b2e14f7a456403751d908db1c07d1" + integrity sha512-0am8fvHKACwofWQxtZLTMv4mDiDwUrdt0DyRaQ2r7YWIpkmpg4GWYy0EyW+gPjiPHzkZKqN9d3UYsZGgvaAASw== + +esbuild-windows-32@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.12.tgz#a4f1a301c1a2fa7701fcd4b91ef9d2620cf293d0" + integrity sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw== + +esbuild-windows-32@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz#fb4fe77c7591418880b3c9b5900adc4c094f2401" + integrity sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA== + +esbuild-windows-64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.12.tgz#bc2b467541744d653be4fe64eaa9b0dbbf8e07f6" + integrity sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA== + +esbuild-windows-64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz#1fca8c654392c0c31bdaaed168becfea80e20660" + integrity sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ== + +esbuild-windows-arm64@0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.12.tgz#9a7266404334a86be800957eaee9aef94c3df328" + integrity sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA== + +esbuild-windows-arm64@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz#4ffd01b6b2888603f1584a2fe96b1f6a6f2b3dd8" + integrity sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg== + +esbuild@0.15.12, esbuild@^0.15.9: + version "0.15.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.12.tgz#6c8e22d6d3b7430d165c33848298d3fc9a1f251c" + integrity sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng== + optionalDependencies: + "@esbuild/android-arm" "0.15.12" + "@esbuild/linux-loong64" "0.15.12" + esbuild-android-64 "0.15.12" + esbuild-android-arm64 "0.15.12" + esbuild-darwin-64 "0.15.12" + esbuild-darwin-arm64 "0.15.12" + esbuild-freebsd-64 "0.15.12" + esbuild-freebsd-arm64 "0.15.12" + esbuild-linux-32 "0.15.12" + esbuild-linux-64 "0.15.12" + esbuild-linux-arm "0.15.12" + esbuild-linux-arm64 "0.15.12" + esbuild-linux-mips64le "0.15.12" + esbuild-linux-ppc64le "0.15.12" + esbuild-linux-riscv64 "0.15.12" + esbuild-linux-s390x "0.15.12" + esbuild-netbsd-64 "0.15.12" + esbuild-openbsd-64 "0.15.12" + esbuild-sunos-64 "0.15.12" + esbuild-windows-32 "0.15.12" + esbuild-windows-64 "0.15.12" + esbuild-windows-arm64 "0.15.12" + +esbuild@0.15.13: + version "0.15.13" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.13.tgz#7293480038feb2bafa91d3f6a20edab3ba6c108a" + integrity sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ== + optionalDependencies: + "@esbuild/android-arm" "0.15.13" + "@esbuild/linux-loong64" "0.15.13" + esbuild-android-64 "0.15.13" + esbuild-android-arm64 "0.15.13" + esbuild-darwin-64 "0.15.13" + esbuild-darwin-arm64 "0.15.13" + esbuild-freebsd-64 "0.15.13" + esbuild-freebsd-arm64 "0.15.13" + esbuild-linux-32 "0.15.13" + esbuild-linux-64 "0.15.13" + esbuild-linux-arm "0.15.13" + esbuild-linux-arm64 "0.15.13" + esbuild-linux-mips64le "0.15.13" + esbuild-linux-ppc64le "0.15.13" + esbuild-linux-riscv64 "0.15.13" + esbuild-linux-s390x "0.15.13" + esbuild-netbsd-64 "0.15.13" + esbuild-openbsd-64 "0.15.13" + esbuild-sunos-64 "0.15.13" + esbuild-windows-32 "0.15.13" + esbuild-windows-64 "0.15.13" + esbuild-windows-arm64 "0.15.13" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -5003,12 +5269,17 @@ escalade@^3.1.1: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escodegen@^1.11.1: version "1.14.3" @@ -5022,6 +5293,18 @@ escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escodegen@~1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" @@ -5034,7 +5317,51 @@ escodegen@~1.9.0: optionalDependencies: source-map "~0.6.1" -eslint-scope@^5.1.1: +eslint-config-prettier@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-import-resolver-node@0.3.6, eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" + integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== + dependencies: + debug "^3.2.7" + +eslint-plugin-header@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6" + integrity sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg== + +eslint-plugin-import@2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.3" + has "^1.0.3" + is-core-module "^2.8.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.5" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -5042,15 +5369,101 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -esprima@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@8.27.0: + version "8.27.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.27.0.tgz#d547e2f7239994ad1faa4bb5d84e5d809db7cf64" + integrity sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ== + dependencies: + "@eslint/eslintrc" "^1.3.3" + "@humanwhocodes/config-array" "^0.11.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + 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.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.4.0" + esquery "^1.4.0" + 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.15.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + 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.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg== + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" @@ -5064,10 +5477,10 @@ estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== estree-walker@^1.0.1: version "1.0.1" @@ -5084,124 +5497,102 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: +etag@1.8.1, etag@^1.8.1, etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== event-emitter@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== dependencies: d "1" es5-ext "~0.10.14" +eventemitter-asyncresource@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b" + integrity sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ== + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== - -eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== - dependencies: - original "^1.0.0" + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -express@4.17.1, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +express-rate-limit@5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.5.1.tgz#110c23f6a65dfa96ab468eda95e71697bc6987a2" + integrity sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg== + +express@4.18.2, express@^4.17.3: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" + body-parser "1.20.1" + content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.0" + cookie "0.5.0" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.2" + depd "2.0.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" + finalhandler "1.2.0" fresh "0.5.2" + http-errors "2.0.0" merge-descriptors "1.0.1" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" + proxy-addr "~2.0.7" + qs "6.11.0" range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" + type "^2.7.2" extend@^3.0.0, extend@~3.0.2: version "3.0.2" @@ -5217,21 +5608,7 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extract-zip@^2.0.0: +extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -5245,83 +5622,80 @@ extract-zip@^2.0.0: extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== falafel@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.4.tgz#b5d86c060c2412a43166243cb1bce44d1abd2819" - integrity sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ== + version "2.2.5" + resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.5.tgz#3ccb4970a09b094e9e54fead2deee64b4a589d56" + integrity sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ== dependencies: acorn "^7.1.1" - foreach "^2.0.5" isarray "^2.0.1" - object-keys "^1.0.6" -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== +fast-glob@^3.2.11, fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" -fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-redact@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.0.tgz#ac2f9e36c9f4976f5db9fb18c6ffbaf308cf316d" - integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" + integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== -fast-safe-stringify@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" - integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.8: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fastparse@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" - integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.1.tgz#8b8f2ac8bf3632d67afcd65dac248d5fdc45385e" - integrity sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA== + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== dependencies: reusify "^1.0.4" faye-websocket@^0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== dependencies: websocket-driver ">=0.5.1" fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== dependencies: pend "~1.2.0" @@ -5332,27 +5706,12 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -filelist@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" - integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== - dependencies: - minimatch "^3.0.4" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" @@ -5361,7 +5720,20 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.1.2, finalhandler@~1.1.2: +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + integrity sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw== + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + +finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== @@ -5374,42 +5746,28 @@ finalhandler@1.1.2, finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-cache-dir@3.3.1, find-cache-dir@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== dependencies: commondir "^1.0.1" make-dir "^3.0.2" pkg-dir "^4.1.0" -find-parent-dir@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" - integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ= - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -find-up@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -5426,40 +5784,28 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + flatstr@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== -flatted@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - -flatten@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== - -follow-redirects@^1.0.0: - version "1.13.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" - integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== - -font-awesome@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" - integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +flatted@^3.1.0, flatted@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= +follow-redirects@^1.0.0, follow-redirects@^1.14.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== foreground-child@^2.0.0: version "2.0.0" @@ -5472,7 +5818,7 @@ foreground-child@^2.0.0: forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== form-data@^3.0.0: version "3.0.1" @@ -5492,33 +5838,35 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - -fraction.js@^4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" - integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== -fresh@0.5.2: +fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + integrity sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -5528,6 +5876,15 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@~7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -5535,25 +5892,17 @@ fs-minipass@^2.0.0, fs-minipass@^2.1.0: dependencies: minipass "^3.0.0" -fs-monkey@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.1.tgz#4a82f36944365e619f4454d9fff106553067b781" - integrity sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA== +fs-monkey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.1: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -5563,6 +5912,21 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + furi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/furi/-/furi-2.0.0.tgz#13d85826a1af21acc691da6254b3888fc39f0b4a" @@ -5571,21 +5935,21 @@ furi@^2.0.0: "@types/is-windows" "^1.0.0" is-windows "^1.0.2" -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== @@ -5595,155 +5959,97 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== dependencies: function-bind "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - -get-pkg-repo@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" - integrity sha1-xztInAbYDMVTbCyFP54FIyBWly0= - dependencies: - hosted-git-info "^2.1.4" - meow "^3.3.0" - normalize-package-data "^2.3.0" - parse-github-repo-url "^1.3.0" - through2 "^2.0.0" - -get-port@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" - integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + has-symbols "^1.0.3" -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== dependencies: assert-plus "^1.0.0" -gh-got@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-9.0.0.tgz#5f82eb5c97aa7a0235f50cf277331cdeda879670" - integrity sha512-RH5n6CDdb6AozElmiKwFhmO/1FmhWWVhfQAVv+JtU8jtPK12JLErce/VQFsFwZ9dTa01SfD7WXb/1iyZp/5XKg== - dependencies: - got "^10.5.7" - -git-raw-commits@^2.0.0, git-raw-commits@^2.0.10, git-raw-commits@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" - integrity sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ== - dependencies: - dargs "^7.0.0" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" - -git-remote-origin-url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" - integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8= - dependencies: - gitconfiglocal "^1.0.0" - pify "^2.3.0" - -git-semver-tags@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" - integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== - dependencies: - meow "^8.0.0" - semver "^6.0.0" - -gitconfiglocal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" - integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s= - dependencies: - ini "^1.3.2" - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" + is-glob "^4.0.1" -glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +glob-parent@^6.0.1, glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: - is-glob "^4.0.1" + is-glob "^4.0.3" glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== +glob@8.0.3, glob@^8.0.1, glob@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^5.0.1" once "^1.3.0" - path-is-absolute "^1.0.0" -glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A== dependencies: - fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "2 || 3" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^6.0.1: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= +glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: + fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "2 || 3" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -5752,22 +6058,40 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globby@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" - integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== +globals@^13.15.0: + version "13.17.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" +globby@^13.1.1: + version "13.1.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.2.tgz#29047105582427ab6eca4f905200667b056da515" + integrity sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" - integrity sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0= + integrity sha512-HJRTIH2EeH44ka+LWig+EqT2ONSYpVlNfx6pyd592/VF1TbfljJ7elwie7oSwcViLGqOdWocSdu2txwBF9bjmQ== dependencies: array-union "^1.0.1" arrify "^1.0.0" @@ -5776,49 +6100,27 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" +google-protobuf@^3.6.1: + version "3.21.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4" + integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA== -got@^10.5.7: - version "10.7.0" - resolved "https://registry.yarnpkg.com/got/-/got-10.7.0.tgz#62889dbcd6cca32cd6a154cc2d0c6895121d091f" - integrity sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg== - dependencies: - "@sindresorhus/is" "^2.0.0" - "@szmarczak/http-timer" "^4.0.0" - "@types/cacheable-request" "^6.0.1" - cacheable-lookup "^2.0.0" - cacheable-request "^7.0.1" - decompress-response "^5.0.0" - duplexer3 "^0.1.4" - get-stream "^5.0.0" - lowercase-keys "^2.0.0" - mimic-response "^2.1.0" - p-cancelable "^2.0.0" - p-event "^4.0.0" - responselike "^2.0.0" - to-readable-stream "^2.0.0" - type-fest "^0.10.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3, graceful-fs@^4.2.4: - version "4.2.6" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" - integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== handle-thing@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -handlebars@4.7.7, handlebars@^4.7.6: +handlebars@4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== @@ -5833,7 +6135,7 @@ handlebars@4.7.7, handlebars@^4.7.6: har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== har-validator@~5.1.0, har-validator@~5.1.3: version "5.1.5" @@ -5843,143 +6145,120 @@ har-validator@~5.1.0, har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== dependencies: ansi-regex "^2.0.0" +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: +has-property-descriptors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" + get-intrinsic "^1.1.1" -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-values@^1.0.0: +has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + has-symbols "^1.0.2" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has@^1.0.0, has@^1.0.1, has@^1.0.3: +has@^1.0.1, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +hdr-histogram-js@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz#0b860534655722b6e3f3e7dca7b78867cf43dcb5" + integrity sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g== + dependencies: + "@assemblyscript/loader" "^0.10.1" + base64-js "^1.2.0" + pako "^1.0.3" -hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== +hdr-histogram-percentiles-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz#9409f4de0c2dda78e61de2d9d78b1e9f3cba283c" + integrity sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw== -hosted-git-info@^3.0.6: - version "3.0.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" - integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^5.0.0, hosted-git-info@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-5.1.0.tgz#9786123f92ef3627f24abc3f15c20d98ec4a6594" + integrity sha512-Ek+QmMEqZF8XrbFdwoDjSbm7rT23pCgEMOJmz6GPk/s4yH//RQfNPArhIxbguNxROq/+5lNBwCDHMhA903Kx1Q== dependencies: - lru-cache "^6.0.0" + lru-cache "^7.5.1" -hosted-git-info@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.1.tgz#710ef5452ea429a844abc33c981056e7371edab7" - integrity sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg== +hosted-git-info@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-6.0.0.tgz#2e9b726a8ac0d68a907c6a8dc4abecac5e0ed69a" + integrity sha512-NURrKJX36ihI69iCqcvN4uuIk9fHcc1C+uax/5fPh4Tr5WJnATir+QM/CMJNKrcOOvxQDsAdS5C9oJliM80X7g== dependencies: - lru-cache "^6.0.0" + lru-cache "^7.5.1" hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== dependencies: inherits "^2.0.1" obuf "^1.0.0" readable-stream "^2.0.1" wbuf "^1.1.0" -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - -html-encoding-sniffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" - integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== dependencies: - whatwg-encoding "^1.0.1" + whatwg-encoding "^1.0.5" -html-entities@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" - integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== +html-entities@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" + integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: +http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -5987,55 +6266,33 @@ http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" - integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== - dependencies: - depd "~1.1.2" + depd "2.0.0" inherits "2.0.4" setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" + statuses "2.0.1" + toidentifier "1.0.1" http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== dependencies: depd "~1.1.2" inherits "2.0.3" setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-parser-js@>=0.5.1: - version "0.5.3" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" - integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== http-proxy-agent@^4.0.1: version "4.0.1" @@ -6046,17 +6303,27 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" + "@tootallnate/once" "2" + agent-base "6" + debug "4" -http-proxy@^1.17.0, http-proxy@^1.18.1: +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== @@ -6068,21 +6335,21 @@ http-proxy@^1.17.0, http-proxy@^1.18.1: http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== dependencies: assert-plus "^1.0.0" jsprim "^1.2.2" sshpk "^1.7.0" -http-status-codes@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477" - integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ== +http-status-codes@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" + integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== -https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== +https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" @@ -6095,29 +6362,34 @@ https-proxy-agent@^2.2.1: agent-base "^4.3.0" debug "^3.1.0" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== dependencies: ms "^2.0.0" -husky@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e" - integrity sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ== +husky@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.2.tgz#5816a60db02650f1f22c8b69b928fd6bcd77a236" + integrity sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg== -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" - integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== +iconv-lite@^0.6.2, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" @@ -6131,42 +6403,46 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-walk@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== dependencies: - minimatch "^3.0.4" + minimatch "^5.0.1" + +ignore-walk@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.0.tgz#1dd41c6eb4f661a49750a510a10c2cd934583fd8" + integrity sha512-bTf9UWe/UP1yxG3QUrj/KOvEhTAUWPcv+WvbFZ28LcqznXabp7Xu6o9y1JEC18+oqODuS7VhTpekV5XvFwsxJg== + dependencies: + minimatch "^5.0.1" -ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" - integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immutable@^3.8.2: +immutable@^3: version "3.8.2" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" - integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM= + integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" +immutable@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" + integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== -import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -6174,36 +6450,21 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" +import-lazy@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -6212,7 +6473,7 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -6225,18 +6486,31 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +ini@3.0.1, ini@^3.0.0, ini@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ini/-/ini-3.0.1.tgz#c76ec81007875bc44d544ff7a11a55d12294102d" + integrity sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ== -ini@^1.3.2, ini@^1.3.4: +ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +init-package-json@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-3.0.2.tgz#f5bc9bac93f2bdc005778bc2271be642fecfcd69" + integrity sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A== + dependencies: + npm-package-arg "^9.0.1" + promzard "^0.3.0" + read "^1.0.7" + read-package-json "^5.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^4.0.0" + injection-js@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/injection-js/-/injection-js-2.4.0.tgz#ebe8871b1a349f23294eaa751bbd8209a636e754" @@ -6244,100 +6518,72 @@ injection-js@^2.4.0: dependencies: tslib "^2.0.0" -inquirer@8.0.0, inquirer@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.0.0.tgz#957a46db1abcf0fdd2ab82deb7470e90afc7d0ac" - integrity sha512-ON8pEJPPCdyjxj+cxsYRe6XfCJepTxANdNnTebsTuQgXpRyZRRT9t4dJwjRubgmvn20CLSEnozRUayXyM9VTXA== +inquirer@8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" + integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== dependencies: ansi-escapes "^4.2.1" - chalk "^4.1.0" + chalk "^4.1.1" cli-cursor "^3.1.0" cli-width "^3.0.0" external-editor "^3.0.3" figures "^3.0.0" lodash "^4.17.21" mute-stream "0.0.8" + ora "^5.4.1" run-async "^2.4.0" - rxjs "^6.6.6" + rxjs "^7.5.5" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" + wrap-ansi "^7.0.0" -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= +ip-regex@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== -ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== -ipaddr.js@1.9.1, ipaddr.js@^1.9.0: +ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arguments@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" - integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== - dependencies: - call-bind "^1.0.0" +ipaddr.js@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" + integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: - binary-extensions "^1.0.0" + has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" @@ -6346,132 +6592,66 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== - -is-color-stop@^1.0.0, is-color-stop@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" -is-core-module@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" - integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== +is-builtin-module@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0" + integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw== dependencies: - has "^1.0.3" + builtin-modules "^3.3.0" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" +is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== +is-cidr@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" + integrity sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA== dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + cidr-regex "^3.1.1" -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== +is-core-module@^2.1.0, is-core-module@^2.8.1, is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" + has "^1.0.3" -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + has-tostringtag "^1.0.0" is-docker@^2.0.0, is-docker@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" - integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" @@ -6483,44 +6663,41 @@ is-interactive@^1.0.0: is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" - integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= +is-number-like@^1.0.3: + version "1.0.8" + resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" + integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA== + dependencies: + lodash.isfinite "^3.3.2" + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== dependencies: - kind-of "^3.0.2" + has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= - -is-path-cwd@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + integrity sha512-cnS56eR9SPAscL77ik76ATVqoPARTqPIVkMDVxRaWH06zT+6+CzIroYRJ0VVvm0Z1zfAvxvz9i/D3Ppjaqt5Nw== is-path-in-cwd@^1.0.0: version "1.0.1" @@ -6529,99 +6706,83 @@ is-path-in-cwd@^1.0.0: dependencies: is-path-inside "^1.0.0" -is-path-in-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" - integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== - dependencies: - is-path-inside "^2.1.0" - is-path-inside@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= + integrity sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g== dependencies: path-is-inside "^1.0.1" -is-path-inside@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" - integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== - dependencies: - path-is-inside "^1.0.2" +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== is-promise@^2.1.0, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-reference@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" - integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: - "@types/estree" "*" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" -is-regex@^1.0.4, is-regex@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" - integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.1" - -is-resolvable@^1.0.0, is-resolvable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== - dependencies: - html-comment-regex "^1.1.0" +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: - has-symbols "^1.0.1" + has-tostringtag "^1.0.0" -is-text-path@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" - integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - text-extensions "^1.0.0" + has-symbols "^1.0.2" is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== is-unicode-supported@^0.1.0: version "0.1.0" @@ -6633,15 +6794,17 @@ is-url@^1.2.4: resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" -is-what@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.12.0.tgz#f4405ce4bd6dd420d3ced51a026fb90e03705e55" - integrity sha512-2ilQz5/f/o9V7WRWJQmpFYNmQFZ9iM+OXRonZKcYgTkCzjb949Vi4h282PD1UfmgHk666rcWonbRJ++KI41VGw== +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== is-windows@^1.0.2: version "1.0.2" @@ -6651,7 +6814,7 @@ is-windows@^1.0.2: is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== is-wsl@^2.2.0: version "2.2.0" @@ -6660,42 +6823,35 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isarray@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== -isbinaryfile@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" - integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isbinaryfile@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" + integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^3.0.0, isobject@^3.0.1: +isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + integrity sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA== dependencies: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" @@ -6703,21 +6859,22 @@ isomorphic-fetch@^2.2.1: isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== -istanbul-lib-coverage@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" - integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.1, istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: - "@babel/core" "^7.7.5" + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" + istanbul-lib-coverage "^3.2.0" semver "^6.3.0" istanbul-lib-report@^3.0.0: @@ -6729,55 +6886,45 @@ istanbul-lib-report@^3.0.0: make-dir "^3.0.0" supports-color "^7.1.0" -istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== +istanbul-lib-source-maps@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.0, istanbul-reports@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" - integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== +istanbul-reports@^3.0.2, istanbul-reports@^3.0.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jake@^10.6.1: - version "10.8.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" - integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== - dependencies: - async "0.9.x" - chalk "^2.4.2" - filelist "^1.0.1" - minimatch "^3.0.4" - -jasmine-core@^3.6.0, jasmine-core@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20" - integrity sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw== +jasmine-core@^4.1.0, jasmine-core@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-4.4.0.tgz#848fe45c1839cacaf1f2429d400d1d4f85d2856a" + integrity sha512-+l482uImx5BVd6brJYlaHe2UwfKoZBqQfNp20ZmdNfsjGFTemGfqHLsXjKEW23w9R/m8WYeFc9JmIgjj6dUtAA== jasmine-core@~2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" - integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4= + integrity sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ== -jasmine-core@~3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.7.0.tgz#33da871fef5595e9aa43d0f50e009b5fe35f7ef2" - integrity sha512-jmeRIgxS/abz8afk9NKJ1yP7n93aZdRaHDWtTHJBlYu6fhbzvErjiO3mMlSSrJefVk1a+26JlABrHS2iBH8Azw== +jasmine-core@~4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-4.5.0.tgz#1a6bd0bde3f60996164311c88a0995d67ceda7c3" + integrity sha512-9PMzyvhtocxb3aXJVOPqBDswdgyAeSB81QnLop4npOpbqnheaTEwPc9ZloQeVswugPManznQBjD8kWDTjlnHuw== -jasmine-reporters@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz#898818ffc234eb8b3f635d693de4586f95548d43" - integrity sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A== +jasmine-reporters@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.5.0.tgz#f9e2e0f82aaa2e07e8d553be56457efe0fd8b39e" + integrity sha512-J69peyTR8j6SzvIPP6aO1Y00wwCqXuIvhwTYvE/di14roCf6X3wDZ4/cKGZ2fGgufjhP2FKjpgrUIKjwau4e/Q== dependencies: - mkdirp "^0.5.1" - xmldom "^0.1.22" + "@xmldom/xmldom" "^0.7.3" + mkdirp "^1.0.4" jasmine-spec-reporter@~7.0.0: version "7.0.0" @@ -6789,53 +6936,63 @@ jasmine-spec-reporter@~7.0.0: jasmine@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e" - integrity sha1-awicChFXax8W3xG4AUbZHU6Lij4= + integrity sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw== dependencies: exit "^0.1.2" glob "^7.0.6" jasmine-core "~2.8.0" -jasmine@^3.3.1: - version "3.6.4" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.6.4.tgz#1f8e4a0d5028a2dc66942b73b9fef4c32be97ad5" - integrity sha512-hIeOou6y0BgCOKYgXYveQvlY+PTHgDPajFf+vLCYbMTQ+VjAP9+EQv0nuC9+gyCAAWISRFauB1XUb9kFuOKtcQ== +jasmine@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-4.4.0.tgz#e3e359723d9679993b70de3e0441517c154b9647" + integrity sha512-xrbOyYkkCvgduNw7CKktDtNb+BwwBv/zvQeHpTkbxqQ37AJL5V4sY3jHoMIJPP/hTc3QxLVwOyxc87AqA+kw5g== dependencies: glob "^7.1.6" - jasmine-core "~3.6.0" + jasmine-core "^4.4.0" jasminewd2@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.2.0.tgz#e37cf0b17f199cce23bea71b2039395246b4ec4e" - integrity sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4= + integrity sha512-Rn0nZe4rfDhzA63Al3ZGh0E+JTmM6ESZYXJGKuqKGZObsAB9fwXPD03GjtIEvJBDOhN94T5MzbwZSqzFHSQPzg== -jest-worker@26.6.2, jest-worker@^26.5.0, jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.0.0" + +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== jquery@^3.3.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" - integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== + version "3.6.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16" + integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw== js-base64@^2.4.3: version "2.6.4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== +js-sdsl@^4.1.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a" + integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" - integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -6850,38 +7007,39 @@ js-yaml@^3.13.1: jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsdom@15.2.1: - version "15.2.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" - integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== - dependencies: - abab "^2.0.0" - acorn "^7.1.0" - acorn-globals "^4.3.2" - array-equal "^1.0.0" - cssom "^0.4.1" - cssstyle "^2.0.0" - data-urls "^1.1.0" - domexception "^1.0.1" - escodegen "^1.11.1" - html-encoding-sniffer "^1.0.2" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +jsdom@16.7.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + 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 "5.1.0" - pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.7" - saxes "^3.1.9" - symbol-tree "^3.2.2" - tough-cookie "^3.0.1" - w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" - webidl-conversions "^4.0.2" + 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 "^7.0.0" - ws "^7.0.0" + whatwg-url "^8.5.0" + ws "^7.4.6" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -6892,23 +7050,18 @@ jsesc@^2.5.1: jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== -json-parse-even-better-errors@^2.3.0: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-parse-even-better-errors@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz#2cb2ee33069a78870a0c7e3da560026b89669cf7" + integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -6919,20 +7072,25 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^1.0.1: version "1.0.1" @@ -6941,29 +7099,34 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.0, json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +json5@^2.1.2, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== -jsonc-parser@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" - integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== +jsonc-parser@3.2.0, jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + integrity sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w== + optionalDependencies: + graceful-fs "^4.1.6" jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== jsonwebtoken@8.5.1: version "8.5.1" @@ -6982,24 +7145,34 @@ jsonwebtoken@8.5.1: semver "^5.6.0" jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" -jszip@^3.1.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" - integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== +jszip@^3.1.3, jszip@^3.10.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== dependencies: lie "~3.3.0" pako "~1.0.2" readable-stream "~2.3.6" - set-immediate-shim "~1.0.1" + setimmediate "^1.0.5" + +just-diff-apply@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.4.1.tgz#1debed059ad009863b4db0e8d8f333d743cdd83b" + integrity sha512-AAV5Jw7tsniWwih8Ly3fXxEZ06y+6p5TwQMsw0dzZ/wPKilzyDgdAnL0Ug4NNIquPUOh1vfFWEHbmXUqM5+o8g== + +just-diff@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202" + integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ== jwa@^1.4.1: version "1.4.1" @@ -7019,35 +7192,35 @@ jws@^3.2.2: safe-buffer "^5.0.1" karma-chrome-launcher@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738" - integrity sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg== + version "3.1.1" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz#baca9cc071b1562a1db241827257bfe5cab597ea" + integrity sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ== dependencies: which "^1.2.1" -karma-coverage@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.0.3.tgz#c10f4711f4cf5caaaa668b1d6f642e7da122d973" - integrity sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g== +karma-coverage@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.0.tgz#64f838b66b71327802e7f6f6c39d569b7024e40c" + integrity sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA== dependencies: - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.1" + istanbul-lib-coverage "^3.2.0" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.0" + istanbul-lib-source-maps "^4.0.1" + istanbul-reports "^3.0.5" minimatch "^3.0.4" -karma-jasmine-html-reporter@^1.5.0: - version "1.5.4" - resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.4.tgz#669f33d694d88fce1b0ccfda57111de716cb0192" - integrity sha512-PtilRLno5O6wH3lDihRnz0Ba8oSn0YUJqKjjux1peoYGwo0AQqrWRbdWk/RLzcGlb+onTyXAnHl6M+Hu3UxG/Q== +karma-jasmine-html-reporter@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.0.0.tgz#76c26ce40e217dc36a630fbcd7b31c3462948bf2" + integrity sha512-SB8HNNiazAHXM1vGEzf8/tSyEhkfxuDdhYdPBX2Mwgzt0OuF2gicApQ+uvXLID/gXyJQgvrM9+1/2SxZFUUDIA== -karma-jasmine@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-4.0.1.tgz#b99e073b6d99a5196fc4bffc121b89313b0abd82" - integrity sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw== +karma-jasmine@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-5.1.0.tgz#3af4558a6502fa16856a0f346ec2193d4b884b2f" + integrity sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ== dependencies: - jasmine-core "^3.6.0" + jasmine-core "^4.1.0" karma-source-map-support@1.4.0: version "1.4.0" @@ -7056,33 +7229,34 @@ karma-source-map-support@1.4.0: dependencies: source-map-support "^0.5.5" -karma@~6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.0.tgz#8291d1ba329a1f57c160b0bd256fb42ae16bfd8b" - integrity sha512-Or+x8T4/TWe7ChBaAkdQARjETSbvxsipp5Y+0UMSu4z0YYzy06OA2UdHAmMIx5gLs4a76X916pY1bzGgYuN7BQ== +karma@~6.4.0: + version "6.4.1" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.1.tgz#f2253716dd3a41aaa813fa9f54b6ee047e1127d9" + integrity sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA== dependencies: + "@colors/colors" "1.5.0" body-parser "^1.19.0" braces "^3.0.2" - chokidar "^3.4.2" - colors "^1.4.0" + chokidar "^3.5.1" connect "^3.7.0" di "^0.0.1" dom-serialize "^2.2.1" - glob "^7.1.6" - graceful-fs "^4.2.4" + glob "^7.1.7" + graceful-fs "^4.2.6" http-proxy "^1.18.1" - isbinaryfile "^4.0.6" - lodash "^4.17.19" - log4js "^6.2.1" - mime "^2.4.5" + isbinaryfile "^4.0.8" + lodash "^4.17.21" + log4js "^6.4.1" + mime "^2.5.2" minimatch "^3.0.4" + mkdirp "^0.5.5" qjobs "^1.2.0" range-parser "^1.2.1" rimraf "^3.0.2" - socket.io "^3.1.0" + socket.io "^4.4.1" source-map "^0.6.1" - tmp "0.2.1" - ua-parser-js "^0.7.23" + tmp "^0.2.1" + ua-parser-js "^0.7.30" yargs "^16.1.1" keygrip@~1.1.0: @@ -7092,84 +7266,174 @@ keygrip@~1.1.0: dependencies: tsscmp "1.0.6" -keyv@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" - integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== - dependencies: - json-buffer "3.0.1" - -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: +kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kleur@4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" - integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== +kleur@4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" + integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== -klona@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" - integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== +klona@^2.0.4, klona@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== -less-loader@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-8.1.0.tgz#8276adc16bf6576dd80b71563685a2cfe93b0a50" - integrity sha512-IE73O5LY5WHA71EDwszM2PIEGDF30xz45GplpRhYuxMXhAvXoMudu/ItjllNR/ht7XLh5N7JegzRg11HYu+xxg== +less-loader@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-11.1.0.tgz#a452384259bdf8e4f6d5fdcc39543609e6313f82" + integrity sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug== dependencies: klona "^2.0.4" -less@4.1.1, less@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/less/-/less-4.1.1.tgz#15bf253a9939791dc690888c3ff424f3e6c7edba" - integrity sha512-w09o8tZFPThBscl5d0Ggp3RcrKIouBoQscnOMgFH3n5V3kN/CXGHNfCkRPtxJk6nKryDXaV9aHLK55RXuH4sAw== +less@4.1.3, less@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/less/-/less-4.1.3.tgz#175be9ddcbf9b250173e0a00b4d6920a5b770246" + integrity sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA== dependencies: copy-anything "^2.0.1" parse-node-version "^1.0.1" - tslib "^1.10.0" + tslib "^2.3.0" optionalDependencies: errno "^0.1.1" graceful-fs "^4.1.2" image-size "~0.5.0" make-dir "^2.1.0" mime "^1.4.1" - needle "^2.5.2" + needle "^3.1.0" source-map "~0.6.0" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" +libnpmaccess@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" + integrity sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag== + dependencies: + aproba "^2.0.0" + minipass "^3.1.1" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + +libnpmdiff@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-4.0.5.tgz#ffaf93fa9440ea759444b8830fdb5c661b09a7c0" + integrity sha512-9fICQIzmH892UwHHPmb+Seup50UIBWcMIK2FdxvlXm9b4kc1nSH0b/BuY1mORJQtB6ydPMnn+BLzOTmd/SKJmw== + dependencies: + "@npmcli/disparity-colors" "^2.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + binary-extensions "^2.2.0" + diff "^5.1.0" + minimatch "^5.0.1" + npm-package-arg "^9.0.1" + pacote "^13.6.1" + tar "^6.1.0" + +libnpmexec@^4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-4.0.13.tgz#6688bd6c02cac31a32d2e56680c3884948cbf453" + integrity sha512-MGi6eD6zqZ1V8VCJenWRc2+rWaFiW/Vkr5Aa/cQAd3duWNvXen9sm101M6ww5ER5PmsT+qX2aZOA3A9ZPfJQXg== + dependencies: + "@npmcli/arborist" "^5.6.2" + "@npmcli/ci-detect" "^2.0.0" + "@npmcli/fs" "^2.1.1" + "@npmcli/run-script" "^4.2.0" + chalk "^4.1.0" + mkdirp-infer-owner "^2.0.0" + npm-package-arg "^9.0.1" + npmlog "^6.0.2" + pacote "^13.6.1" + proc-log "^2.0.0" + read "^1.0.7" + read-package-json-fast "^2.0.2" + semver "^7.3.7" + walk-up-path "^1.0.0" + +libnpmfund@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-3.0.4.tgz#be1fd46bcfa9432660f98d935135d7ee3e620239" + integrity sha512-azKUVFkL27AsvzEzLKMHX/L8j/GE2TL6eZ6KIdc9hsvleoNLT+Y6XO9w9v7JWwg03smZK9dbqwvnYZzO3vzrIA== + dependencies: + "@npmcli/arborist" "^5.6.2" + +libnpmhook@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/libnpmhook/-/libnpmhook-8.0.4.tgz#6c58e5fe763ff5d600ae9c20457ea9a69d1f7d87" + integrity sha512-nuD6e+Nx0OprjEi0wOeqASMl6QIH235th/Du2/8upK3evByFhzIgdfOeP1OhstavW4xtsl0hk5Vw4fAWWuSUgA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^13.0.0" + +libnpmorg@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-4.0.4.tgz#2a01d49372cf0df90d79a61e69bddaf2ed704311" + integrity sha512-1bTpD7iub1rDCsgiBguhJhiDufLQuc8DEti20euqsXz9O0ncXVpCYqf2SMmHR4GEdmAvAj2r7FMiyA9zGdaTpA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^13.0.0" + +libnpmpack@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-4.1.3.tgz#025cfe39829acd8260662bf259e3a9331fc1e4b2" + integrity sha512-rYP4X++ME3ZiFO+2iN3YnXJ4LB4Gsd0z5cgszWJZxaEpDN4lRIXirSyynGNsN/hn4taqnlxD+3DPlFDShvRM8w== + dependencies: + "@npmcli/run-script" "^4.1.3" + npm-package-arg "^9.0.1" + pacote "^13.6.1" + +libnpmpublish@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-6.0.5.tgz#5a894f3de2e267d62f86be2a508e362599b5a4b1" + integrity sha512-LUR08JKSviZiqrYTDfywvtnsnxr+tOvBU0BF8H+9frt7HMvc6Qn6F8Ubm72g5hDTHbq8qupKfDvDAln2TVPvFg== + dependencies: + normalize-package-data "^4.0.0" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + semver "^7.3.7" + ssri "^9.0.0" + +libnpmsearch@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-5.0.4.tgz#b32aa2b23051c00cdcc0912274d0d416e6655d81" + integrity sha512-XHDmsvpN5+pufvGnfLRqpy218gcGGbbbXR6wPrDJyd1em6agKdYByzU5ccskDHH9iVm2UeLydpDsW1ksYuU0cg== + dependencies: + npm-registry-fetch "^13.0.0" + +libnpmteam@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-4.0.4.tgz#ac26068808d93b1051d926457db14e4b3ff669ef" + integrity sha512-rzKSwi6MLzwwevbM/vl+BBQTErgn24tCfgPUdzBlszrw3j5necOu7WnTzgvZMDv6maGUwec6Ut1rxszOgH0l+Q== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^13.0.0" + +libnpmversion@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/libnpmversion/-/libnpmversion-3.0.7.tgz#e4c6c07ee28cf351ce1e2293a5ac9922b09ea94d" + integrity sha512-O0L4eNMUIMQ+effi1HsZPKp2N6wecwqGqB8PvkvmLPWN7EsdabdzAVG48nv0p/OjlbIai5KQg/L+qMMfCA4ZjA== + dependencies: + "@npmcli/git" "^3.0.0" + "@npmcli/run-script" "^4.1.3" + json-parse-even-better-errors "^2.3.1" + proc-log "^2.0.0" + semver "^7.3.7" + license-checker@^25.0.0: version "25.0.1" resolved "https://registry.yarnpkg.com/license-checker/-/license-checker-25.0.1.tgz#4d14504478a5240a857bb3c21cd0491a00d761fa" @@ -7186,13 +7450,12 @@ license-checker@^25.0.0: spdx-satisfies "^4.0.0" treeify "^1.1.0" -license-webpack-plugin@2.3.17: - version "2.3.17" - resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-2.3.17.tgz#75d05d8b2c3f223be8988d144e9739df8f502319" - integrity sha512-4jJ5/oIkhylMw2EjXh9sxPP8KC3FYBjTcxOCoTIaC2J/zVbJhfw992UEpSsov8VTt97XtU+xJyE4cJn4gHB2PA== +license-webpack-plugin@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz#1e18442ed20b754b82f1adeff42249b81d11aec6" + integrity sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw== dependencies: - "@types/webpack-sources" "^0.1.5" - webpack-sources "^1.2.0" + webpack-sources "^3.0.0" lie@~3.3.0: version "3.3.0" @@ -7201,79 +7464,49 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" +limiter@^1.0.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" + integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" +loader-utils@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== -loader-utils@2.0.0, loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" +loader-utils@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" + integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== -loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== +loader-utils@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.3.tgz#d4b15b8504c63d1fc3f2ade52d41bc8459d6ede1" + integrity sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" - json5 "^1.0.1" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" + json5 "^2.1.2" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +localtunnel@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-2.0.2.tgz#528d50087151c4790f89c2db374fe7b0a48501f0" + integrity sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + axios "0.21.4" + debug "4.3.2" + openurl "1.1.1" + yargs "17.1.1" locate-path@^5.0.0: version "5.0.0" @@ -7296,113 +7529,71 @@ lockfile@1.0.4: dependencies: signal-exit "^3.0.2" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isfinite@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" + integrity sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA== lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.ismatch@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" - integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - -lodash.set@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" - integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - -lodash.template@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@4, lodash@4.17.21, lodash@^4.17.21: +lodash@4, lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.21, lodash@^4.7.0, lodash@~4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" - log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -7411,35 +7602,22 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -log4js@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb" - integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw== +log4js@^6.4.1: + version "6.7.0" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.7.0.tgz#fff671a74b2f6e956d135c3c756c79072809a23b" + integrity sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q== dependencies: - date-format "^3.0.0" - debug "^4.1.1" - flatted "^2.0.1" - rfdc "^1.1.4" - streamroller "^2.2.4" - -loglevel@^1.6.8: - version "1.7.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" - integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + date-format "^4.0.14" + debug "^4.3.4" + flatted "^3.2.7" + rfdc "^1.3.0" + streamroller "^3.1.3" long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - lowdb@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" @@ -7451,12 +7629,12 @@ lowdb@1.0.0: pify "^3.0.0" steno "^0.4.1" -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@7.14.0, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: + version "7.14.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f" + integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ== -lru-cache@6.0.0, lru-cache@^6.0.0: +lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== @@ -7466,7 +7644,7 @@ lru-cache@6.0.0, lru-cache@^6.0.0: lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" - integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== dependencies: es5-ext "~0.10.2" @@ -7482,17 +7660,12 @@ lunr-mutable-indexes@2.3.2: resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== -macos-release@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" - integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg== - -magic-string@0.25.7, magic-string@^0.25.0, magic-string@^0.25.7: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== +magic-string@0.26.7, magic-string@^0.26.0: + version "0.26.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f" + integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow== dependencies: - sourcemap-codec "^1.4.4" + sourcemap-codec "^1.4.8" magic-string@^0.22.4: version "0.22.5" @@ -7501,13 +7674,6 @@ magic-string@^0.22.4: dependencies: vlq "^0.2.2" -make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -7516,95 +7682,78 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-fetch-happen@^8.0.9: - version "8.0.14" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" - integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== +make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6, make-fetch-happen@^10.2.0: + version "10.2.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== dependencies: - agentkeepalive "^4.1.3" - cacache "^15.0.5" + agentkeepalive "^4.2.1" + cacache "^16.1.0" http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" + http-proxy-agent "^5.0.0" https-proxy-agent "^5.0.0" is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" + lru-cache "^7.7.1" + minipass "^3.1.6" minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" + minipass-fetch "^2.0.3" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" + negotiator "^0.6.3" promise-retry "^2.0.1" - socks-proxy-agent "^5.0.0" - ssri "^8.0.0" - -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-obj@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" - integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g== + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= +make-fetch-happen@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.0.1.tgz#b3c51663d018d9e11d57fdd4393a4c5a1a7d56eb" + integrity sha512-clv3IblugXn2CDUmqFhNzii3rjKa46u5wNeivc+QlLXkGI5FjLX3rGboo+y2kwf1pd8W0iDiC384cemeDtw9kw== dependencies: - object-visit "^1.0.0" - -marked@2.0.1, marked@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3" - integrity sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw== - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + agentkeepalive "^4.2.1" + cacache "^17.0.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^3.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^10.0.0" -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== +marked@4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.2.tgz#1d2075ad6cdfe42e651ac221c32d949a26c0672a" + integrity sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ== media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -mem@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-8.0.0.tgz#b5e4b6d2d241c6296da05436173b4d0c7ae1f9ac" - integrity sha512-qrcJOe6uD+EW8Wrci1Vdiua/15Xw3n/QnaNXE7varnB6InxSk7nu3/i5jfy3S6kWxr8WYJ6R1o0afMUtvorTsA== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^3.1.0" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memfs@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.0.tgz#f9438e622b5acd1daa8a4ae160c496fdd1325b26" - integrity sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A== +memfs@^3.4.3: + version "3.4.7" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.7.tgz#e5252ad2242a724f938cb937e3c4f7ceb1f70e5a" + integrity sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw== dependencies: - fs-monkey "1.0.1" + fs-monkey "^1.0.3" memoizee@0.4.15: version "0.4.15" @@ -7620,72 +7769,24 @@ memoizee@0.4.15: next-tick "^1.1.0" timers-ext "^0.1.7" -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -meow@^3.3.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -meow@^8.0.0: - version "8.1.2" - resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" - integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-source-map@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" - integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= + integrity sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA== dependencies: source-map "^0.5.6" -merge-source-map@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" - integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== - dependencies: - source-map "^0.6.1" - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -7693,131 +7794,95 @@ merge2@^1.3.0: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -mime-db@1.45.0: - version "1.45.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" - integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + braces "^3.0.2" + picomatch "^2.3.1" -"mime-db@>= 1.43.0 < 2": - version "1.46.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" - integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.28, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.28" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" - integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.45.0" + mime-db "1.52.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@2.4.6: - version "2.4.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== +mime@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mime@2.5.2: +mime@~2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== -mime@^2.4.4, mime@^2.4.5: - version "2.5.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.0.tgz#2b4af934401779806ee98026bb42e8c1ae1876b1" - integrity sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" - integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^2.0.0, mimic-response@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -mini-css-extract-plugin@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.4.1.tgz#975e27c1d0bd8e052972415f47c79cea5ed37548" - integrity sha512-COAGbpAsU0ioFzj+/RRfO5Qv177L1Z/XAx2EmCF33b8GDDqKygMffBTws2lit8iaPdrbKEY5P+zsseBUCREZWQ== +mini-css-extract-plugin@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz#9a1251d15f2035c342d99a468ab9da7a0451b71e" + integrity sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + schema-utils "^4.0.0" minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== +minimatch@5.1.0, minimatch@^5.0.1, minimatch@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" + brace-expansion "^2.0.1" -minimist@1.2.5, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimatch@~3.0.4: + version "3.0.8" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" + integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== minipass-collect@^1.0.2: version "1.0.2" @@ -7826,16 +7891,27 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" -minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.3.3.tgz#34c7cea038c817a8658461bf35174551dce17a0a" - integrity sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ== +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.0.tgz#02481219ddbd3d30eb0e354016f680b10c6f2bcb" + integrity sha512-NSx3k5gR4Q5Ts2poCM/19d45VwhVLBtJZ6ypYcthj2BwmDx/e7lW8Aadnyt3edd2W0ecb+b0o7FYLRYE2AGcQg== dependencies: - minipass "^3.1.0" + minipass "^3.1.6" minipass-sized "^1.0.3" - minizlib "^2.0.0" + minizlib "^2.1.2" optionalDependencies: - encoding "^0.1.12" + encoding "^0.1.13" minipass-flush@^1.0.5: version "1.0.5" @@ -7852,7 +7928,7 @@ minipass-json-stream@^1.0.1: jsonparse "^1.3.1" minipass "^3.0.0" -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: +minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== @@ -7866,14 +7942,21 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: + version "3.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + dependencies: + yallist "^4.0.0" + +minipass@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.5.tgz#6da7e53a48db8a856eeb9153d85b230a2119e819" + integrity sha512-rQ/p+KfKBkeNwo04U15i+hOwoVBVmekmm/HcfTkTN2t9pbQKCMm4eN5gFeqgrrSp/kH/7BYYhTIHOxGqzbBPaA== dependencies: yallist "^4.0.0" -minizlib@^2.0.0, minizlib@^2.1.1: +minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -7881,81 +7964,61 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" +mitt@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d" + integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw== mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@1.0.4, mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: +mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + +mkdirp@1.0.4, mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== +mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: - minimist "^1.2.5" - -modify-values@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" - integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + minimist "^1.2.6" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== dependencies: - dns-packet "^1.3.1" + dns-packet "^5.2.2" thunky "^1.0.2" -multimatch@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" - integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== - dependencies: - "@types/minimatch" "^3.0.3" - array-differ "^3.0.0" - array-union "^2.1.0" - arrify "^2.0.1" - minimatch "^3.0.4" - -mute-stream@0.0.8: +mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== @@ -7963,62 +8026,45 @@ mute-stream@0.0.8: mv@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" - integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI= + integrity sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg== dependencies: mkdirp "~0.5.1" ncp "~2.0.0" rimraf "~2.4.0" -nan@^2.12.1: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== - -nanoid@^3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== - -nanoid@^3.1.22: - version "3.1.22" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" - integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== ncp@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" - integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== -needle@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" - integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== +needle@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-3.1.0.tgz#3bf5cd090c28eb15644181ab6699e027bd6c53c9" + integrity sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw== dependencies: debug "^3.2.6" - iconv-lite "^0.4.4" + iconv-lite "^0.6.3" sax "^1.2.4" -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@0.6.3, negotiator@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== neo-async@^2.6.0, neo-async@^2.6.2: version "2.6.2" @@ -8030,51 +8076,56 @@ next-tick@1, next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -ng-packagr@~12.0.0-next.0: - version "12.0.0-next.1" - resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-12.0.0-next.1.tgz#ce5e20113f589822c3d2c4cdf9bf062a8cd91847" - integrity sha512-z6rlmsrZaLsMhkXyA5EM2SFvsxw0sS1ASunRkdv3yNLcisu5UOJpZ4u5AKWk5V3Z1jgUx4+RQDZ68RwfzBK82A== - dependencies: - "@rollup/plugin-commonjs" "^17.0.0" - "@rollup/plugin-json" "^4.1.0" - "@rollup/plugin-node-resolve" "^11.1.0" - ajv "^7.0.3" - ansi-colors "^4.1.1" - autoprefixer "^10.2.4" - browserslist "^4.16.1" - chokidar "^3.5.1" - commander "^7.0.0" - cssnano "^4.1.10" - glob "^7.1.6" +ng-packagr@15.0.0-rc.0: + version "15.0.0-rc.0" + resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-15.0.0-rc.0.tgz#379c49828d19a5a652b1a111a58f5c093174164f" + integrity sha512-EfhfrH3qFteiBpV5GBFguhVlq8RAyuud0pTmMDv/T2ckHCIQJs97GQKGj+E2lwMOi8bQKhfvcgd10uSeHDGp4w== + dependencies: + "@rollup/plugin-json" "^5.0.0" + "@rollup/plugin-node-resolve" "^15.0.0" + ajv "^8.11.0" + ansi-colors "^4.1.3" + autoprefixer "^10.4.12" + browserslist "^4.21.4" + cacache "^17.0.0" + chokidar "^3.5.3" + commander "^9.4.0" + dependency-graph "^0.11.0" + esbuild-wasm "^0.15.9" + find-cache-dir "^3.3.2" + glob "^8.0.3" injection-js "^2.4.0" - less "^4.1.0" - node-sass-tilde-importer "^1.0.2" + jsonc-parser "^3.2.0" + less "^4.1.3" ora "^5.1.0" - postcss "^8.2.4" - postcss-url "^10.1.1" - rimraf "^3.0.0" - rollup "^2.37.0" + postcss "^8.4.16" + postcss-url "^10.1.3" + rollup "^3.0.0" rollup-plugin-sourcemaps "^0.6.3" - rxjs "^6.5.0" - sass "^1.32.5" - stylus "^0.54.8" - sync-rpc "^1.3.6" - terser "^5.5.1" + rxjs "^7.5.6" + sass "^1.55.0" + optionalDependencies: + esbuild "^0.15.9" -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nice-napi@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b" + integrity sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA== + dependencies: + node-addon-api "^3.0.0" + node-gyp-build "^4.2.2" -node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-addon-api@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + +node-fetch@2.6.7, node-fetch@^2.2.0: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" node-fetch@^1.0.1: version "1.7.3" @@ -8084,43 +8135,36 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-gyp-build@^4.2.2: + version "4.5.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== -node-gyp@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" - integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== +node-gyp@^9.0.0, node-gyp@^9.1.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.3.0.tgz#f8eefe77f0ad8edb3b3b898409b53e697642b319" + integrity sha512-A6rJWfXFz7TQNjpldJ915WFb1LnhO4lIve3ANPbWreuEoLoKlFT3sxIepPBkLhM27crW8YmN+pjlgbasH6cH/Q== dependencies: env-paths "^2.2.0" glob "^7.1.4" - graceful-fs "^4.2.3" - nopt "^5.0.0" - npmlog "^4.1.2" - request "^2.88.2" + graceful-fs "^4.2.6" + make-fetch-happen "^10.0.3" + nopt "^6.0.0" + npmlog "^6.0.0" rimraf "^3.0.2" - semver "^7.3.2" - tar "^6.0.2" + semver "^7.3.5" + tar "^6.1.2" which "^2.0.2" -node-releases@^1.1.70: - version "1.1.70" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08" - integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw== - -node-sass-tilde-importer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.2.tgz#1a15105c153f648323b4347693fdb0f331bad1ce" - integrity sha512-Swcmr38Y7uB78itQeBm3mThjxBy9/Ah/ykPIaURY/L6Nec9AyRoL/jJ7ECfMR+oZeCTVQNxVMu/aHU+TLRVbdg== - dependencies: - find-parent-dir "^0.3.0" - -node-uuid@1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" - integrity sha1-sEDrCSOWivq/jTL7HxfxFn/auQc= +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== nopt@^4.0.1: version "4.0.3" @@ -8130,14 +8174,14 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== +nopt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== dependencies: - abbrev "1" + abbrev "^1.0.0" -normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0, "normalize-package-data@~1.0.1 || ^2.0.0": +normalize-package-data@^2.0.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -8147,22 +8191,25 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package- semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.0.tgz#1f8a7c423b3d2e85eb36985eaf81de381d01301a" - integrity sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw== +normalize-package-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-4.0.1.tgz#b46b24e0616d06cadf9d5718b29b6d445a82a62c" + integrity sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg== dependencies: - hosted-git-info "^3.0.6" - resolve "^1.17.0" - semver "^7.3.2" - validate-npm-package-license "^3.0.1" + hosted-git-info "^5.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= +normalize-package-data@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-5.0.0.tgz#abcb8d7e724c40d88462b84982f7cbf6859b4588" + integrity sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q== dependencies: - remove-trailing-separator "^1.0.1" + hosted-git-info "^6.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -8172,29 +8219,47 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -normalize-url@^4.1.0, normalize-url@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +npm-audit-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-3.0.0.tgz#1bf3e531208b5f77347c8d00c3d9badf5be30cd6" + integrity sha512-tWQzfbwz1sc4244Bx2BVELw0EmZlCsCF0X93RDcmmwhonCsPMoEviYsi+32R+mdRvOWXolPce9zo64n2xgPESw== + dependencies: + chalk "^4.0.0" npm-bundled@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== dependencies: npm-normalize-package-bin "^1.0.1" -npm-install-checks@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" - integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-bundled@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-3.0.0.tgz#7e8e2f8bb26b794265028491be60321a25a39db7" + integrity sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ== + dependencies: + npm-normalize-package-bin "^3.0.0" + +npm-install-checks@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-5.0.0.tgz#5ff27d209a4e3542b8ac6b0c1db6063506248234" + integrity sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA== + dependencies: + semver "^7.1.1" + +npm-install-checks@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.0.0.tgz#9a021d8e8b3956d61fd265c2eda4735bcd3d9b83" + integrity sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q== dependencies: semver "^7.1.1" @@ -8203,241 +8268,278 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== -npm-package-arg@8.1.2, npm-package-arg@^8.1.2: - version "8.1.2" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.2.tgz#b868016ae7de5619e729993fbd8d11dc3c52ab62" - integrity sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA== +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-normalize-package-bin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz#6097436adb4ef09e2628b59a7882576fe53ce485" + integrity sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q== + +npm-package-arg@9.1.2, npm-package-arg@^9.0.0, npm-package-arg@^9.0.1, npm-package-arg@^9.1.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-9.1.2.tgz#fc8acecb00235f42270dda446f36926ddd9ac2bc" + integrity sha512-pzd9rLEx4TfNJkovvlBSLGhq31gGu2QDexFPWT19yCDh0JgnRhlBLNo5759N0AJmBk+kQ9Y/hXoLnlgFD+ukmg== dependencies: - hosted-git-info "^4.0.1" - semver "^7.3.4" - validate-npm-package-name "^3.0.0" + hosted-git-info "^5.0.0" + proc-log "^2.0.1" + semver "^7.3.5" + validate-npm-package-name "^4.0.0" -"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0": - version "6.1.1" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" - integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== +npm-package-arg@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-10.0.0.tgz#a34f4a4208a937074b1fff0943a684fbacc83977" + integrity sha512-7dkh8mRp7s0KwVHKIVJnFCJQ2B34gOGnzgBjDGyprycmARq/82SX/lhilQ95ZuacP/G/1gsS345iAkKmxWBQ2Q== dependencies: - hosted-git-info "^2.7.1" - osenv "^0.1.5" - semver "^5.6.0" - validate-npm-package-name "^3.0.0" + hosted-git-info "^6.0.0" + proc-log "^3.0.0" + semver "^7.3.5" + validate-npm-package-name "^5.0.0" -npm-package-arg@^8.0.0, npm-package-arg@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.0.tgz#b5f6319418c3246a1c38e1a8fbaa06231bc5308f" - integrity sha512-/ep6QDxBkm9HvOhOg0heitSd7JHA1U7y1qhhlRlteYYAi9Pdb/ZV7FW5aHpkrpM8+P+4p/jjR8zCyKPBMBjSig== +npm-packlist@^5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== dependencies: - hosted-git-info "^3.0.6" - semver "^7.0.0" - validate-npm-package-name "^3.0.0" + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" -npm-packlist@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.1.4.tgz#40e96b2b43787d0546a574542d01e066640d09da" - integrity sha512-Qzg2pvXC9U4I4fLnUrBmcIT4x0woLtUgxUi9eC+Zrcv1Xx5eamytGAfbDWQ67j7xOcQ2VW1I3su9smVTIdu7Hw== +npm-packlist@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-7.0.1.tgz#984455991f12fed40d7018b8d012ed17a37303c0" + integrity sha512-XddbYutimy7hdmP7S1tHMjFwghn64lvgdnhYG0KLGFBWjEvMt1/jg95OR3vPNNCjkakHS+k4a//3XOO8JOGI2A== dependencies: - glob "^7.1.6" - ignore-walk "^3.0.3" - npm-bundled "^1.1.1" - npm-normalize-package-bin "^1.0.1" + ignore-walk "^6.0.0" -npm-pick-manifest@6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" - integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== +npm-pick-manifest@8.0.1, npm-pick-manifest@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz#c6acd97d1ad4c5dbb80eac7b386b03ffeb289e5f" + integrity sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA== dependencies: - npm-install-checks "^4.0.0" - npm-normalize-package-bin "^1.0.1" - npm-package-arg "^8.1.2" - semver "^7.3.4" + npm-install-checks "^6.0.0" + npm-normalize-package-bin "^3.0.0" + npm-package-arg "^10.0.0" + semver "^7.3.5" -npm-pick-manifest@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.0.tgz#2befed87b0fce956790f62d32afb56d7539c022a" - integrity sha512-ygs4k6f54ZxJXrzT0x34NybRlLeZ4+6nECAIbr2i0foTnijtS1TJiyzpqtuUAJOps/hO0tNDr8fRV5g+BtRlTw== +npm-pick-manifest@^7.0.0, npm-pick-manifest@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz#1d372b4e7ea7c6712316c0e99388a73ed3496e84" + integrity sha512-gk37SyRmlIjvTfcYl6RzDbSmS9Y4TOBXfsPnoYqTHARNgWbyDiCSMLUpmALDj4jjcTZpURiEfsSHJj9k7EV4Rw== dependencies: - npm-install-checks "^4.0.0" - npm-package-arg "^8.0.0" - semver "^7.0.0" + npm-install-checks "^5.0.0" + npm-normalize-package-bin "^2.0.0" + npm-package-arg "^9.0.0" + semver "^7.3.5" -npm-registry-client@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4" - integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg== +npm-profile@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-6.2.1.tgz#975c31ec75a6ae029ab5b8820ffdcbae3a1e3d5e" + integrity sha512-Tlu13duByHyDd4Xy0PgroxzxnBYWbGGL5aZifNp8cx2DxUrHSoETXtPKg38aRPsBWMRfDtvcvVfJNasj7oImQQ== dependencies: - concat-stream "^1.5.2" - graceful-fs "^4.1.6" - normalize-package-data "~1.0.1 || ^2.0.0" - npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" - once "^1.3.3" - request "^2.74.0" - retry "^0.10.0" - safe-buffer "^5.1.1" - semver "2 >=2.2.1 || 3.x || 4 || 5" - slide "^1.1.3" - ssri "^5.2.4" - optionalDependencies: - npmlog "2 || ^3.1.0 || ^4.0.0" + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" -npm-registry-fetch@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661" - integrity sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA== +npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1, npm-registry-fetch@^13.3.1: + version "13.3.1" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz#bb078b5fa6c52774116ae501ba1af2a33166af7e" + integrity sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw== dependencies: - "@npmcli/ci-detect" "^1.0.0" - lru-cache "^6.0.0" - make-fetch-happen "^8.0.9" - minipass "^3.1.3" - minipass-fetch "^1.3.0" + make-fetch-happen "^10.0.6" + minipass "^3.1.6" + minipass-fetch "^2.0.3" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^9.0.1" + proc-log "^2.0.0" + +npm-registry-fetch@^14.0.0: + version "14.0.2" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.2.tgz#f637630d9005aeebe4d7411226fb11fa1628c5e8" + integrity sha512-TMenrMagFA9KF81E2bkS5XRyzERK4KXu70vgXt5+i8FcrFeLNgNsc6e5hekTqjDwPDkL3HGn/holWcXDMfnFgw== + dependencies: + make-fetch-happen "^11.0.0" + minipass "^3.1.6" + minipass-fetch "^3.0.0" minipass-json-stream "^1.0.1" - minizlib "^2.0.0" - npm-package-arg "^8.0.0" + minizlib "^2.1.2" + npm-package-arg "^10.0.0" + proc-log "^3.0.0" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: - path-key "^2.0.0" + path-key "^3.0.0" -"npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" +npm-user-validate@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561" + integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw== + +npm@^8.11.0: + version "8.19.2" + resolved "https://registry.yarnpkg.com/npm/-/npm-8.19.2.tgz#db90e88584d065f51b069ab46b4f02f5cf4898b7" + integrity sha512-MWkISVv5f7iZbfNkry5/5YBqSYJEDAKSJdL+uzSQuyLg+hgLQUyZynu3SH6bOZlvR9ZvJYk2EiJO6B1r+ynwHg== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^5.6.2" + "@npmcli/ci-detect" "^2.0.0" + "@npmcli/config" "^4.2.1" + "@npmcli/fs" "^2.1.0" + "@npmcli/map-workspaces" "^2.0.3" + "@npmcli/package-json" "^2.0.0" + "@npmcli/run-script" "^4.2.1" + abbrev "~1.1.1" + archy "~1.0.0" + cacache "^16.1.3" + chalk "^4.1.2" + chownr "^2.0.0" + cli-columns "^4.0.0" + cli-table3 "^0.6.2" + columnify "^1.6.0" + fastest-levenshtein "^1.0.12" + glob "^8.0.1" + graceful-fs "^4.2.10" + hosted-git-info "^5.1.0" + ini "^3.0.1" + init-package-json "^3.0.2" + is-cidr "^4.0.2" + json-parse-even-better-errors "^2.3.1" + libnpmaccess "^6.0.4" + libnpmdiff "^4.0.5" + libnpmexec "^4.0.13" + libnpmfund "^3.0.4" + libnpmhook "^8.0.4" + libnpmorg "^4.0.4" + libnpmpack "^4.1.3" + libnpmpublish "^6.0.5" + libnpmsearch "^5.0.4" + libnpmteam "^4.0.4" + libnpmversion "^3.0.7" + make-fetch-happen "^10.2.0" + minipass "^3.1.6" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + ms "^2.1.2" + node-gyp "^9.1.0" + nopt "^6.0.0" + npm-audit-report "^3.0.0" + npm-install-checks "^5.0.0" + npm-package-arg "^9.1.0" + npm-pick-manifest "^7.0.2" + npm-profile "^6.2.0" + npm-registry-fetch "^13.3.1" + npm-user-validate "^1.0.1" + npmlog "^6.0.2" + opener "^1.5.2" + p-map "^4.0.0" + pacote "^13.6.2" + parse-conflict-json "^2.0.2" + proc-log "^2.0.1" + qrcode-terminal "^0.12.0" + read "~1.0.7" + read-package-json "^5.0.2" + read-package-json-fast "^2.0.3" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.7" + ssri "^9.0.1" + tar "^6.1.11" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^2.0.0" + validate-npm-package-name "^4.0.0" + which "^2.0.2" + write-file-atomic "^4.0.1" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== +npmlog@^6.0.0, npmlog@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== dependencies: - boolbase "~1.0.0" + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" -nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== dependencies: boolbase "^1.0.0" -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - nwsapi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4, object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.8.0, object-inspect@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== +object-inspect@^1.12.2, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== object-inspect@~1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== -object-is@^1.0.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068" - integrity sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - -object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: +object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz#0dfda8d108074d9c563e80490c883b6661091544" - integrity sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" - integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - has "^1.0.3" + es-abstract "^1.19.1" obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -octokit-pagination-methods@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" - integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== dependencies: ee-first "1.1.1" @@ -8446,33 +8548,43 @@ on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" -open@8.0.5: - version "8.0.5" - resolved "https://registry.yarnpkg.com/open/-/open-8.0.5.tgz#92ee3faafef4ddbe78006f7881572f3e81430b8f" - integrity sha512-hkPXCz7gijWp2GoWqsQ4O/5p7F6d5pIQ/+9NyeWG1nABJ4zvLi9kJRv1a44kVf5p13wK0WMoiRA+Xey68yOytA== +open@8.4.0, open@^8.0.9: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== dependencies: define-lazy-prop "^2.0.0" is-docker "^2.1.1" is-wsl "^2.2.0" -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +openurl@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387" + integrity sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA== + +opn@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" + integrity sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g== dependencies: is-wsl "^1.1.0" @@ -8488,10 +8600,22 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" -ora@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.0.tgz#42eda4855835b9cd14d33864c97a3c95a3f56bf4" - integrity sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg== +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +ora@5.4.1, ora@^5.1.0, ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: bl "^4.1.0" chalk "^4.1.0" @@ -8503,46 +8627,17 @@ ora@5.4.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ora@^5.0.0, ora@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" - integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== - dependencies: - bl "^4.0.3" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - log-symbols "^4.0.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-name@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -osenv@^0.1.4, osenv@^0.1.5: +osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -8550,63 +8645,20 @@ osenv@^0.1.4, osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" - integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== - -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - -p-event@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" - integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== - dependencies: - p-timeout "^3.1.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - -p-limit@^2.0.0, p-limit@^2.2.0: +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -8621,11 +8673,6 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-map@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -8633,61 +8680,75 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" - -p-timeout@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== dependencies: - p-finally "^1.0.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + "@types/retry" "0.12.0" + retry "^0.13.1" p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pacote@11.3.1: - version "11.3.1" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.1.tgz#6ce95dd230db475cbd8789fd1f986bec51b4bf7c" - integrity sha512-TymtwoAG12cczsJIrwI/euOQKtjrQHlD0k0oyt9QSmZGpqa+KdlxKdWR/YUjYizkixaVyztxt/Wsfo8bL3A6Fg== - dependencies: - "@npmcli/git" "^2.0.1" - "@npmcli/installed-package-contents" "^1.0.6" - "@npmcli/promise-spawn" "^1.2.0" - "@npmcli/run-script" "^1.8.2" - cacache "^15.0.5" +pacote@15.0.6: + version "15.0.6" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-15.0.6.tgz#8c498b5c23270da4f4c87f7eeba0248a3ae61342" + integrity sha512-dQwcz/sME7QIL+cdrw/jftQfMMXxSo17i2kJ/gnhBhUvvBAsxoBu1lw9B5IzCH/Ce8CvEkG/QYZ6txzKfn0bTw== + dependencies: + "@npmcli/git" "^4.0.0" + "@npmcli/installed-package-contents" "^2.0.1" + "@npmcli/promise-spawn" "^6.0.1" + "@npmcli/run-script" "^6.0.0" + cacache "^17.0.0" + fs-minipass "^2.1.0" + minipass "^3.1.6" + npm-package-arg "^10.0.0" + npm-packlist "^7.0.0" + npm-pick-manifest "^8.0.0" + npm-registry-fetch "^14.0.0" + proc-log "^3.0.0" + promise-retry "^2.0.1" + read-package-json "^6.0.0" + read-package-json-fast "^3.0.0" + ssri "^10.0.0" + tar "^6.1.11" + +pacote@^13.0.3, pacote@^13.6.1, pacote@^13.6.2: + version "13.6.2" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" + integrity sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg== + dependencies: + "@npmcli/git" "^3.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/promise-spawn" "^3.0.0" + "@npmcli/run-script" "^4.1.0" + cacache "^16.0.0" chownr "^2.0.0" fs-minipass "^2.1.0" infer-owner "^1.0.4" - minipass "^3.1.3" - mkdirp "^1.0.3" - npm-package-arg "^8.0.1" - npm-packlist "^2.1.4" - npm-pick-manifest "^6.0.0" - npm-registry-fetch "^9.0.0" + minipass "^3.1.6" + mkdirp "^1.0.4" + npm-package-arg "^9.0.0" + npm-packlist "^5.1.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" promise-retry "^2.0.1" - read-package-json-fast "^2.0.1" + read-package-json "^5.0.0" + read-package-json-fast "^2.0.3" rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.1.0" + ssri "^9.0.0" + tar "^6.1.11" pako@^0.2.5: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== -pako@^1.0.6, pako@~1.0.2: +pako@^1.0.3, pako@^1.0.6, pako@~1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -8699,25 +8760,14 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-github-repo-url@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" - integrity sha1-nn2LslKmy2ukJZUGC3v23z28H1A= - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= +parse-conflict-json@^2.0.1, parse-conflict-json@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz#3d05bc8ffe07d39600dc6436c6aefe382033d323" + integrity sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA== dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.1" + just-diff "^5.0.1" + just-diff-apply "^5.2.0" parse-json@^5.0.0: version "5.2.0" @@ -8761,48 +8811,28 @@ parse5-sax-parser@^6.0.1: dependencies: parse5 "^6.0.1" -parse5@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== +parse5@*: + version "7.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746" + integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg== + dependencies: + entities "^4.4.0" + +parse5@6.0.1, parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== parse5@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -8811,48 +8841,27 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-type@^4.0.0: version "4.0.0" @@ -8862,39 +8871,51 @@ path-type@^4.0.0: pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -pidtree@0.5.0, pidtree@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.5.0.tgz#ad5fbc1de78b8a5f99d6fbdd4f6e4eee21d1aca1" - integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@0.6.0, pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== -pidusage@2.0.21, pidusage@^2.0.17: - version "2.0.21" - resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-2.0.21.tgz#7068967b3d952baea73e57668c98b9eaa876894e" - integrity sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA== +pidusage@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-3.0.2.tgz#6faa5402b2530b3af2cf93d13bcf202889724a53" + integrity sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w== + dependencies: + safe-buffer "^5.2.1" + +pidusage@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-3.0.1.tgz#b8383319aca2ed810d1bcc4207c1c5c377d94f5f" + integrity sha512-/UlE6DQIe6yuDvm3v6756U0ErEsj60FLQTRZ4qPQF9b5yZKhf4c0llzD0tZpyE03nn8HQoLniFgKsL0ABB3nCg== dependencies: safe-buffer "^5.2.1" -pify@^2.0.0, pify@^2.3.0: +pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pify@^4.0.1: version "4.0.1" @@ -8904,40 +8925,45 @@ pify@^4.0.1: pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== pino-std-serializers@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz#b56487c402d882eb96cd67c257868016b61ad671" integrity sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg== -pino@6.11.2: - version "6.11.2" - resolved "https://registry.yarnpkg.com/pino/-/pino-6.11.2.tgz#2f3d119c526651aab4ec3d280844785d52d0b690" - integrity sha512-bmzxwbrIPxQUlAuMkF4PWVErUGERU4z37HazlhflKFg08crsNE3fACGN6gPwg5xtKOK47Ux5cZm8YCuLV4wWJg== +pino@6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-6.14.0.tgz#b745ea87a99a6c4c9b374e4f29ca7910d4c69f78" + integrity sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg== dependencies: fast-redact "^3.0.0" - fast-safe-stringify "^2.0.7" + fast-safe-stringify "^2.0.8" flatstr "^1.0.12" pino-std-serializers "^3.1.0" - quick-format-unescaped "4.0.1" + process-warning "^1.0.0" + quick-format-unescaped "^4.0.3" sonic-boom "^1.0.2" -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== +piscina@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/piscina/-/piscina-3.2.0.tgz#f5a1dde0c05567775690cccefe59d9223924d154" + integrity sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA== dependencies: - find-up "^3.0.0" + eventemitter-asyncresource "^1.0.0" + hdr-histogram-js "^2.0.1" + hdr-histogram-percentiles-obj "^3.0.0" + optionalDependencies: + nice-napi "^1.0.2" -pkg-dir@^4.1.0, pkg-dir@^4.2.0: +pkg-dir@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -8947,952 +8973,189 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: pkginfo@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" - integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= + integrity sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ== pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== -pn@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" - integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== - -popper.js@^1.14.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" - integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== - -portfinder@^1.0.26: - version "1.0.28" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" - integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== - dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.5" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -postcss-attribute-case-insensitive@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" - integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^6.0.2" - -postcss-calc@^7.0.1: - version "7.0.5" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" - integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== - dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== - dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-color-functional-notation@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" - integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-gray@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" - integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-color-hex-alpha@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" - integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== - dependencies: - postcss "^7.0.14" - postcss-values-parser "^2.0.1" - -postcss-color-mod-function@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" - integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-rebeccapurple@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" - integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-colormin@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.0.0.tgz#283b8934c8bdbc531e7648aeb0970107f6d06d0e" - integrity sha512-Yt84+5V6CgS/AhK7d7MA58vG8dSZ7+ytlRtWLaQhag3HXOncTfmYpuUOX4cDoXjvLfw1sHRCHMiBjYhc35CymQ== - dependencies: - browserslist "^4.16.0" - color "^3.1.1" - postcss-value-parser "^4.1.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.0.tgz#cd77e1d23ebe8fcf508640551eed08e232784cba" - integrity sha512-V5kmYm4xoBAjNs+eHY/6XzXJkkGeg4kwNf2ocfqhLb1WBPEa4oaSmoi1fnVO7Dkblqvus9h+AenDvhCKUCK7uQ== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-custom-media@^7.0.8: - version "7.0.8" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" - integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== - dependencies: - postcss "^7.0.14" - -postcss-custom-properties@^8.0.11: - version "8.0.11" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" - integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== - dependencies: - postcss "^7.0.17" - postcss-values-parser "^2.0.1" - -postcss-custom-selectors@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" - integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-dir-pseudo-class@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" - integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-comments@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.0.tgz#6c27310e0657c0b9e38a6175ad001b5aa28964bc" - integrity sha512-Umig6Gxs8m20RihiXY6QkePd6mp4FxkA1Dg+f/Kd6uw0gEMfKRjDeQOyFkLibexbJJGHpE3lrN/Q0R9SMrUMbQ== - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.0.tgz#6a2c4f779e8d20da6781e90730f234f9e650c51c" - integrity sha512-vEJJ+Y3pFUnO1FyCBA6PSisGjHtnphL3V6GsNvkASq/VkP3OX5/No5RYXXLxHa2QegStNzg6HYrYdo71uR4caQ== - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.0.tgz#0f0a9baee415f5f7be4ae046ba235e98626ba821" - integrity sha512-+wigy099Y1xZxG36WG5L1f2zeH1oicntkJEW4TDIqKKDO2g9XVB3OhoiHTu08rDEjLnbcab4rw0BAccwi2VjiQ== - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.0.tgz#ac00f695a60001eda52135a11fac87376b8da9ee" - integrity sha512-hybnScTaZM2iEA6kzVQ6Spozy7kVdLw+lGw8hftLlBEzt93uzXoltkYp9u0tI8xbfhxDLTOOzHsHQCkYdmzRUg== - -postcss-double-position-gradients@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" - integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== - dependencies: - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-env-function@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" - integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-focus-visible@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" - integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== - dependencies: - postcss "^7.0.2" - -postcss-focus-within@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" - integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== - dependencies: - postcss "^7.0.2" - -postcss-font-variant@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" - integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== - dependencies: - postcss "^7.0.2" - -postcss-gap-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" - integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== - dependencies: - postcss "^7.0.2" - -postcss-image-set-function@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" - integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-import@14.0.1: - version "14.0.1" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.0.1.tgz#6a3f8f2ce74a95fc7c72ecfe3eddfa0e9124e677" - integrity sha512-Xn2+z++vWObbEPhiiKO1a78JiyhqipyrXHBb3AHpv0ks7Cdg+GxQQJ24ODNMTanldf7197gSP3axppO9yaG0lA== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-initial@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" - integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== - dependencies: - lodash.template "^4.5.0" - postcss "^7.0.2" - -postcss-lab-function@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" - integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-loader@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-5.2.0.tgz#ccd6668a778902d653602289c765a8bc481986dc" - integrity sha512-uSuCkENFeUaOYsKrXm0eNNgVIxc71z8RcckLMbVw473rGojFnrUeqEz6zBgXsH2q1EIzXnO/4pEz9RhALjlITA== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.4" - semver "^7.3.4" - -postcss-logical@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" - integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== - dependencies: - postcss "^7.0.2" - -postcss-media-minmax@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" - integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== - dependencies: - postcss "^7.0.2" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-longhand@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.0.tgz#103dee28c55491df727f17d7b8e91e93e7a472ee" - integrity sha512-VZNFA40K8BYHzJNA6jHPdg1Nofsz/nK5Dkszrcb5IgWcLroSBZOD6I/iNQzpejSU/3XwpOiZNaYAdBV4KcvxWA== - dependencies: - css-color-names "^1.0.1" - postcss-value-parser "^4.1.0" - stylehacks "^5.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-merge-rules@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.0.tgz#e0d0c0d45c98376f4adb49eb1f1dfe2aebfd7048" - integrity sha512-TfsXbKjNYCGfUPEXGIGPySnMiJbdS+3gcVeV8gwmJP4RajyKZHW8E0FYDL1WmggTj3hi+m+WUCAvqRpX2ut4Kg== - dependencies: - browserslist "^4.16.0" - caniuse-api "^3.0.0" - cssnano-utils "^2.0.0" - postcss-selector-parser "^6.0.4" - vendors "^1.0.3" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-font-values@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.0.tgz#fee5d0fa192fae8757cb744870a0ad02be5f402e" - integrity sha512-zi2JhFaMOcIaNxhndX5uhsqSY1rexKDp23wV8EOmC9XERqzLbHsoRye3aYF716Zm+hkcR4loqKDt8LZlmihwAg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.0.tgz#95dbe61567a45c0cd7ab897d78fb65d5096844ed" - integrity sha512-/jPtNgs6JySMwgsE5dPOq8a2xEopWTW3RyqoB9fLqxgR+mDUNLSi7joKd+N1z7FXWgVkc4l/dEBMXHgNAaUbvg== - dependencies: - cssnano-utils "^2.0.0" - is-color-stop "^1.1.0" - postcss-value-parser "^4.1.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-params@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.0.tgz#12c7f75d69b0b4827fafbd6649970a53784a9c24" - integrity sha512-KvZYIxTPBVKjdd+XgObq9A+Sfv8lMkXTpbZTsjhr42XbfWIeLaTItMlygsDWfjArEc3muUfDaUFgNSeDiJ5jug== - dependencies: - alphanum-sort "^1.0.2" - browserslist "^4.16.0" - cssnano-utils "^2.0.0" - postcss-value-parser "^4.1.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-minify-selectors@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.0.0.tgz#d3e43d97fd0ba83ba0010950fc5acfa420f7caa9" - integrity sha512-cEM0O0eWwFIvmo6nfB0lH0vO/XFwgqIvymODbfPXZ1gTA3i76FKnb7TGUrEpiTxaXH6tgYQ6DcTHwRiRS+YQLQ== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^3.1.2" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-nesting@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" - integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== - dependencies: - postcss "^7.0.2" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" - -postcss-normalize-charset@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.0.tgz#59e1fe2094fb2e3371cc5b054cbc39828a41a710" - integrity sha512-pqsCkgo9KmQP0ew6DqSA+uP9YN6EfsW20pQ3JU5JoQge09Z6Too4qU0TNDsTNWuEaP8SWsMp+19l15210MsDZQ== - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-display-values@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.0.tgz#4ff2d3b3b5146a366de28ec9e24131a1868f1933" - integrity sha512-t4f2d//gH1f7Ns0Jq3eNdnWuPT7TeLuISZ6RQx4j8gpl5XrhkdshdNcOnlrEK48YU6Tcb6jqK7dorME3N4oOGA== - dependencies: - cssnano-utils "^2.0.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.0.tgz#fe1d9a8122dd385b9c6908bd2008140dea17750d" - integrity sha512-0o6/qU5ky74X/eWYj/tv4iiKCm3YqJnrhmVADpIMNXxzFZywsSQxl8F7cKs8jQEtF3VrJBgcDHTexZy1zgDoYg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.0.tgz#e11d88fbf63f89179c6a7391853b2fe7f46e589d" - integrity sha512-KRT14JbrXKcFMYuc4q7lh8lvv8u22wLyMrq+UpHKLtbx2H/LOjvWXYdoDxmNrrrJzomAWL+ViEXr48/IhSUJnQ== - dependencies: - cssnano-utils "^2.0.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.0.tgz#2ea08ff4cb8817ce160755e9fdc7e6ef6d495002" - integrity sha512-wSO4pf7GNcDZpmelREWYADF1+XZWrAcbFLQCOqoE92ZwYgaP/RLumkUTaamEzdT2YKRZAH8eLLKGWotU/7FNPw== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.0.tgz#380eb1c9b179f96efc307c659a8049116f16f381" - integrity sha512-TwPaDX+wl9wO3MUm23lzGmOzGCGKnpk+rSDgzB2INpakD5dgWR3L6bJq1P1LQYzBAvz8fRIj2NWdnZdV4EV98Q== - dependencies: - cssnano-utils "^2.0.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.0.tgz#aa46a89c86ae51a01cbca13e73c1ed7b0b38807e" - integrity sha512-2CpVoz/67rXU5s9tsPZDxG1YGS9OFHwoY9gsLAzrURrCxTAb0H7Vp87/62LvVPgRWTa5ZmvgmqTp2rL8tlm72A== - dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.0.tgz#626a4c7d30007f94466cdf245e7ed9f253f1dbd9" - integrity sha512-ICDaGFBqLgA3dlrCIRuhblLl80D13YtgEV9NJPTYJtgR72vu61KgxAHv+z/lKMs1EbwfSQa3ALjOFLSmXiE34A== - dependencies: - is-absolute-url "^3.0.3" - normalize-url "^4.5.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.0.tgz#1faf147a4f8d3d93a3c75109d120b4eefa00589b" - integrity sha512-KRnxQvQAVkJfaeXSz7JlnD9nBN9sFZF9lrk9452Q2uRoqrRSkinqifF8Iex7wZGei2DZVG/qpmDFDmRvbNAOGA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-ordered-values@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.0.tgz#a50f224c5f40c566b338b0663655478737dcebee" - integrity sha512-dPr+SRObiHueCIc4IUaG0aOGQmYkuNu50wQvdXTGKy+rzi2mjmPsbeDsheLk5WPb9Zyf2tp8E+I+h40cnivm6g== - dependencies: - cssnano-utils "^2.0.0" - postcss-value-parser "^4.1.0" - -postcss-overflow-shorthand@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" - integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== - dependencies: - postcss "^7.0.2" - -postcss-page-break@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" - integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== - dependencies: - postcss "^7.0.2" - -postcss-place@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" - integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-preset-env@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" - integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== - dependencies: - autoprefixer "^9.6.1" - browserslist "^4.6.4" - caniuse-lite "^1.0.30000981" - css-blank-pseudo "^0.1.4" - css-has-pseudo "^0.10.0" - css-prefers-color-scheme "^3.1.1" - cssdb "^4.4.0" - postcss "^7.0.17" - postcss-attribute-case-insensitive "^4.0.1" - postcss-color-functional-notation "^2.0.1" - postcss-color-gray "^5.0.0" - postcss-color-hex-alpha "^5.0.3" - postcss-color-mod-function "^3.0.3" - postcss-color-rebeccapurple "^4.0.1" - postcss-custom-media "^7.0.8" - postcss-custom-properties "^8.0.11" - postcss-custom-selectors "^5.1.2" - postcss-dir-pseudo-class "^5.0.0" - postcss-double-position-gradients "^1.0.0" - postcss-env-function "^2.0.2" - postcss-focus-visible "^4.0.0" - postcss-focus-within "^3.0.0" - postcss-font-variant "^4.0.0" - postcss-gap-properties "^2.0.0" - postcss-image-set-function "^3.0.1" - postcss-initial "^3.0.0" - postcss-lab-function "^2.0.1" - postcss-logical "^3.0.0" - postcss-media-minmax "^4.0.0" - postcss-nesting "^7.0.0" - postcss-overflow-shorthand "^2.0.0" - postcss-page-break "^2.0.0" - postcss-place "^4.0.1" - postcss-pseudo-class-any-link "^6.0.0" - postcss-replace-overflow-wrap "^3.0.0" - postcss-selector-matches "^4.0.0" - postcss-selector-not "^4.0.0" - -postcss-pseudo-class-any-link@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" - integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-initial@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.0.tgz#c724e5513b0ae7f3d7bff16f0fc82133fb2f820a" - integrity sha512-wR6pXUaFbSMG1oCKx8pKVA+rnSXCHlca5jMrlmkmif+uig0HNUTV9oGN5kjKsM3mATQAldv2PF9Tbl2vqLFjnA== - dependencies: - browserslist "^4.16.0" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-reduce-transforms@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.0.tgz#5c820f71fbd4eec82b323523642b7b2d1c7d29ef" - integrity sha512-iHdGODW4YzM3WjVecBhPQt6fpJC4lGQZxJKjkBNHpp2b8dzmvj0ogKThqya+IRodQEFzjfXgYeESkf172FH5Lw== - dependencies: - cssnano-utils "^2.0.0" - postcss-value-parser "^4.1.0" - -postcss-replace-overflow-wrap@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" - integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== - dependencies: - postcss "^7.0.2" - -postcss-selector-matches@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" - integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-not@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" - integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-parser@^3.0.0, postcss-selector-parser@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" +popper.js@^1.14.1: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== +portscanner@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" + integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw== dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" + async "^2.6.0" + is-number-like "^1.0.3" -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== +postcss-loader@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.0.1.tgz#4c883cc0a1b2bfe2074377b7a74c1cd805684395" + integrity sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ== dependencies: - is-svg "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" + cosmiconfig "^7.0.0" + klona "^2.0.5" + semver "^7.3.7" -postcss-svgo@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.0.tgz#c8d806e573394ab24f1e233cac5be4c199e9f1b2" - integrity sha512-M3/VS4sFI1Yp9g0bPL+xzzCNz5iLdRUztoFaugMit5a8sMfkVzzhwqbsOlD8IFFymCdJDmXmh31waYHWw1K4BA== +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" - svgo "^2.3.0" -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" + postcss-selector-parser "^6.0.4" -postcss-unique-selectors@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.0.tgz#17856278f6c38d024defc9694d568bb09dd7f771" - integrity sha512-o9l4pF8SRn7aCMTmzb/kNv/kjV7wPZpZ8Nlb1Gq8v/Qvw969K1wanz1RVA0ehHzWe9+wHXaC2DvZlak/gdMJ5w== +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.2" - uniqs "^2.0.0" + icss-utils "^5.0.0" -postcss-url@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-10.1.1.tgz#f58b4ec684a7b662c170357150eddcbc04cefa24" - integrity sha512-cYeRNcXUMiM1sr3UgHkY+zMuqhSmJaLeP3VOZWWqShBDMB10DlrK5KfciLK0LGr7xKDPP5nH7Q2odvDHQSrP9A== +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + version "6.0.10" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== dependencies: - make-dir "3.1.0" - mime "2.4.6" - minimatch "3.0.4" - xxhashjs "0.2.2" - -postcss-value-parser@^3.0.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + cssesc "^3.0.0" + util-deprecate "^1.0.2" -postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" - integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== +postcss-url@^10.1.3: + version "10.1.3" + resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-10.1.3.tgz#54120cc910309e2475ec05c2cfa8f8a2deafdf1e" + integrity sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw== dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" + make-dir "~3.1.0" + mime "~2.5.2" + minimatch "~3.0.4" + xxhashjs "~0.2.2" -"postcss@5 - 7", postcss@7.x.x, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.35" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" - integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@7.0.21: - version "7.0.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" - integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== +postcss@8.4.18, postcss@^8.2.14, postcss@^8.3.7, postcss@^8.4.16, postcss@^8.4.7: + version "8.4.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2" + integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA== dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" -postcss@8.2.10: - version "8.2.10" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.10.tgz#ca7a042aa8aff494b334d0ff3e9e77079f6f702b" - integrity sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw== +postcss@8.4.19: + version "8.4.19" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.19.tgz#61178e2add236b17351897c8bcc0b4c8ecab56fc" + integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA== dependencies: - colorette "^1.2.2" - nanoid "^3.1.22" - source-map "^0.6.1" + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" -postcss@^8.2.4: - version "8.2.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe" - integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg== +postcss@^8.4.19: + version "8.4.20" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56" + integrity sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g== dependencies: - colorette "^1.2.1" - nanoid "^3.1.20" - source-map "^0.6.1" + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" -postcss@^8.2.8: - version "8.2.9" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.9.tgz#fd95ff37b5cee55c409b3fdd237296ab4096fba3" - integrity sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.22" - source-map "^0.6.1" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -prettier-bytes@^1.0.3: +prettier-bytes@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/prettier-bytes/-/prettier-bytes-1.0.4.tgz#994b02aa46f699c50b6257b5faaa7fe2557e62d6" - integrity sha1-mUsCqkb2mcULYle1+qp/4lV+YtY= + integrity sha512-dLbWOa4xBn+qeWeIF60qRoB6Pk2jX5P3DIVgOQyMyvBpu931Q+8dXz8X0snJiFkQdohDDLnZQECjzsAj75hgZQ== -prettier@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" - integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== +prettier@2.7.1, prettier@^2.0.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== pretty-bytes@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e" - integrity sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA== + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -pretty-ms@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-5.1.0.tgz#b906bdd1ec9e9799995c372e2b1c34f073f95384" - integrity sha512-4gaK1skD2gwscCfkswYQRmddUb2GJZtzDGRjHWadVHtK/DIKFufa12MvES6/xu1tVbUYeia5bmLcwJtZJQUqnw== +pretty-ms@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" + integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== dependencies: parse-ms "^2.1.0" +proc-log@^2.0.0, proc-log@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" + integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== + +proc-log@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" + integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== + process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@2.0.3, progress@^2.0.1: +process-warning@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" + integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== + +progress@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== promise-retry@^2.0.1: version "2.0.1" @@ -9902,6 +9165,13 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +promzard@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw== + dependencies: + read "1" + protobufjs@6.8.8: version "6.8.8" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" @@ -9942,15 +9212,15 @@ protractor@^7.0.0, protractor@~7.0.0: webdriver-manager "^12.1.7" yargs "^15.3.1" -proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: - forwarded "~0.1.2" + forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-from-env@^1.1.0: +proxy-from-env@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -9958,17 +9228,12 @@ proxy-from-env@^1.1.0: prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -psl@^1.1.24, psl@^1.1.28: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== -puka@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/puka/-/puka-1.0.1.tgz#a2df782b7eb4cf9564e4c93a5da422de0dfacc02" - integrity sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g== +psl@^1.1.24, psl@^1.1.28, psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== pump@^3.0.0: version "3.0.0" @@ -9978,68 +9243,78 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer@8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-8.0.0.tgz#a236669118aa795331c2d0ca19877159e7664705" - integrity sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ== - dependencies: - debug "^4.1.0" - devtools-protocol "0.0.854822" - extract-zip "^2.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - pkg-dir "^4.2.0" - progress "^2.0.1" - proxy-from-env "^1.1.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" +puppeteer-core@18.2.1: + version "18.2.1" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-18.2.1.tgz#9b7827bb2bf478bb615e2c21425e4659555dc1fe" + integrity sha512-MRtTAZfQTluz3U2oU/X2VqVWPcR1+94nbA2V6ZrSZRVEwLqZ8eclZ551qGFQD/vD2PYqHJwWOW/fpC721uznVw== + dependencies: + cross-fetch "3.1.5" + debug "4.3.4" + devtools-protocol "0.0.1045489" + extract-zip "2.0.1" + https-proxy-agent "5.0.1" + proxy-from-env "1.1.0" + rimraf "3.0.2" + tar-fs "2.1.1" + unbzip2-stream "1.4.3" + ws "8.9.0" + +puppeteer@18.2.1: + version "18.2.1" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-18.2.1.tgz#08967cd423efe511ee4c6e3a5c882ffaf2e6bbf3" + integrity sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ== + dependencies: + https-proxy-agent "5.0.1" + progress "2.0.3" + proxy-from-env "1.1.0" + puppeteer-core "18.2.1" q@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" - integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= + integrity sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg== -q@^1.1.2, q@^1.4.1, q@^1.5.1: +q@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== qjobs@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qrcode-terminal@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +qs@6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" + integrity sha512-AY4g8t3LMboim0t6XWFdz6J5OuJ1ZNYu54SXihS/OMpgyCqYmcAJnWqkNSOjSjWmq3xxy+GF9uWQI2lI/7tKIA== + +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== querystringify@^2.1.1: version "2.2.0" @@ -10047,21 +9322,16 @@ querystringify@^2.1.1: integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" - integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== - -quick-format-unescaped@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz#437a5ea1a0b61deb7605f8ab6a8fd3858dbeb701" - integrity sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A== + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== -quicktype-core@^6.0.69: +quicktype-core@6.0.69: version "6.0.69" resolved "https://registry.yarnpkg.com/quicktype-core/-/quicktype-core-6.0.69.tgz#955347b64e8a7b6a37af49fe12f5772abc153b8e" integrity sha512-wKQ+/fwgdtFOcbeRiZkIBLA2ajvrFvmtTmexdv7PlO1dyp3C7Irbn2/HjwzalD1dYFrtMEYWohB/4rr3Mg75Xw== @@ -10082,7 +9352,7 @@ quicktype-core@^6.0.69: quote-stream@^1.0.1, quote-stream@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" - integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= + integrity sha512-kKr2uQ2AokadPjvTyKJQad9xELbZwYzWlNfI3Uz2j/ib5u6H9lDP7fUUR//rMycd0gv4Z5P1qXMfXR8YpIxrjQ== dependencies: buffer-equal "0.0.1" minimist "^1.1.3" @@ -10095,40 +9365,30 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.1: +range-parser@^1.2.1, range-parser@~1.2.0, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== +raw-body@2.5.1, raw-body@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== dependencies: - bytes "3.1.0" - http-errors "1.7.2" + bytes "3.1.2" + http-errors "2.0.0" iconv-lite "0.4.24" unpipe "1.0.0" -raw-loader@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" - integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= - dependencies: - pify "^2.3.0" +read-cmd-shim@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.1.tgz#868c235ec59d1de2db69e11aec885bc095aea087" + integrity sha512-kEmDUoYf/CDy8yZbLTmhB1X9kkjf9Q80PCNsDMb7ufrGd6zZSQA1+UyjrO+pZm5K/S4OXCWJeiIt1JA8kAsa6g== read-installed@~4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" - integrity sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc= + integrity sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ== dependencies: debuglog "^1.0.1" read-package-json "^2.0.0" @@ -10139,14 +9399,22 @@ read-installed@~4.0.3: optionalDependencies: graceful-fs "^4.1.2" -read-package-json-fast@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.1.tgz#c767f6c634873ffb6bb73788191b65559734f555" - integrity sha512-bp6z0tdgLy9KzdfENDIw/53HWAolOVoQTRWXv7PUiqAo3YvvoUVeLr7RWPWq+mu7KUOu9kiT4DvxhUgNUBsvug== +read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== dependencies: json-parse-even-better-errors "^2.3.0" npm-normalize-package-bin "^1.0.1" +read-package-json-fast@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-3.0.1.tgz#de13ae1c591850534daf77e083e851f94af67733" + integrity sha512-8+HW7Yo+cjfF+md8DqsZHgats2mxf7gGYow/+2JjxrftoHFZz9v4dzd0EubzYbkNaLxrTVcnllHwklXN2+7aTQ== + dependencies: + json-parse-even-better-errors "^3.0.0" + npm-normalize-package-bin "^3.0.0" + read-package-json@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" @@ -10157,58 +9425,32 @@ read-package-json@^2.0.0: normalize-package-data "^2.0.0" npm-normalize-package-bin "^1.0.0" -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= +read-package-json@^5.0.0, read-package-json@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.2.tgz#b8779ccfd169f523b67208a89cc912e3f663f3fa" + integrity sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q== dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^2.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= +read-package-json@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.0.tgz#6a741841ad72a40e77a82b9c3c8c10e865bbc519" + integrity sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w== dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" + glob "^8.0.1" + json-parse-even-better-errors "^3.0.0" + normalize-package-data "^5.0.0" + npm-normalize-package-bin "^3.0.0" -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== +read@1, read@^1.0.7, read@~1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" + mute-stream "~0.0.4" readable-stream@2.3.0: version "2.3.0" @@ -10223,16 +9465,7 @@ readable-stream@2.3.0: string_decoder "~1.0.0" util-deprecate "~1.0.1" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.3, readable-stream@~2.3.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@~2.3.3, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -10245,7 +9478,16 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdir-scoped-modules@^1.0.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdir-scoped-modules@^1.0.0, readdir-scoped-modules@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== @@ -10255,157 +9497,92 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== dependencies: resolve "^1.1.6" -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - reflect-metadata@^0.1.13, reflect-metadata@^0.1.2: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regenerate-unicode-properties@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" - integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== dependencies: - regenerate "^1.4.0" + regenerate "^1.4.2" -regenerate@^1.4.0: +regenerate@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@0.13.7, regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== +regenerator-runtime@0.13.10, regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.4: + version "0.13.10" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" + integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" - integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== +regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== dependencies: "@babel/runtime" "^7.8.4" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regex-parser@^2.2.11: version "2.2.11" resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== -regexp.prototype.flags@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" + functions-have-names "^1.2.2" -regexpu-core@^4.7.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" - integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.2.0" - regjsgen "^0.5.1" - regjsparser "^0.6.4" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.2.0" - -regjsgen@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regjsparser@^0.6.4: - version "0.6.7" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.7.tgz#c00164e1e6713c2e3ee641f1701c4b7aa0a7f86c" - integrity sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ== +regexpu-core@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" + integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - request@2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" @@ -10432,7 +9609,7 @@ request@2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@2.88.2, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.87.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -10461,7 +9638,7 @@ request@2.88.2, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88 require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" @@ -10476,60 +9653,60 @@ require-main-filename@^2.0.0: requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-url-loader@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz#235e2c28e22e3e432ba7a5d4e305c59a58edfc08" - integrity sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ== - dependencies: - adjust-sourcemap-loader "3.0.0" - camelcase "5.3.1" - compose-function "3.0.3" - convert-source-map "1.7.0" - es6-iterator "2.0.3" - loader-utils "1.2.3" - postcss "7.0.21" - rework "1.0.1" - rework-visit "1.0.0" +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url-loader@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz#ee3142fb1f1e0d9db9524d539cfa166e9314f795" + integrity sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg== + dependencies: + adjust-sourcemap-loader "^4.0.0" + convert-source-map "^1.7.0" + loader-utils "^2.0.0" + postcss "^8.2.14" source-map "0.6.1" -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@1.22.1, resolve@^1.1.5, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" -resolve@1.20.0, resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== +resolve@~1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: - is-core-module "^2.2.0" path-parse "^1.0.6" -responselike@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" - integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== +resolve@~1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== dependencies: - lowercase-keys "^2.0.0" + is-core-module "^2.1.0" + path-parse "^1.0.6" + +resp-modifier@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/resp-modifier/-/resp-modifier-6.0.2.tgz#b124de5c4fbafcba541f48ffa73970f4aa456b4f" + integrity sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw== + dependencies: + debug "^2.2.0" + minimatch "^3.0.2" restore-cursor@^3.1.0: version "3.1.0" @@ -10539,53 +9716,25 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rework-visit@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" - integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo= - -rework@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" - integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc= - dependencies: - convert-source-map "^0.3.3" - css "^2.0.0" - -rfdc@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.2.0.tgz#9e9894258f48f284b43c3143c68070a4f373b949" - integrity sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA== - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" @@ -10594,7 +9743,7 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.3: +rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -10604,17 +9753,10 @@ rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.3: rimraf@~2.4.0: version "2.4.5" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" - integrity sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto= + integrity sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ== dependencies: glob "^6.0.1" -rimraf@~2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rollup-plugin-sourcemaps@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" @@ -10623,12 +9765,12 @@ rollup-plugin-sourcemaps@^0.6.3: "@rollup/pluginutils" "^3.0.9" source-map-resolve "^0.6.0" -rollup@^2.37.0: - version "2.39.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.39.0.tgz#be4f98c9e421793a8fec82c854fb567c35e22ab6" - integrity sha512-+WR3bttcq7zE+BntH09UxaW3bQo3vItuYeLsyk4dL2tuwbeSKJuvwiawyhEnvRdRgrII0Uzk00FpctHO/zB1kw== +rollup@^3.0.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.2.3.tgz#67d894c981ad50cc811779748e52c05742560c64" + integrity sha512-qfadtkY5kl0F5e4dXVdj2D+GtOdifasXHFMiL1SMf9ADQDv5Eti6xReef9FKj+iQPR2pvtqWna57s/PjARY4fg== optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" run-async@^2.4.0: version "2.4.1" @@ -10642,6 +9784,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rx@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" + integrity sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug== + rxjs@6.6.7: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -10649,67 +9796,81 @@ rxjs@6.6.7: dependencies: tslib "^1.9.0" -rxjs@^6.4.0, rxjs@^6.5.0, rxjs@^6.5.3: - version "6.6.3" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" - integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== +rxjs@^5.5.6: + version "5.5.12" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" + integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== dependencies: - tslib "^1.9.0" + symbol-observable "1.0.1" -rxjs@^6.6.6: - version "6.6.6" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.6.tgz#14d8417aa5a07c5e633995b525e1e3c0dec03b70" - integrity sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg== +rxjs@^7.5.5, rxjs@^7.5.6: + version "7.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" + integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== dependencies: - tslib "^1.9.0" + tslib "^2.1.0" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== dependencies: - ret "~0.1.10" + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-11.0.1.tgz#8672f896593466573b904f47693e0695368e38c9" - integrity sha512-Vp1LcP4slTsTNLEiDkTcm8zGN/XYYrZz2BZybQbliWA8eXveqA/AxsEjllQTpJbg2MzCsx/qNO48sHdZtOaxTw== +sass-loader@13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.1.0.tgz#e5b9acf14199a9bc6eaed7a0b8b23951c2cebf6f" + integrity sha512-tZS1RJQ2n2+QNyf3CCAo1H562WjL/5AM6Gi8YcPVVoNxQX8d19mx8E+8fRrMWsyc93ZL6Q8vZDSM0FHVTJaVnQ== dependencies: klona "^2.0.4" neo-async "^2.6.2" -sass@1.32.8: - version "1.32.8" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc" - integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ== +sass-loader@13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.2.0.tgz#80195050f58c9aac63b792fa52acb6f5e0f6bdc3" + integrity sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg== dependencies: - chokidar ">=2.0.0 <4.0.0" + klona "^2.0.4" + neo-async "^2.6.2" + +sass@1.55.0, sass@^1.55.0: + version "1.55.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.55.0.tgz#0c4d3c293cfe8f8a2e8d3b666e1cf1bff8065d1c" + integrity sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" -sass@^1.32.5: - version "1.32.7" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.7.tgz#632a9df2b85dc4b346977fcaf2d5e6f2b7039fd8" - integrity sha512-C8Z4bjqGWnsYa11o8hpKAuoyFdRhrSHcYjCr+XAWVPSIQqC8mp2f5Dx4em0dKYehPzg5XSekmCjqJnEZbIls9A== +sass@1.56.1: + version "1.56.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.1.tgz#94d3910cd468fd075fa87f5bb17437a0b617d8a7" + integrity sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ== dependencies: - chokidar ">=2.0.0 <4.0.0" + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" -"sauce-connect-proxy@https://saucelabs.com/downloads/sc-4.6.4-linux.tar.gz": +"sauce-connect-proxy@https://saucelabs.com/downloads/sc-4.8.1-linux.tar.gz": version "0.0.0" - resolved "https://saucelabs.com/downloads/sc-4.6.4-linux.tar.gz#992e2cb0d91e54b27a4f5bbd2049f3b774718115" + resolved "https://saucelabs.com/downloads/sc-4.8.1-linux.tar.gz#9c16682e4c9716734432789884f868212f95f563" saucelabs@^1.5.0: version "1.5.0" @@ -10718,64 +9879,41 @@ saucelabs@^1.5.0: dependencies: https-proxy-agent "^2.2.1" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: +sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^3.1.9: - version "3.1.11" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" - integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== - dependencies: - xmlchars "^2.1.1" - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.6.5, schema-utils@^2.7.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" + xmlchars "^2.2.0" -schema-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" - integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== +schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== dependencies: - "@types/json-schema" "^7.0.6" + "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" -seedrandom@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" - integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selenium-webdriver@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-3.5.0.tgz#9036c82874e6c0f5cbff0a0f18223bc31c99cb77" - integrity sha512-1bCZYRfDy7vsu1dkLrclTLvWPxSo6rOIkxZXvB2wnzeWkEoiTKpw612EUGA3jRZxPzAzI9OlxuULJV8ge1vVXQ== - dependencies: - jszip "^3.1.3" - rimraf "^2.5.4" - tmp "0.0.30" - xml2js "^0.4.17" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== selenium-webdriver@3.6.0, selenium-webdriver@^3.0.1: version "3.6.0" @@ -10787,21 +9925,23 @@ selenium-webdriver@3.6.0, selenium-webdriver@^3.0.1: tmp "0.0.30" xml2js "^0.4.17" -selfsigned@^1.10.8: - version "1.10.8" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" - integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== +selenium-webdriver@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.4.0.tgz#3f280504f6c0ac64a24b176304213b5a49ec2553" + integrity sha512-Du+/xfpvNi9zHAeYgXhOWN9yH0hph+cuX+hHDBr7d+SbtQVcfNJwBzLsbdHrB1Wh7MHXFuIkSG88A9TRRQUx3g== dependencies: - node-forge "^0.10.0" + jszip "^3.10.0" + tmp "^0.2.1" + ws ">=8.7.0" -semver-dsl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0" - integrity sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA= +selfsigned@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" + integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== dependencies: - semver "^5.3.0" + node-forge "^1" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -10811,22 +9951,17 @@ semver@5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@7.3.4, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -semver@7.3.5, semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== +semver@7.3.8, semver@^7.0.0, semver@^7.1.1, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@~7.3.0: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" @@ -10835,10 +9970,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== dependencies: debug "2.6.9" depd "~1.1.2" @@ -10847,24 +9982,43 @@ send@0.17.1: escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +send@0.18.0, send@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "2.0.1" -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" -serve-index@^1.9.1: +serve-index@1.9.1, serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== dependencies: accepts "~1.3.4" batch "0.6.1" @@ -10874,46 +10028,46 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.1" + send "0.18.0" + +server-destroy@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" + integrity sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ== -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-immediate-shim@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -10929,14 +10083,7 @@ shallow-clone@^3.0.0: shallow-copy@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" - integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" + integrity sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw== shebang-command@^2.0.0: version "2.0.0" @@ -10945,148 +10092,114 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@^0.8.3, shelljs@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" - integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" interpret "^1.0.0" rechoir "^0.6.2" -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - is-arrayish "^0.3.1" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slide@^1.1.3, slide@~1.1.3: +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +slide@~1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= + integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw== -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" +socket.io-adapter@~2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6" + integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== +socket.io-client@^4.4.1: + version "4.5.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.3.tgz#bed69209d001465b2fea650d2e95c1e82768ab5e" + integrity sha512-I/hqDYpQ6JKwtJOf5ikM+Qz+YujZPMEl6qBLhxiP0nX+TfXKhW4KZZG8lamrD6Y5ngjmYHreESVasVCgi5Kl3A== dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -socket.io-adapter@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz#edc5dc36602f2985918d631c1399215e97a1b527" - integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg== + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.2.3" + socket.io-parser "~4.2.0" -socket.io-parser@~4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" - integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== +socket.io-parser@~4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5" + integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g== dependencies: - "@types/component-emitter" "^1.2.10" - component-emitter "~1.3.0" + "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" -socket.io@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.1.tgz#905e3d4a3b37d8e7970e67a4a6eb81110a5778ba" - integrity sha512-7cBWdsDC7bbyEF6WbBqffjizc/H4YF1wLdZoOzuYfo2uMNSFjJKuQ36t0H40o9B20DO6p+mSytEd92oP4S15bA== +socket.io@^4.4.1: + version "4.5.3" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.3.tgz#44dffea48d7f5aa41df4a66377c386b953bc521c" + integrity sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg== dependencies: - "@types/cookie" "^0.4.0" - "@types/cors" "^2.8.8" - "@types/node" "^14.14.10" accepts "~1.3.4" base64id "~2.0.0" - debug "~4.3.1" - engine.io "~4.1.0" - socket.io-adapter "~2.1.0" - socket.io-parser "~4.0.3" - -sockjs-client@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add" - integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q== - dependencies: - debug "^3.2.6" - eventsource "^1.0.7" - faye-websocket "^0.11.3" - inherits "^2.0.4" - json3 "^3.3.3" - url-parse "^1.4.7" + debug "~4.3.2" + engine.io "~6.2.0" + socket.io-adapter "~2.4.0" + socket.io-parser "~4.2.0" -sockjs@^0.3.21: - version "0.3.21" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" - integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== dependencies: faye-websocket "^0.11.3" - uuid "^3.4.0" + uuid "^8.3.2" websocket-driver "^0.7.4" -socks-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" - integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== dependencies: - agent-base "6" - debug "4" - socks "^2.3.3" + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" -socks@^2.3.3: - version "2.5.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.5.1.tgz#7720640b6b5ec9a07d556419203baa3f0596df5f" - integrity sha512-oZCsJJxapULAYJaEYBSzMcz8m3jqgGrHaGhkmU/o/PQfFWYWxkAaA0UMGImb6s6tEXfKi959X6VJjMMQ3P6TTQ== +socks@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: - ip "^1.1.5" - smart-buffer "^4.1.0" + ip "^2.0.0" + smart-buffer "^4.2.0" sonic-boom@^1.0.2: version "1.4.1" @@ -11096,33 +10209,19 @@ sonic-boom@^1.0.2: atomic-sleep "^1.0.0" flatstr "^1.0.12" -source-list-map@^2.0.0, source-list-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-loader@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-1.1.3.tgz#7dbc2fe7ea09d3e43c51fd9fc478b7f016c1f820" - integrity sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA== - dependencies: - abab "^2.0.5" - iconv-lite "^0.6.2" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - source-map "^0.6.1" - whatwg-mimetype "^2.3.0" +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== +source-map-loader@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.1.tgz#72f00d05f5d1f90f80974eda781cbd7107c125f2" + integrity sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA== dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" + abab "^2.0.6" + iconv-lite "^0.6.3" + source-map-js "^1.0.2" source-map-resolve@^0.6.0: version "0.6.0" @@ -11132,10 +10231,10 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@0.5.19, source-map-support@^0.5.17, source-map-support@^0.5.5, source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== +source-map-support@0.5.21, source-map-support@^0.5.5, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -11155,27 +10254,22 @@ source-map-support@~0.4.0: dependencies: source-map "^0.5.6" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@0.7.3, source-map@^0.7.3, source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +source-map@0.7.4, source-map@^0.7.3, source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: +sourcemap-codec@^1.4.8: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== @@ -11211,9 +10305,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" - integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + version "3.0.12" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== spdx-ranges@^2.0.0: version "2.1.1" @@ -11230,9 +10324,9 @@ spdx-satisfies@^4.0.0: spdx-ranges "^2.0.0" spdx-satisfies@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/spdx-satisfies/-/spdx-satisfies-5.0.0.tgz#d740b8f14caeada36fb307629dee87146970a256" - integrity sha512-/hGhwh20BeGmkA+P/lm06RvXD94JduwNxtx/oX3B5ClPt1/u/m5MCaDNo1tV3Y9laLkQr/NRde63b9lLMhlNfw== + version "5.0.1" + resolved "https://registry.yarnpkg.com/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz#9feeb2524686c08e5f7933c16248d4fdf07ed6a6" + integrity sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw== dependencies: spdx-compare "^1.0.0" spdx-expression-parse "^3.0.0" @@ -11261,48 +10355,15 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -split2@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" - integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw== - dependencies: - through2 "^2.0.2" - -split2@^3.0.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" - integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== - dependencies: - readable-stream "^3.0.0" - -split@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -sprintf-js@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -11314,25 +10375,20 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -ssri@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" - integrity sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ== +ssri@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.0.tgz#1e34554cbbc4728f5290674264e21b64aaf27ca7" + integrity sha512-64ghGOpqW0k+jh7m5jndBGdVEoPikWwGQmBNN5ks6jyUSMymzHDTlnNHOvzp+6MmHOljr2MokUzvRksnTwG0Iw== dependencies: - safe-buffer "^5.1.1" + minipass "^3.1.1" -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== +ssri@^9.0.0, ssri@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== dependencies: minipass "^3.1.1" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - static-eval@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.1.0.tgz#a16dbe54522d7fa5ef1389129d813fd47b148014" @@ -11340,14 +10396,6 @@ static-eval@^2.0.0: dependencies: escodegen "^1.11.1" -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - static-module@^2.2.0: version "2.2.5" resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" @@ -11368,82 +10416,81 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + integrity sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg== + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== steno@^0.4.1: version "0.4.4" resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" - integrity sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs= + integrity sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w== dependencies: graceful-fs "^4.1.3" -streamroller@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" - integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ== - dependencies: - date-format "^2.1.0" - debug "^4.1.1" - fs-extra "^8.1.0" - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= +stream-throttle@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/stream-throttle/-/stream-throttle-0.1.3.tgz#add57c8d7cc73a81630d31cd55d3961cfafba9c3" + integrity sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ== dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" + commander "^2.2.0" + limiter "^1.0.5" -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== +streamroller@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.3.tgz#d95689a8c29b30d093525d0baffe6616fd62ca7e" + integrity sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w== dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + date-format "^4.0.14" + debug "^4.3.4" + fs-extra "^8.1.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" +string-argv@~0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" - integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" -string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" - integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" string_decoder@^1.1.1: version "1.3.0" @@ -11466,119 +10513,39 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: +strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - is-utf8 "^0.2.0" + ansi-regex "^5.0.1" strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - -style-loader@2.0.0: +strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" - integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -stylehacks@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.0.tgz#c49b0b2cf9917fe37dc030b96a4c34698b932933" - integrity sha512-QOWm6XivDLb+fqffTZP8jrmPmPITVChl2KCY2R05nsCWwLi3VGhCdVc3IVGNwd1zzTt1jPd67zIKjpQfxzQZeA== - dependencies: - browserslist "^4.16.0" - postcss-selector-parser "^6.0.4" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -stylus-loader@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-4.3.3.tgz#381bb6341272ac50bcdfd0b877707eac99b6b757" - integrity sha512-PpWB5PnCXUzW4WMYhCvNzAHJBjIBPMXwsdfkkKuA9W7k8OQFMl/19/AQvaWsxz2IptxUlCseyJ6TY/eEKJ4+UQ== - dependencies: - fast-glob "^3.2.4" - klona "^2.0.4" - loader-utils "^2.0.0" - normalize-path "^3.0.0" - schema-utils "^3.0.0" - -stylus@0.54.8, stylus@^0.54.8: - version "0.54.8" - resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" - integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== - dependencies: - css-parse "~2.0.0" - debug "~3.1.0" - glob "^7.1.6" - mkdirp "~1.0.4" - safer-buffer "^2.1.2" - sax "~1.2.4" - semver "^6.3.0" - source-map "^0.7.3" +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== supports-color@^5.3.0: version "5.5.0" @@ -11587,75 +10554,46 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" + has-flag "^4.0.0" -svgo@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.3.0.tgz#6b3af81d0cbd1e19c83f5f63cec2cb98c70b5373" - integrity sha512-fz4IKjNO6HDPgIQxu4IxwtubtbSfGEAJUq/IXyTPIkGhWck/faiiwfkvsB8LnBkKLvSoyNNIY6d13lZprJMc9Q== - dependencies: - "@trysound/sax" "0.1.1" - chalk "^4.1.0" - commander "^7.1.0" - css-select "^3.1.2" - css-tree "^1.1.2" - csso "^4.2.0" - stable "^0.1.8" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -symbol-observable@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-3.0.0.tgz#eea8f6478c651018e059044268375c408c15c533" - integrity sha512-6tDOXSHiVjuCaasQSWTmHUWn4PuG7qa3+1WT031yTc/swT7+rLiw3GOrFxaH1E3lLP09dH3bVuVDf2gK5rxG3Q== +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + integrity sha512-Kb3PrPYz4HanVF1LVGuAdW6LoVgIwjUYJGzFe7NDrBLCN4lsV/5J0MFurV+ygS4bRVwrCEt2c7MQ1R2a72oJDw== + +symbol-observable@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== -symbol-tree@^3.2.2: +symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -sync-rpc@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7" - integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw== - dependencies: - get-port "^3.1.0" - tapable@^2.1.1, tapable@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" - integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -tar-fs@^2.0.0: +tar-fs@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== @@ -11676,10 +10614,10 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^6.0.0, tar@^6.0.2, tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== +tar@^6.1.0, tar@^6.1.11, tar@^6.1.2, tar@^6.1.6: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -11688,58 +10626,26 @@ tar@^6.0.0, tar@^6.0.2, tar@^6.1.0: mkdirp "^1.0.3" yallist "^4.0.0" -temp@^0.9.0: - version "0.9.4" - resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620" - integrity sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA== - dependencies: - mkdirp "^0.5.1" - rimraf "~2.6.2" - -terser-webpack-plugin@4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" - integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - jest-worker "^26.5.0" - p-limit "^3.0.2" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.3.4" - webpack-sources "^1.4.3" - -terser-webpack-plugin@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" - integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== - dependencies: - jest-worker "^26.6.2" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.5.1" - -terser@5.6.1: - version "5.6.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c" - integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw== +terser-webpack-plugin@^5.1.3: + version "5.3.6" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" + integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" + "@jridgewell/trace-mapping" "^0.3.14" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.14.1" -terser@^5.3.4, terser@^5.5.1: - version "5.6.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2" - integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA== +terser@5.15.1, terser@^5.14.1: + version "5.15.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.1.tgz#8561af6e0fd6d839669c73b92bdd5777d870ed6c" + integrity sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" + source-map-support "~0.5.20" test-exclude@^6.0.0: version "6.0.0" @@ -11750,17 +10656,20 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-extensions@^1.0.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" - integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== - -text-table@0.2.0: +text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tfunk@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tfunk/-/tfunk-4.0.0.tgz#de9399feaf2060901d590b7faad80fcd5443077e" + integrity sha512-eJQ0dGfDIzWNiFNYFVjJ+Ezl/GmwHaFTBTjrtqNPW0S7cuVDBrZrmzUz6VkMeCR4DZFqhd4YtLwsw3i2wYHswQ== + dependencies: + chalk "^1.1.3" + dlv "^1.1.3" -through2@^2.0.0, through2@^2.0.2, through2@~2.0.3: +through2@^2.0.0, through2@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -11768,17 +10677,10 @@ through2@^2.0.0, through2@^2.0.2, through2@~2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through2@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" - integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== - dependencies: - readable-stream "3" - -through@2, "through@>=2.2.7 <3", through@X.X.X, through@^2.3.6, through@^2.3.8: +"through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== thunky@^1.0.2: version "1.1.0" @@ -11793,61 +10695,41 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tiny-inflate@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== +tiny-relative-date@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" + integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== + tmp@0.0.30: version "0.0.30" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed" - integrity sha1-ckGdSovn1s51FI/YsyTlk6cRwu0= + integrity sha512-HXdTB7lvMwcb55XFfrTM8CPr/IYREk4hVBFaQ4b/6nInrluSL86hfHm7vu0luYKCfyBZp2trCjpc8caC3vVM3w== dependencies: os-tmpdir "~1.0.1" -tmp@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== dependencies: - os-tmpdir "~1.0.2" + os-tmpdir "~1.0.2" + +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-readable-stream@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8" - integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w== - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" @@ -11856,37 +10738,20 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== +tough-cookie@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" + psl "^1.1.33" punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" tough-cookie@~2.4.3: version "2.4.3" @@ -11896,12 +10761,25 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: - punycode "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== tree-kill@1.2.2, tree-kill@^1.2.0: version "1.2.2" @@ -11913,157 +10791,114 @@ treeify@^1.1.0: resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - -trim-newlines@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" - integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== - -trim-off-newlines@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" - integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= - -ts-api-guardian@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/ts-api-guardian/-/ts-api-guardian-0.6.0.tgz#9f44cf9bad1db5de358ccca7b4e6fb1d2c7fe462" - integrity sha512-DVA+UgPI1TVRI7GNDG1XBxwGs+cKqJq1os00txr52TUxBPwsUWWT7f83VHWvPQvh7G2wj93U/dTUBeDq3FIDTg== - dependencies: - chalk "^2.3.1" - diff "^3.5.0" - minimist "^1.2.0" +treeverse@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-2.0.0.tgz#036dcef04bc3fd79a9b79a68d4da03e882d8a9ca" + integrity sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A== -ts-node@^9.0.0, ts-node@^9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" - integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== - dependencies: +"true-case-path@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" + integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== + +ts-node@^10.0.0: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" arg "^4.1.0" create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.17" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" - integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tsconfig-paths@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== -tslib@^1.10.0, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - -tslint-no-circular-imports@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/tslint-no-circular-imports/-/tslint-no-circular-imports-0.7.0.tgz#9df0a15654d66b172e0b7843eed073fa5ae99b5f" - integrity sha512-k3wxpeMC4ef40UbpfBVHEHIzKfNZq5/SCtAO1YjGsaNTklo+K53/TWLrym+poA65RJFDiYgYNWvkeIIkJNA0Vw== - -tslint-sonarts@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslint-sonarts/-/tslint-sonarts-1.9.0.tgz#feb593e92db328c0328b430b838adbe65d504de9" - integrity sha512-CJWt+IiYI8qggb2O/JPkS6CkC5DY1IcqRsm9EHJ+AxoWK70lvtP7jguochyNDMP2vIz/giGdWCfEM39x/I/Vnw== - dependencies: - immutable "^3.8.2" - -tslint@^6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" - integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.3" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.13.0" - tsutils "^2.29.0" +tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== tsscmp@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== -tsutils@2.27.2: - version "2.27.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7" - integrity sha512-qf6rmT84TFMuxAKez2pIfR8UCai49iQsfB7YWVjV1bKpy/d0PWT5rEOSM6La9PiHZ0k1RRZQiwVdVJfQ3BPHgg== - dependencies: - tslib "^1.8.1" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== +tsutils@3.21.0, tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== dependencies: safe-buffer "^5.0.1" tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== -typanion@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/typanion/-/typanion-3.3.0.tgz#92d949d8fb90ba90a152b97909255e5420bfc81d" - integrity sha512-e+54C4+ozFsTorFe50JNQlXlt4HVGQUvqul7VS0GbDfKlxh3aXbJY87Yu9IdtzvJWCyTgDX7q1PeMd3FH9zZqA== +typanion@^3.3.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/typanion/-/typanion-3.12.1.tgz#d33deb130aba23ef6f2a3c69e7fb28148dd9089a" + integrity sha512-3SJF/czpzqq6G3lprGFLa6ps12yb1uQ1EmitNnep2fDMNh1aO/Zbq9sWY+3lem0zYb2oHJnQWyabTGUZ+L1ScQ== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== dependencies: prelude-ls "~1.1.2" -type-fest@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642" - integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -12076,37 +10911,57 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== -type@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.2.0.tgz#3edd448793f517d8b9dd108b486a043f5befd91f" - integrity sha512-M/u37b4oSGlusaU8ZB96BfFPWQ8MbsZYXB+kXGMiDj6IKinkcNaQvmirBuWj8mAXqP6LYn1rQvbTYum3yPhaOA== +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== -typed-graphqlify@^2.3.0: - version "2.4.5" - resolved "https://registry.yarnpkg.com/typed-graphqlify/-/typed-graphqlify-2.4.5.tgz#e881f8a90911c32aec76a112e8e5077ead36e151" - integrity sha512-0iD2WkYmdxsTS2TGchnrY+vuSZHrTkj2i/Pa3UKTNPdrig5bV2f8UpGEfQ4Gq8pp3Vd9esqGIVgjC7hYR6OsOg== +typed-assert@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/typed-assert/-/typed-assert-1.0.9.tgz#8af9d4f93432c4970ec717e3006f33f135b06213" + integrity sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg== typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript@4.8.4, typescript@^4.6.2, typescript@~4.8.0: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@4.2.4, typescript@~4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +typescript@~4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +ua-parser-js@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" + integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== -ua-parser-js@^0.7.23: - version "0.7.24" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.24.tgz#8d3ecea46ed4f1f1d63ec25f17d8568105dc027c" - integrity sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw== +ua-parser-js@^0.7.30: + version "0.7.32" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.32.tgz#cd8c639cdca949e30fa68c44b7813ef13e36d211" + integrity sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw== uglify-js@^3.1.4: - version "3.12.8" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.8.tgz#a82e6e53c9be14f7382de3d068ef1e26e7d4aaf8" - integrity sha512-fvBeuXOsvqjecUtF/l1dwsrrf5y2BCUk9AOJGzGcm6tE7vegku5u/YvqjyDaAGr422PLoLnrxg3EnRvTqsdC1w== + version "3.17.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.3.tgz#f0feedf019c4510f164099e8d7e72ff2d7304377" + integrity sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" -unbzip2-stream@^1.3.3: +unbzip2-stream@1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== @@ -12114,88 +10969,75 @@ unbzip2-stream@^1.3.3: buffer "^5.2.1" through "^2.3.8" -unicode-canonical-property-names-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" - integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== -unicode-match-property-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" - integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== dependencies: - unicode-canonical-property-names-ecmascript "^1.0.4" - unicode-property-aliases-ecmascript "^1.0.4" + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" - integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== -unicode-property-aliases-ecmascript@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" - integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== unicode-trie@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" - integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= + integrity sha512-WgVuO0M2jDl7hVfbPgXv2LUrD81HM0bQj/bvLGiw6fJ4Zo8nNFnDrA0/hU2Te/wz6pjxCm5cxJwtLjo2eyV51Q== dependencies: pako "^0.2.5" tiny-inflate "^1.0.0" -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + unique-slug "^3.0.0" -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== +unique-filename@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" + integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g== dependencies: - unique-slug "^2.0.0" + unique-slug "^4.0.0" -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== dependencies: imurmurhash "^0.1.4" -universal-user-agent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-3.0.0.tgz#4cc88d68097bffd7ac42e3b7c903e7481424b4b9" - integrity sha512-T3siHThqoj5X0benA5H0qcDnrKGXzU8TKoX15x/tQHw1hQBvIEBHjxQ2klizYsqBOO/Q+WuxoQUihadeeqDnoA== +unique-slug@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" + integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ== dependencies: - os-name "^3.0.0" - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + imurmurhash "^0.1.4" universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + unix-crypt-td-js@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz#4912dfad1c8aeb7d20fa0a39e4c31918c1d5d5dd" @@ -12204,25 +11046,15 @@ unix-crypt-td-js@1.1.4: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + escalade "^3.1.1" + picocolors "^1.0.0" uri-js@^4.2.2: version "4.4.1" @@ -12232,86 +11064,63 @@ uri-js@^4.2.2: punycode "^2.1.0" urijs@^1.19.1: - version "1.19.6" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.6.tgz#51f8cb17ca16faefb20b9a31ac60f84aa2b7c870" - integrity sha512-eSXsXZ2jLvGWeLYlQA3Gh36BcjF+0amo92+wHPyN1mdR8Nxf75fuEuYTd9c0a+m/vhCjRK0ESlE9YNLW+E1VEw== + version "1.19.11" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc" + integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ== -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -url-parse@^1.4.3, url-parse@^1.4.7: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" -url-template@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" - integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util-extend@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" - integrity sha1-p8IW0mdUUWljeztu3GypEZ4v+T8= - -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" + integrity sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA== utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@8.3.2: +uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^3.3.2, uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-to-istanbul@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz#5b95cef45c0f83217ec79f8fc7ee1c8b486aee07" - integrity sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g== + version "7.1.2" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" + integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" source-map "^0.7.3" -validate-npm-package-license@^3.0.1: +validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== @@ -12319,104 +11128,110 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validate-npm-package-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= +validate-npm-package-name@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz#fe8f1c50ac20afdb86f177da85b3600f0ac0d747" + integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== + dependencies: + builtins "^5.0.0" + +validate-npm-package-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz#f16afd48318e6f90a1ec101377fa0384cfc8c713" + integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ== dependencies: - builtins "^1.0.3" + builtins "^5.0.0" -validator@13.5.2: - version "13.5.2" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.5.2.tgz#c97ae63ed4224999fb6f42c91eaca9567fe69a46" - integrity sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ== +validator@13.7.0, validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vendors@^1.0.0, vendors@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -verdaccio-audit@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/verdaccio-audit/-/verdaccio-audit-10.0.0.tgz#d3304d923c7f2c28c173a02425208c941f25217b" - integrity sha512-Epsh+C7ZEdq39PR9QeDBTWktbeqc0zOQjMzWte6Ut5Jh6fPLZzxGF8VK8O67B6mnTwLvGy50A1aPVM97Ysh5Rw== +verdaccio-audit@10.2.3: + version "10.2.3" + resolved "https://registry.yarnpkg.com/verdaccio-audit/-/verdaccio-audit-10.2.3.tgz#a0746541a3bc733174775f75961a9102f551d6b6" + integrity sha512-mXOT6EiB9hK5dMjRTtJlL+hu2YswXuGSw28xOAPyZLWLTASDJy6Zs++o4P/6FyQ03yB0peK2KX7gHZ7APGZk2Q== dependencies: - express "4.17.1" - request "2.88.2" + body-parser "1.20.1" + express "4.18.2" + https-proxy-agent "5.0.1" + node-fetch "2.6.7" verdaccio-auth-memory@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/verdaccio-auth-memory/-/verdaccio-auth-memory-10.0.0.tgz#057f7f94e96d21c69b1ba7983c00fe8cb7072fef" - integrity sha512-pl6WDwYUNetGvFT6Veh3LuNpWLqxuDBC3i1URvuI+biIfAeqgr2M07ICN9p1cNlyWKdcEC/YILqCYg9aE/KwsQ== + version "10.2.0" + resolved "https://registry.yarnpkg.com/verdaccio-auth-memory/-/verdaccio-auth-memory-10.2.0.tgz#97eaa22fe9f4d0536469a9a16ba33817ba65df3d" + integrity sha512-HP8LHdNpHVFO4isL7VR8pNPoRzBlJUKQlNqma5xBPtSRKvKEc9Co3D6Sg2y9NofD8Yr1Q3dMBDDRtx8dxs9xZQ== dependencies: - "@verdaccio/commons-api" "^10.0.0" + "@verdaccio/commons-api" "10.2.0" -verdaccio-htpasswd@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/verdaccio-htpasswd/-/verdaccio-htpasswd-10.0.0.tgz#7a7f44e8ed4db40c53deef0f5101f2a16dce4ff1" - integrity sha512-3TKwiLwl8/fbaTDawHvjSYcsyMmdARg58keP/1plv74x+Jw0sC66HbbRwQ/tPO5mqoG0UwoWW+lkO8h/OiWi9w== +verdaccio-htpasswd@10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/verdaccio-htpasswd/-/verdaccio-htpasswd-10.5.1.tgz#d49e13dec82d17a1f6aa491285e914e3535467e9" + integrity sha512-DCUOITs+Ta4Hep429BjopYrUw9hEJsJ1mbMP0l9Glan1S2YvTPanhtm5Ahw/joljUlt3xawKz9Gmt1QJujfMew== dependencies: - "@verdaccio/file-locking" "^10.0.0" - apache-md5 "1.1.2" + "@verdaccio/file-locking" "10.3.0" + apache-md5 "1.1.8" bcryptjs "2.4.3" - http-errors "1.8.0" + http-errors "2.0.0" unix-crypt-td-js "1.1.4" -verdaccio@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/verdaccio/-/verdaccio-5.0.1.tgz#d9d598fa7a1256e2cc1b2be8b417716d75771870" - integrity sha512-Qn5PZhXi4JBHUQZZgNS5s1V7CtmORkFIyZ8kLqccoxOwLObYAv3k4LthpTosoRBEC1FGqbg+HTHR0OnUDj26Fw== - dependencies: - "@verdaccio/commons-api" "10.0.0" - "@verdaccio/local-storage" "10.0.1" - "@verdaccio/readme" "10.0.0" - "@verdaccio/streams" "10.0.0" - "@verdaccio/ui-theme" "3.0.1" +verdaccio@5.16.3: + version "5.16.3" + resolved "https://registry.yarnpkg.com/verdaccio/-/verdaccio-5.16.3.tgz#441aeffb55453905f6acfcb03ce7aa228aab77fa" + integrity sha512-2MWGcInH4wR1zSWQpsr51sAZjOzAMvtbi5IrqD4+1VCU2VB1VI5kAwte+ic8DA09thX1xaKGQgOsjMdT6p3jNQ== + dependencies: + "@verdaccio/commons-api" "10.2.0" + "@verdaccio/local-storage" "10.3.1" + "@verdaccio/readme" "10.4.2" + "@verdaccio/streams" "10.2.0" + "@verdaccio/ui-theme" "6.0.0-6-next.50" JSONStream "1.3.5" - async "3.2.0" - body-parser "1.19.0" - clipanion "3.0.0-rc.11" + async "3.2.4" + body-parser "1.20.1" + clipanion "3.1.0" compression "1.7.4" cookies "0.8.0" cors "2.8.5" - dayjs "1.10.4" - debug "^4.3.1" - envinfo "7.7.4" - express "4.17.1" - fast-safe-stringify "^2.0.7" + dayjs "1.11.6" + debug "^4.3.4" + envinfo "7.8.1" + eslint-import-resolver-node "0.3.6" + express "4.18.2" + express-rate-limit "5.5.1" + fast-safe-stringify "2.1.1" handlebars "4.7.7" - http-errors "1.8.0" - js-yaml "4.0.0" + http-errors "2.0.0" + js-yaml "4.1.0" jsonwebtoken "8.5.1" - kleur "4.1.4" + kleur "4.1.5" lodash "4.17.21" - lru-cache "6.0.0" + lru-cache "7.14.0" lunr-mutable-indexes "2.3.2" - marked "2.0.1" + marked "4.2.2" memoizee "0.4.15" - mime "2.5.2" - minimatch "3.0.4" + mime "3.0.0" + minimatch "5.1.0" mkdirp "1.0.4" mv "2.1.1" - pino "6.11.2" + pino "6.14.0" pkginfo "0.4.1" - prettier-bytes "^1.0.3" - pretty-ms "^5.0.0" + prettier-bytes "^1.0.4" + pretty-ms "^7.0.1" request "2.88.0" - semver "7.3.4" - validator "13.5.2" - verdaccio-audit "10.0.0" - verdaccio-htpasswd "10.0.0" + semver "7.3.7" + validator "13.7.0" + verdaccio-audit "10.2.3" + verdaccio-htpasswd "10.5.1" verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" @@ -12430,28 +11245,31 @@ vlq@^0.2.2: void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= + integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== -w3c-hr-time@^1.0.1: +w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: browser-process-hrtime "^1.0.0" -w3c-xmlserializer@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" - integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== dependencies: - domexception "^1.0.1" - webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -watchpack@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" - integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -12463,10 +11281,10 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -wcwidth@^1.0.1: +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== dependencies: defaults "^1.0.3" @@ -12495,169 +11313,146 @@ webdriver-manager@^12.1.7: semver "^5.3.0" xml2js "^0.4.17" -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webpack-dev-middleware@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-4.1.0.tgz#f0c1f12ff4cd855b3b5eec89ee0f69bcc5336364" - integrity sha512-mpa/FY+DiBu5+r5JUIyTCYWRfkWgyA3/OOE9lwfzV9S70A4vJYLsVRKj5rMFEsezBroy2FmPyQ8oBRVW8QmK1A== - dependencies: - colorette "^1.2.1" - mem "^8.0.0" - memfs "^3.2.0" - mime-types "^2.1.28" - range-parser "^1.2.1" - schema-utils "^3.0.0" +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== -webpack-dev-middleware@^3.7.2: - version "3.7.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" - integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== - dependencies: - memory-fs "^0.4.1" - mime "^2.4.4" - mkdirp "^0.5.1" - range-parser "^1.2.1" - webpack-log "^2.0.0" +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-dev-server@3.11.2: - version "3.11.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708" - integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ== +webpack-dev-middleware@5.3.3, webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== dependencies: - ansi-html "0.0.7" - bonjour "^3.5.0" - chokidar "^2.1.8" + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@4.11.1: + version "4.11.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5" + integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.1" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.8" - semver "^6.3.0" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" serve-index "^1.9.1" - sockjs "^0.3.21" - sockjs-client "^1.5.0" + sockjs "^0.3.24" spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" - -webpack-log@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" - integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== - dependencies: - ansi-colors "^3.0.0" - uuid "^3.3.2" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" -webpack-merge@5.7.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" - integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== +webpack-merge@5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" + integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== dependencies: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-sources@^1.3.0, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-sources@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" - integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== - dependencies: - source-list-map "^2.0.1" - source-map "^0.6.1" +webpack-sources@^3.0.0, webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack-subresource-integrity@1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.5.2.tgz#e40b6578d3072e2d24104975249c52c66e9a743e" - integrity sha512-GBWYBoyalbo5YClwWop9qe6Zclp8CIXYGIz12OPclJhIrSplDxs1Ls1JDMH8xBPPrg1T6ISaTW9Y6zOrwEiAzw== - dependencies: - webpack-sources "^1.3.0" - -webpack@5.32.0: - version "5.32.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.32.0.tgz#f013932d778dad81bd51292d0ea00865056dc22c" - integrity sha512-jB9PrNMFnPRiZGnm/j3qfNqJmP3ViRzkuQMIf8za0dgOYvSLi/cgA+UEEGvik9EQHX1KYyGng5PgBTTzGrH9xg== - dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.46" - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/wasm-edit" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - acorn "^8.0.4" +webpack-subresource-integrity@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz#8b7606b033c6ccac14e684267cb7fb1f5c2a132a" + integrity sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q== + dependencies: + typed-assert "^1.0.8" + +webpack@5.74.0: + version "5.74.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980" + integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.7.1" + acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.7.0" - es-module-lexer "^0.4.0" - eslint-scope "^5.1.1" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" - json-parse-better-errors "^1.0.2" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.0.0" + schema-utils "^3.1.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.1" - watchpack "^2.0.0" - webpack-sources "^2.1.1" - -webpack@^5: - version "5.31.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.31.0.tgz#fab61d0be896feca4af87bdad5c18815c0d63455" - integrity sha512-3fUfZT/FUuThWSSyL32Fsh7weUUfYP/Fjc/cGSbla5KiSo0GtI1JMssCRUopJTvmLjrw05R2q7rlLtiKdSzkzQ== - dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.46" - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/wasm-edit" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - acorn "^8.0.4" + terser-webpack-plugin "^5.1.3" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +webpack@5.75.0: + version "5.75.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" + integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.7.1" + acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.7.0" - es-module-lexer "^0.4.0" - eslint-scope "^5.1.1" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" - json-parse-better-errors "^1.0.2" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.0.0" + schema-utils "^3.1.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.1" - watchpack "^2.0.0" - webpack-sources "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.4.0" + webpack-sources "^3.2.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" @@ -12673,7 +11468,7 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: +whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== @@ -12681,30 +11476,49 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: iconv-lite "0.4.24" whatwg-fetch@>=0.10.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868" - integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A== + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== -whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: +whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== -which@^1.2.1, which@^1.2.9: +which@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -12718,26 +11532,26 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== +which@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-3.0.0.tgz#a9efd016db59728758a390d23f1687b6e8f59f8e" + integrity sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== dependencies: - string-width "^1.0.2 || 2" + string-width "^1.0.2 || 2 || 3 || 4" wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -windows-release@^3.1.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" - integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== - dependencies: - execa "^1.0.0" - -word-wrap@~1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -12745,16 +11559,7 @@ word-wrap@~1.2.3: wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== wrap-ansi@^6.2.0: version "6.2.0" @@ -12777,19 +11582,30 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== +write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: - async-limiter "~1.0.0" + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@8.9.0, ws@>=8.7.0, ws@^8.4.2: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" + integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== -ws@^7.0.0, ws@^7.2.3, ws@~7.4.2: - version "7.4.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd" - integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA== +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +ws@~8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" + integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== xhr2@^0.2.0: version "0.2.1" @@ -12814,22 +11630,22 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmlchars@^2.1.1: +xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmldom@^0.1.22: - version "0.1.31" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" - integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xxhashjs@0.2.2: +xxhashjs@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw== @@ -12837,14 +11653,14 @@ xxhashjs@0.2.2: cuint "^0.2.2" y18n@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" - integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== y18n@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" - integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" @@ -12852,17 +11668,14 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^1.10.0, yaml@^1.5.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@21.1.1, yargs-parser@^21.0.0, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs-parser@^18.1.2: version "18.1.3" @@ -12872,31 +11685,36 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.0.0: - version "20.2.6" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20" - integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA== +yargs-parser@^20.0.0, yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^20.2.2, yargs-parser@^20.2.3: - version "20.2.5" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.5.tgz#5d37729146d3f894f39fc94b6796f5b239513186" - integrity sha512-jYRGS3zWy20NtDtK2kBgo/TlAoy5YUuhD9/LZ7z7W4j1Fdw2cqD0xEEclf8fxc8xjD6X5Qr+qQQwCEsP8iRiYg== +yargs@17.1.1: + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== + 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" -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@17.6.2: + version "17.6.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" yargs@^15.3.1: version "15.4.1" @@ -12915,7 +11733,7 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.0.0, yargs@^16.1.1, yargs@^16.2.0: +yargs@^16.0.0, yargs@^16.1.1: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -12928,10 +11746,23 @@ yargs@^16.0.0, yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.0.0, yargs@^17.2.1, yargs@^17.3.1: + version "17.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" + integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g== + 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.0.0" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" @@ -12946,14 +11777,20 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zone.js@^0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.3.tgz#255a6313174731cc014d63233ef04fd9858da375" - integrity sha512-Y4hTHoh4VcxU5BDGAqEoOnOiyT254w6CiHtpQxAJUSMZPyVgdbKf+5R7Mwz6xsPhMIeBXk5rTopRZDpjssTCUg== +z-schema@~5.0.2: + version "5.0.4" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.4.tgz#ecad8bc5ef3283ae032d603286386cfb1380cce5" + integrity sha512-gm/lx3hDzJNcLwseIeQVm1UcwhWIKpSB4NqH89pTBtFns4k/HDHudsICtvG05Bvw/Mv3jMyk700y5dadueLHdA== dependencies: - tslib "^2.0.0" + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.7.0" + optionalDependencies: + commander "^2.20.3" -zone.js@~0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.3.tgz#3e5e4da03c607c9dcd92e37dd35687a14a140c16" - integrity sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg== +zone.js@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.12.0.tgz#a4a6e5fab6d34bd37d89c77e89ac2e6f4a3d2c30" + integrity sha512-XtC+I5dXU14HrzidAKBNMqneIVUykLEAA1x+v4KVrd6AUPWlwYORF8KgsVqvgdHiKZ4BkxxjvYi/ksEixTPR0Q== + dependencies: + tslib "^2.3.0"