diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 000000000000..92e60820829a --- /dev/null +++ b/.bazelrc @@ -0,0 +1,181 @@ +# Disable NG CLI TTY mode +build --action_env=NG_FORCE_TTY=false + +# Required by `rules_ts`. +common --@aspect_rules_ts//ts:skipLibCheck=always +common --@aspect_rules_ts//ts:default_to_tsc_transpiler + +# Make TypeScript compilation fast, by keeping a few copies of the compiler +# running as daemons, and cache SourceFile AST's to reduce parse time. +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 + +# Frozen lockfile +common --lockfile_mode=error + +############################### +# Filesystem interactions # +############################### + +# Create symlinks in the project: +# - dist/bin for outputs +# - dist/testlogs, dist/genfiles +# - bazel-out +# NB: bazel-out should be excluded from the editor configuration. +# The checked-in /.vscode/settings.json does this for VSCode. +# Other editors may require manual config to ignore this directory. +# In the past, we saw a problem where VSCode traversed a massive tree, opening file handles and +# eventually a surprising failure with auto-discovery of the C++ toolchain in +# MacOS High Sierra. +# See https://github.com/bazelbuild/bazel/issues/4603 +build --symlink_prefix=dist/ + +# Turn off legacy 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 +# https://github.com/bazelbuild/bazel/issues/7026 for more details. +# This flag is needed to so that the bazel cache is not invalidated +# when running bazel via `pnpm bazel`. +# See https://github.com/angular/angular/issues/27514. +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 +common --incompatible_allow_tags_propagation + +# Ensure sandboxing is enabled even for exclusive tests +test --incompatible_exclusive_test_sandboxed + +############################### +# Saucelabs support # +# Turn on these settings with # +# --config=saucelabs # +############################### + +# Expose SauceLabs environment to actions +# These environment variables are needed by +# web_test_karma to run on Saucelabs +test:saucelabs --action_env=SAUCE_USERNAME +test:saucelabs --action_env=SAUCE_ACCESS_KEY +test:saucelabs --action_env=SAUCE_READY_FILE +test:saucelabs --action_env=SAUCE_PID_FILE +test:saucelabs --action_env=SAUCE_TUNNEL_IDENTIFIER +test:saucelabs --define=KARMA_WEB_TEST_MODE=SL_REQUIRED + +############################### +# Release support # +# Turn on these settings with # +# --config=release # +############################### + +# 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="pnpm -s ng-dev release build-env-stamp --mode=release" +build:release --stamp + +build:snapshot --workspace_status_command="pnpm -s ng-dev release build-env-stamp --mode=snapshot" +build:snapshot --stamp +build:snapshot --//:enable_snapshot_repo_deps + +build:e2e --workspace_status_command="pnpm -s ng-dev release build-env-stamp --mode=release" +build:e2e --stamp +test:e2e --test_timeout=3600 --experimental_ui_max_stdouterr_bytes=2097152 + +# Retry in the event of flakes +test:e2e --flaky_test_attempts=2 + +build:local --//:enable_package_json_tar_deps + +############################### +# Output # +############################### + +# A more useful default output mode for bazel query +# Prints eg. "ng_module rule //foo:bar" rather than just "//foo:bar" +query --output=label_kind + +# By default, failing tests don't print any output, it goes to the log file +test --test_output=errors +################################ +# Remote Execution Setup # +################################ + +# Use the Angular team internal GCP instance for remote execution. +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 +build:remote --define=EXECUTOR=remote + +# Setup the remote build execution servers. +build:remote --remote_cache=remotebuildexecution.googleapis.com +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 provided by the shared dev-infra package and targets k8 remote containers. +build:remote --extra_execution_platforms=@devinfra//bazel/remote-execution:platform_with_network +build:remote --host_platform=@devinfra//bazel/remote-execution:platform_with_network +build:remote --platforms=@devinfra//bazel/remote-execution:platform_with_network + +# Set remote caching settings +build:remote --remote_accept_cached=true +build:remote --remote_upload_local_results=false + +# Force remote executions to consider the entire run as linux. +# This is required for OSX cross-platform RBE. +build:remote --cpu=k8 +build:remote --host_cpu=k8 + +# Set up authentication mechanism for RBE +build:remote --google_default_credentials + +# Use HTTP remote cache +build:remote-cache --remote_cache=https://storage.googleapis.com/angular-team-cache +build:remote-cache --remote_accept_cached=true +build:remote-cache --remote_upload_local_results=false +build:remote-cache --google_default_credentials + +# Additional flags added when running a "trusted build" with additional access +build:trusted-build --remote_upload_local_results=true + +# Fixes issues with browser archives and files with spaces. Could be +# removed in Bazel 8 when Bazel runfiles supports spaces. +build --experimental_inprocess_symlink_creation + +#################################################### +# rules_js specific flags +#################################################### + +# TODO(josephperrott): investigate if this can be removed eventually. +# Prevents the npm package extract from occuring or caching on RBE which overwhelms our quota +build --modify_execution_info=NpmPackageExtract=+no-remote + +# Allow the Bazel server to check directory sources for changes. `rules_js` previously +# heavily relied on this, but still uses directory "inputs" in some cases. +# See: https://github.com/aspect-build/rules_js/issues/1408. +startup --host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1 + +#################################################### +# User bazel configuration +# NOTE: This needs to be the *last* entry in the config. +#################################################### + +# Load any settings which are specific to the current user. Needs to be *last* statement +# in this config, as the user configuration should be able to overwrite flags from this file. +try-import .bazelrc.user diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 000000000000..6d2890793d47 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.5.0 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..c0a70d2acb14 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# https://editorconfig.org + +root = true + +[*.ts] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +spaces_around_brackets = inside +trim_trailing_whitespace = true +quote_type = single + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..de32b85d6693 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# JS and TS files must always use LF for tools to work +*.js eol=lf +*.ts eol=lf +*.json eol=lf +*.css eol=lf +*.scss eol=lf +*.less eol=lf +*.html eol=lf +*.svg eol=lf 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.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/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..898698af3906 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +blank_issues_enabled: false +contact_links: + - name: Docs or angular.dev issue report + url: https://github.com/angular/angular/issues/new + about: Report an issue in Angular's documentation or angular.dev application + - name: Security issue disclosure + url: https://angular.dev/best-practices/security + 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 new file mode 100644 index 000000000000..1237bc279e11 --- /dev/null +++ b/.github/SAVED_REPLIES.md @@ -0,0 +1,101 @@ +# Saved Responses for Angular CLI's Issue Tracker + +The following are canned responses that the Angular CLI team should use to close issues on our issue tracker that fall into the listed resolution categories. + +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/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/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](https://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/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. + +Angular CLI always runs *some* static analysis, even in JIT mode, in order to discover lazy-loaded routes. +This may cause a lot of static analysis errors to surface when importing your project into the CLI, or upgrading for older versions where we didn't run this kind of analysis. + +Below are good resources on how to debug these errors: +- https://gist.github.com/chuckjaz/65dcc2fd5f4f5463e492ed0cb93bca60 +- https://github.com/rangle/angular-2-aot-sandbox#aot-dos-and-donts + +If your problem still persists, it might be a bug with the Angular Compiler itself. +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. + +**It is your responsibility as a library consumer to use lockfiles**. No one wants to do a release with bugs but it sometimes happens, and the best we can do is to fix it as fast as possible with a new release. When you have a couple of thousand total dependencies it is only a matter of time until one of them has a bad release. +``` diff --git a/.github/_CODEOWNERS.tmp b/.github/_CODEOWNERS.tmp new file mode 100644 index 000000000000..3e79d395914f --- /dev/null +++ b/.github/_CODEOWNERS.tmp @@ -0,0 +1,17 @@ +/packages/_/ @hansl @clydin +/packages/angular/pwa @hansl @Brocco +/packages/angular_devkit/architect/ @filipesilva @hansl +/packages/angular_devkit/architect_cli/ @filipesilva @hansl +/packages/angular_devkit/build_angular/ @filipesilva @clydin +/packages/angular_devkit/build_ng_packagr/ @filipesilva @clydin +/packages/angular_devkit/build_optimizer/ @filipesilva @clydin +/packages/angular_devkit/core/ @hansl @clydin +/packages/angular_devkit/schematics/ @hansl @Brocco +/packages/angular_devkit/schematics_cli/ @hansl @Brocco +/packages/ngtools/webpack/ @hansl @filipesilva @clydin +/packages/schematics/angular/ @hansl @Brocco +/packages/schematics/package_update/ @hansl @Brocco +/packages/schematics/schematics/ @hansl @Brocco +/packages/schematics/update/ @hansl @Brocco +/rules/ @hansl @clydin +/scripts/ @hansl @clydin diff --git a/.github/angular-robot.yml b/.github/angular-robot.yml new file mode 100644 index 000000000000..3e3a56a25603 --- /dev/null +++ b/.github/angular-robot.yml @@ -0,0 +1,92 @@ +# Configuration for angular-robot + +# options for the merge plugin +merge: + # the status will be added to your pull requests + status: + # set to true to disable + disabled: true + # the name of the status + context: 'ci/angular: merge status' + # text to show when all checks pass + successText: 'All checks passed!' + # text to show when some 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. + + Please help to unblock it by resolving these conflicts. Thanks! + + # label to monitor + mergeLabel: 'action: merge' + + # list of checks that will determine if the merge label can be added + checks: + # whether the PR shouldn't have a conflict with the base branch + noConflict: true + # whether the PR should have all reviews completed. + 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: *' + + # 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' + + # list of PR statuses that need to be successful + # 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: + + {{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: 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: + - - 'area: *' + # arrays of labels that determine if an issue has been fully triaged + l2TriageLabels: + - - 'type: bug/fix' + - 'severity*' + - 'freq*' + - 'area: *' + - - 'type: feature' + - 'area: *' + - - 'type: refactor' + - 'area: *' + - - 'type: RFC / Discussion / question' + - 'area: *' + - - 'type: docs' + - 'area: *' + +# Size checking +size: + # 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/codeql/config.yml b/.github/codeql/config.yml new file mode 100644 index 000000000000..ad81a268eda4 --- /dev/null +++ b/.github/codeql/config.yml @@ -0,0 +1,8 @@ +name: 'Angular CLI CodeQL config' + +query-filters: + # TODO(josephperrott): reevaluate if these can be reenabled. + - exclude: + id: js/bad-code-sanitization + - exclude: + id: js/regex-injection diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..a01353b6bcf0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 + +updates: + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'daily' + commit-message: + prefix: 'build' + labels: + - 'area: build & ci' + - 'target: patch' + - 'action: merge' + # Disable version updates + # This does not affect security updates + open-pull-requests-limit: 0 diff --git a/.github/shared-actions/windows-bazel-test/action.yml b/.github/shared-actions/windows-bazel-test/action.yml new file mode 100644 index 000000000000..7a5cf7c0297b --- /dev/null +++ b/.github/shared-actions/windows-bazel-test/action.yml @@ -0,0 +1,42 @@ +name: Native Windows Bazel E2E test +description: Runs an Angular CLI e2e Bazel test on native Windows +author: Angular + +inputs: + test_target_name: + description: E2E test target name. + required: true + e2e_temp_dir: + description: 'The temporary directory path for E2E tests.' + required: false + # Use D:\\ by default as it's much faster + # See: https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows + default: 'D:\\tmp_dir' + +runs: + using: 'composite' + steps: + - name: Set up temp directory + shell: bash + run: | + mkdir ${{ inputs.e2e_temp_dir }} + + - name: Convert symlinks for Windows host + shell: pwsh + run: | + $runfiles_dir = "./dist/bin/tests/${{inputs.test_target_name}}_/${{inputs.test_target_name}}.bat.runfiles" + + # Needed for resolution because Aspect/Bazel looks for repositories at `/external`. + # TODO(devversion): consult with Aspect on why this is needed. + Set-Location -Path "${runfiles_dir}\_main" + New-Item -ItemType SymbolicLink -Path "external" -Target ".." + + - name: Run CLI E2E tests + shell: bash + env: + BAZEL_BINDIR: '.' + E2E_TEMP: ${{ inputs.e2e_temp_dir }} + run: | + node ./scripts/windows-testing/parallel-executor.mjs \ + "./dist/bin/tests/${{ inputs.test_target_name }}_/${{ inputs.test_target_name }}.bat.runfiles" \ + ${{ inputs.test_target_name }} 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..a30031ce31fd --- /dev/null +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -0,0 +1,22 @@ +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 + if: github.event.repository.fork == false + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - uses: angular/dev-infra/github-actions/branch-manager@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..000b239cd7a0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,254 @@ +name: CI + +on: + push: + branches: + - main + - '[0-9]+.[0-9]+.x' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Generate JSON schema types + # Schema types are required to correctly lint the TypeScript code + run: pnpm run build-schema + - name: Run ESLint + run: pnpm lint --cache-strategy content + - name: Validate NgBot Configuration + run: pnpm ng-dev ngbot verify + - name: Validate Circular Dependencies + run: pnpm ts-circular-deps check + - name: Run Validation + run: pnpm admin validate + - name: Check tooling setup + run: pnpm check-tooling-setup + + build: + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Build release targets + run: pnpm ng-dev release build + + test: + needs: build + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Run module and package tests + run: pnpm bazel test -- //... -//tests/... + + e2e: + needs: test + strategy: + fail-fast: false + matrix: + node: [20, 22, 24] + subset: [esbuild, webpack] + shard: [0, 1, 2, 3, 4, 5] + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Run CLI E2E tests + run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }} + + build-e2e-windows: + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Build E2E tests for Windows on Linux + run: | + pnpm bazel build \ + --config=e2e \ + //tests:e2e.webpack_node22 \ + //tests:e2e.esbuild_node22 \ + --platforms=tools:windows_x64 + - name: Store built Windows E2E tests + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: win-e2e-build-artifacts + path: | + dist/bin/tests/** + !**/node_modules/** + retention-days: 1 + if-no-files-found: 'error' + + e2e-windows: + needs: build-e2e-windows + strategy: + fail-fast: false + matrix: + node: [22] + subset: [esbuild, webpack] + shard: [0, 1, 2, 3, 4, 5] + runs-on: windows-2025 + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Download built Windows E2E tests + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: win-e2e-build-artifacts + path: dist/bin/tests/ + - name: Run CLI E2E tests + uses: ./.github/shared-actions/windows-bazel-test + with: + test_target_name: e2e.${{ matrix.subset }}_node${{ matrix.node }} + env: + E2E_SHARD_TOTAL: 6 + E2E_SHARD_INDEX: ${{ matrix.shard }} + + e2e-package-managers: + needs: test + strategy: + fail-fast: false + matrix: + node: [22] + subset: [yarn, pnpm, bun] + shard: [0, 1, 2] + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Run CLI E2E tests + run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=3 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }} + + e2e-snapshots: + needs: test + if: github.ref == 'refs/heads/main' + strategy: + fail-fast: false + matrix: + node: [22] + subset: [esbuild, webpack] + shard: [0, 1, 2, 3, 4, 5] + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Run CLI E2E tests + run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} + + browsers: + needs: build + runs-on: ubuntu-latest + name: Browser Compatibility Tests + env: + SAUCE_TUNNEL_IDENTIFIER: angular-cli-${{ github.workflow }}-${{ github.run_number }} + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} + - name: Run E2E Browser tests + env: + SAUCE_USERNAME: ${{ vars.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + SAUCE_LOG_FILE: /tmp/angular/sauce-connect.log + SAUCE_READY_FILE: /tmp/angular/sauce-connect-ready-file.lock + SAUCE_PID_FILE: /tmp/angular/sauce-connect-pid-file.lock + SAUCE_TUNNEL_IDENTIFIER: 'angular-${{ github.run_number }}' + SAUCE_READY_FILE_TIMEOUT: 120 + run: | + ./scripts/saucelabs/start-tunnel.sh & + ./scripts/saucelabs/wait-for-tunnel.sh + pnpm bazel test --config=saucelabs //tests:e2e.saucelabs + ./scripts/saucelabs/stop-tunnel.sh + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + if: ${{ failure() }} + with: + name: sauce-connect-log + path: ${{ env.SAUCE_CONNECT_DIR_IN_HOST }}/sauce-connect.log + + publish-snapshots: + needs: build + runs-on: ubuntu-latest + env: + CIRCLE_BRANCH: ${{ github.ref_name }} + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - run: pnpm admin snapshots --verbose + env: + SNAPSHOT_BUILDS_GITHUB_TOKEN: ${{ secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000000..3932ccf6a3b8 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,34 @@ +name: 'CodeQL' + +on: + push: + branches: ['main', '*.*.x'] + schedule: + - cron: '39 9 * * 1' + +permissions: {} + +jobs: + analyze: + name: Analyze + runs-on: 'ubuntu-latest' + permissions: + security-events: write + packages: read + strategy: + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Initialize CodeQL + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + languages: javascript-typescript + build-mode: none + config-file: .github/codeql/config.yml + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + category: '/language:javascript-typescript' diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml new file mode 100644 index 000000000000..23811dc9f573 --- /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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: angular/dev-infra/github-actions/pull-request-labeling@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} + post_approval_changes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: angular/dev-infra/github-actions/post-approval-changes@942d738d8f4d65b161d06e6c399fefec318cdbfe + 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..fae7787a6dff --- /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@942d738d8f4d65b161d06e6c399fefec318cdbfe + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml new file mode 100644 index 000000000000..abe20d3f3921 --- /dev/null +++ b/.github/workflows/perf.yml @@ -0,0 +1,55 @@ +name: Performance Tracking + +on: + push: + branches: + - main + # Run workflows for all releasable branches + - '[0-9]+.[0-9]+.x' + +permissions: + contents: 'read' + id-token: 'write' + +defaults: + run: + shell: bash + +jobs: + list: + timeout-minutes: 3 + runs-on: ubuntu-latest + outputs: + workflows: ${{ steps.workflows.outputs.workflows }} + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - id: workflows + run: echo "workflows=$(pnpm -s ng-dev perf workflows --list)" >> "$GITHUB_OUTPUT" + + workflow: + timeout-minutes: 30 + runs-on: ubuntu-latest + needs: list + strategy: + matrix: + workflow: ${{ fromJSON(needs.list.outputs.workflows) }} + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + # We utilize the google-github-actions/auth action to allow us to get an active credential using workflow + # identity federation. This allows us to request short lived credentials on demand, rather than storing + # credentials in secrets long term. More information can be found at: + # https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 + with: + project_id: 'internal-200822' + workload_identity_provider: 'projects/823469418460/locations/global/workloadIdentityPools/measurables-tracking/providers/angular' + service_account: 'measures-uploader@internal-200822.iam.gserviceaccount.com' + - run: pnpm ng-dev perf workflows --name ${{ matrix.workflow }} --commit-sha ${{github.sha}} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 000000000000..d103d3f2edcd --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,216 @@ +name: Pull Request + +on: + pull_request: + types: [opened, synchronize, reopened] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +defaults: + run: + shell: bash + +jobs: + analyze: + runs-on: ubuntu-latest + outputs: + snapshots: ${{ steps.filter.outputs.snapshots }} + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + snapshots: + - 'tests/e2e/ng-snapshot/package.json' + + lint: + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup ESLint Caching + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + with: + path: .eslintcache + key: ${{ runner.os }}-${{ hashFiles('.eslintrc.json') }} + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Generate JSON schema types + # Schema types are required to correctly lint the TypeScript code + run: pnpm run build-schema + - name: Run ESLint + run: pnpm lint --cache-strategy content + - name: Validate NgBot Configuration + run: pnpm ng-dev ngbot verify + - name: Validate Circular Dependencies + run: pnpm ts-circular-deps check + - name: Run Validation + run: pnpm admin validate + - name: Check tooling setup + run: pnpm check-tooling-setup + - name: Check commit message + # Commit message validation is only done on pull requests as its too late to validate once + # it has been merged. + run: pnpm ng-dev commit-message validate-range ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} + - name: Check code format + # Code formatting checks are only done on pull requests as its too late to validate once + # it has been merged. + run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }} + - name: Check Package Licenses + uses: angular/dev-infra/github-actions/linting/licenses@942d738d8f4d65b161d06e6c399fefec318cdbfe + + build: + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Build release targets + run: pnpm ng-dev release build + - name: Store PR release packages + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: packages + path: dist/releases/*.tgz + retention-days: 14 + + test: + needs: build + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Run module and package tests + run: pnpm bazel test -- //... -//tests/... + + e2e: + needs: build + strategy: + fail-fast: false + matrix: + node: [22] + subset: [esbuild, webpack] + shard: [0, 1, 2, 3, 4, 5] + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Run CLI E2E tests + run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }} + + build-e2e-windows-subset: + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Build E2E tests for Windows on Linux + run: | + pnpm bazel build \ + --config=e2e \ + //tests:e2e.esbuild_node22 \ + --platforms=tools:windows_x64 + - name: Store built Windows E2E tests + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: win-e2e-build-artifacts + path: | + dist/bin/tests/** + !**/node_modules/** + retention-days: 1 + if-no-files-found: 'error' + + e2e-windows-subset: + needs: build-e2e-windows-subset + runs-on: windows-2025 + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Download built Windows E2E tests + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: win-e2e-build-artifacts + path: dist/bin/tests/ + - name: Run CLI E2E tests + uses: ./.github/shared-actions/windows-bazel-test + with: + test_target_name: e2e.esbuild_node22 + env: + E2E_SHARD_TOTAL: 1 + TESTBRIDGE_TEST_ONLY: tests/basic/{build,rebuild,serve}.ts + + e2e-package-managers: + needs: build + strategy: + fail-fast: false + matrix: + node: [22] + subset: [yarn, pnpm, bun] + shard: [0, 1, 2] + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Run CLI E2E tests + run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=3 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }} + + e2e-snapshots: + needs: [analyze, build] + if: needs.analyze.outputs.snapshots == 'true' + strategy: + fail-fast: false + matrix: + node: [22] + subset: [esbuild, webpack] + shard: [0, 1, 2, 3, 4, 5] + runs-on: ubuntu-latest + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Install node modules + run: pnpm install --frozen-lockfile + - name: Setup Bazel + uses: angular/dev-infra/github-actions/bazel/setup@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@942d738d8f4d65b161d06e6c399fefec318cdbfe + - name: Run CLI E2E tests + run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..63ad55057037 --- /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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + + - name: 'Run analysis' + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + # Upload the results as artifacts. + - name: 'Upload artifact' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index a180e51682de..83582f8ece43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,60 @@ -node_modules/ -.idea -npm-debug.log +# Outputs +bazel-* +test-project-host-* +dist/ +dist-schema/ + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# IDEs +jsconfig.json + +# Intellij IDEA/WebStorm +# https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +.idea/inspectionProfiles/ +.idea/**/compiler.xml +.idea/**/encodings.xml +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Also ignore code styles because .editorconfig is used instead. +.idea/codeStyles/ + +# VSCode +# https://github.com/github/gitignore/blob/master/Global/VisualStudioCode.gitignore +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +**/*.code-workspace + +# Typings file. typings/ -tmp/ \ No newline at end of file + +# Misc +coverage/ +node_modules/ +tmp/ +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 +.DS_Store diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 000000000000..0c6213fc6bb7 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +pnpm -s ng-dev commit-message pre-commit-validate --file $1; diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000000..bbcdc40e0112 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm -s ng-dev format staged; diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg new file mode 100755 index 000000000000..2333b7b798c0 --- /dev/null +++ b/.husky/prepare-commit-msg @@ -0,0 +1 @@ +pnpm -s ng-dev commit-message restore-commit-message-draft $1 $2; diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000000..f651b93dd40b --- /dev/null +++ b/.mailmap @@ -0,0 +1,54 @@ +# Please keep this file in alphabetical order, per section. + + +################################################################################ +# Angular Team +################################################################################ +Hans Larsen +Igor Minar + Igor Minar + + +################################################################################ +# Angular CLI Team +################################################################################ +Charles Lyding + Charles Lyding <19598772+clydin@users.noreply.github.com> +Filipe Silva +Mike Brocchi +Alan Agius <17563226+alan-agius4@users.noreply.github.com> + Alan Agius <17563226+alan-agius4@users.noreply.github.com> + Alan Agius <17563226+alan-agius4@users.noreply.github.com> + Alan Agius <17563226+alan-agius4@users.noreply.github.com> + + +################################################################################ +# OpenSource Contributors +################################################################################ +Ashok Tamang + Ashok Tamang +Bram Borggreve +Carlo Dapor + +Cédric Exbrayat + Cédric Exbrayat +Chris Pearce + Chris Pearce +Dominic Watson + Dominic Watson +Jeremy Wells +Jan Kuri +Jim Cummins + Jim Cummins +John Papa + John Papa +Meligy +Michał Gołębiowski-Owczarek +Preston Van Loon + Preston Van Loon +Rodric Haddad +Shai Reznik + Shai Reznik + Shai Reznik +Stephen Cavaliere + Stephen Cavaliere diff --git a/.monorepo.json b/.monorepo.json new file mode 100644 index 000000000000..4d5face644df --- /dev/null +++ b/.monorepo.json @@ -0,0 +1,123 @@ +{ + "packages": { + "@_/builders": {}, + "devkit": {}, + "@angular/build": { + "name": "Angular Build System", + "section": "Tooling", + "links": [ + { + "label": "README", + "url": "/packages/angular/build/README.md" + } + ], + "snapshotRepo": "angular/angular-build-builds" + }, + "@angular/cli": { + "name": "Angular CLI", + "section": "Tooling", + "links": [ + { + "label": "README", + "url": "/packages/angular/cli/README.md" + } + ], + "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", + "snapshotRepo": "angular/angular-pwa-builds" + }, + "@angular/ssr": { + "name": "Angular SSR", + "links": [ + { + "label": "README", + "url": "/packages/angular/ssr/README.md" + } + ], + "snapshotRepo": "angular/angular-ssr-builds" + }, + "@angular-devkit/architect": { + "name": "Architect", + "links": [ + { + "label": "README", + "url": "/packages/angular_devkit/architect/README.md" + } + ], + "snapshotRepo": "angular/angular-devkit-architect-builds" + }, + "@angular-devkit/architect-cli": { + "name": "Architect CLI", + "section": "Tooling", + "snapshotRepo": "angular/angular-devkit-architect-cli-builds" + }, + "@angular-devkit/build-angular": { + "name": "Build Angular", + "links": [ + { + "label": "README", + "url": "/packages/angular_devkit/build_angular/README.md" + } + ], + "snapshotRepo": "angular/angular-devkit-build-angular-builds" + }, + "@angular-devkit/build-webpack": { + "name": "Build Webpack", + "links": [ + { + "label": "README", + "url": "/packages/angular_devkit/build_webpack/README.md" + } + ], + "snapshotRepo": "angular/angular-devkit-build-webpack-builds" + }, + "@angular-devkit/core": { + "name": "Core", + "links": [ + { + "label": "README", + "url": "/packages/angular_devkit/core/README.md" + } + ], + "snapshotRepo": "angular/angular-devkit-core-builds" + }, + "@angular-devkit/schematics": { + "name": "Schematics", + "links": [ + { + "label": "README", + "url": "/packages/angular_devkit/schematics/README.md" + } + ], + "snapshotRepo": "angular/angular-devkit-schematics-builds" + }, + "@angular-devkit/schematics-cli": { + "name": "Schematics CLI", + "section": "Tooling", + "snapshotRepo": "angular/angular-devkit-schematics-cli-builds" + }, + "@ngtools/webpack": { + "name": "Webpack Angular Plugin", + "section": "Misc", + "snapshotRepo": "angular/ngtools-webpack-builds" + }, + "@schematics/angular": { + "name": "Angular Schematics", + "section": "Schematics", + "snapshotRepo": "angular/schematics-angular-builds" + } + } +} diff --git a/.ng-dev/caretaker.mjs b/.ng-dev/caretaker.mjs new file mode 100644 index 000000000000..d21e783f4af5 --- /dev/null +++ b/.ng-dev/caretaker.mjs @@ -0,0 +1,17 @@ +/** + * The configuration for `ng-dev caretaker` commands. + * + * @type { import("@angular/ng-dev").CaretakerConfig } + */ +export const caretaker = { + 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"`, + }, + ], +}; diff --git a/.ng-dev/commit-message.mjs b/.ng-dev/commit-message.mjs new file mode 100644 index 000000000000..d30060674355 --- /dev/null +++ b/.ng-dev/commit-message.mjs @@ -0,0 +1,14 @@ +import { packages } from '../scripts/packages.mts'; + +/** + * The configuration for `ng-dev commit-message` commands. + * + * @type { import("@angular/ng-dev").CommitMessageConfig } + */ +export const commitMessage = { + maxLineLength: Infinity, + minBodyLength: 0, + minBodyLengthTypeExcludes: ['docs'], + // Note: When changing this logic, also change the `contributing.ejs` file. + scopes: packages.map(({ name }) => name), +}; diff --git a/.ng-dev/config.mjs b/.ng-dev/config.mjs new file mode 100644 index 000000000000..6add9773c06c --- /dev/null +++ b/.ng-dev/config.mjs @@ -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/dx-perf-workflows.yml b/.ng-dev/dx-perf-workflows.yml new file mode 100644 index 000000000000..70960ff760db --- /dev/null +++ b/.ng-dev/dx-perf-workflows.yml @@ -0,0 +1,49 @@ +workflows: + build-cli: + name: '@angular/cli build' + prepare: + - bazel clean + workflow: + - bazel build //packages/angular/cli:npm_package + + angular-build-integration: + name: '@angular/build integration' + disabled: true + prepare: + - bazel clean + workflow: + - bazel test //packages/angular/build:integration_tests + + modules-builder-tests: + name: '@ngtools/webpack test' + prepare: + - bazel clean + workflow: + - bazel test //packages/ngtools/webpack:test + + devkit-core-tests: + name: '@angular/devkit/core tests' + prepare: + - bazel clean + workflow: + - bazel test //packages/angular_devkit/core:test + + devkit-core-tests-rerun: + name: '@angular/devkit/core return test' + prepare: + - bazel clean + workflow: + - bazel test //packages/angular_devkit/core:test + # Add a single line to the beginning of a file to trigger a rebuild/retest + - sed -i '1i // comment' packages/angular_devkit/core/src/workspace/core_spec.ts + - bazel test //packages/angular_devkit/core:test + cleanup: + # Remove the single line added + - sed -i '1d' packages/angular_devkit/core/src/workspace/core_spec.ts + + build-unit-tests: + name: '@angular/build tests' + prepare: + - bazel clean + workflow: + - bazel test //packages/angular/build:test diff --git a/.ng-dev/format.mjs b/.ng-dev/format.mjs new file mode 100644 index 000000000000..c750eeea395c --- /dev/null +++ b/.ng-dev/format.mjs @@ -0,0 +1,9 @@ +/** + * Configuration for the `ng-dev format` command. + * + * @type { import("@angular/ng-dev").FormatConfig } + */ +export const format = { + 'prettier': true, + 'buildifier': true, +}; diff --git a/.ng-dev/github.mjs b/.ng-dev/github.mjs new file mode 100644 index 000000000000..467741f70f83 --- /dev/null +++ b/.ng-dev/github.mjs @@ -0,0 +1,13 @@ +/** + * Github configuration for the ng-dev command. This repository is + * used as remote for the merge script. + * + * @type { import("@angular/ng-dev").GithubConfig } + */ +export const github = { + owner: 'angular', + name: 'angular-cli', + mainBranchName: 'main', + useNgDevAuthService: true, + mergeMode: 'team-only', +}; diff --git a/.ng-dev/pull-request.mjs b/.ng-dev/pull-request.mjs new file mode 100644 index 000000000000..1f2d84d7ccba --- /dev/null +++ b/.ng-dev/pull-request.mjs @@ -0,0 +1,12 @@ +/** + * 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). + * + * @type { import("@angular/ng-dev").PullRequestConfig } + */ +export const pullRequest = { + githubApiMerge: { + default: 'auto', + labels: [{ pattern: 'merge: squash commits', method: 'squash' }], + }, +}; diff --git a/.ng-dev/release.mjs b/.ng-dev/release.mjs new file mode 100644 index 000000000000..2aadf9db122c --- /dev/null +++ b/.ng-dev/release.mjs @@ -0,0 +1,35 @@ +import semver from 'semver'; +import { releasePackages } from '../scripts/packages.mts'; + +/** + * Configuration for the `ng-dev release` command. + * + * @type { import("@angular/ng-dev").ReleaseConfig } + */ +export const release = { + representativeNpmPackage: '@angular/cli', + npmPackages: releasePackages.map(({ name, experimental }) => ({ name, experimental })), + buildPackages: async () => { + // The `performNpmReleaseBuild` function is loaded at runtime to avoid loading additional + // files and dependencies unless a build is required. + const { performNpmReleaseBuild } = await import('../scripts/build-packages-dist.mts'); + return performNpmReleaseBuild(); + }, + prereleaseCheck: async (newVersionStr) => { + const newVersion = new semver.SemVer(newVersionStr); + const { assertValidDependencyRanges } = + await import('../scripts/release-checks/dependency-ranges/index.mts'); + + await assertValidDependencyRanges(newVersion, releasePackages); + }, + 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/tsconfig.json b/.ng-dev/tsconfig.json new file mode 100644 index 000000000000..a250849eb673 --- /dev/null +++ b/.ng-dev/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "resolveJsonModule": true, + "allowJs": true, + "rewriteRelativeImportExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "module": "Node16", + "moduleResolution": "Node16", + "checkJs": true, + "noEmit": true, + "types": [] + }, + "include": ["**/*.mjs"], + "exclude": [] +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000000..9227ff789b96 --- /dev/null +++ b/.npmrc @@ -0,0 +1,11 @@ +engine-strict = true + +# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on +# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what +# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules) +hoist=false + +# Avoid pnpm auto-installing peer dependencies. We want to be explicit about our versions used +# for peer dependencies, avoiding potential mismatches. In addition, it ensures we can continue +# to rely on peer dependency placeholders substituted via Bazel. +auto-install-peers=false diff --git a/.nvmrc b/.nvmrc index fae6e3d04b2c..5767036af0e2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -4.2.1 +22.21.1 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..1adbf7759080 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,17 @@ +/bazel-out/ +/docs/design/analytics.md +/dist-schema/ +/goldens/public-api +/modules/testing/builder/projects/ +/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/ +/packages/schematics/angular/third_party/ +/README.md +/CONTRIBUTING.md +dist/ +/tests/e2e/assets/ +/tools/test/*.json +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000000..22e9e4076522 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "quoteProps": "preserve", + "singleQuote": true +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b5d11594c846..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -sudo: false -env: - - NODE_VERSION=4 - - NODE_VERSION=5 - -os: - - linux - - osx -script: npm run-script test -addons: - firefox: "latest" -before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.30.1/install.sh | bash; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then source ~/.nvm/nvm-exec; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew tap caskroom/cask; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew cask install google-chrome; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh -e /etc/init.d/xvfb start; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CHROME_BIN=chromium-browser; fi - - nvm install $NODE_VERSION - - npm config set spin false - - npm config set progress false -install: - - node --version - - npm --version - - git --version - - npm install --no-optional diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000000..6f3e0e3d3375 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,49 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.2.0", + "configurations": [ + { + "name": "DEBUG: Attach to bazel test", + "type": "node", + "request": "attach", + "port": 9229, + "restart": true, + "timeout": 600000, + "sourceMaps": true, + "skipFiles": ["/**"], + "sourceMapPathOverrides": { + "?:*@0.0.0/node_modules/@angular-devkit/build-angular/*": "${workspaceFolder}/packages/angular_devkit/build_angular/*", + "?:*@0.0.0/node_modules/@angular-devkit/build-webpack/*": "${workspaceFolder}/packages/angular_devkit/build_webpack/*", + "?:*@0.0.0/node_modules/@angular-devkit/*": "${workspaceFolder}/packages/angular_devkit/*", + "?:*@0.0.0/node_modules/@angular/*": "${workspaceFolder}/packages/angular/*", + "?:*/bin/*": "${workspaceFolder}/*" + }, + "resolveSourceMapLocations": ["*/**", "!**/rxjs**"] + }, + { + "name": "DEBUG: Run bazel test (Custom Target)", + "type": "node", + "request": "launch", + "restart": true, + "timeout": 600000, + "runtimeExecutable": "pnpm", + "runtimeArgs": ["bazel", "test", "${input:bazelTarget}", "--config=debug"], + "console": "integratedTerminal", + "cwd": "${workspaceFolder}" + } + ], + "compounds": [ + { + "name": "DEBUG: Run bazel test (Custom Target) + Attach", + "configurations": ["DEBUG: Attach to bazel test", "DEBUG: Run bazel test (Custom Target)"] + } + ], + "inputs": [ + { + "id": "bazelTarget", + "type": "promptString", + "description": "Enter the Bazel test target (e.g., //path/to/my:unit_test)", + "default": "//packages/..." + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..b98a874af297 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,26 @@ +{ + "[javascript]": { + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.formatOnSave": true + }, + // Exclude third party modules and build artifacts from the editor watchers/searches. + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/**": true, + "**/bazel-out/**": true, + "**/dist/**": true, + "**/dist-schema/**": true + }, + "search.exclude": { + "**/node_modules": true, + "**/bazel-out": true, + "**/dist": true, + "**/dist-schema": true, + ".history": true + }, + "git.ignoreLimitWarning": true, + "gitlens.advanced.blame.customArguments": ["--ignore-revs-file .git-blame-ignore-revs"] +} diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 000000000000..99bc6eb0355f --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,110 @@ +load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") +load("@devinfra//bazel/validation:defs.bzl", "validate_ts_version_matching") +load("@npm//:defs.bzl", "npm_link_all_packages") +load("//tools:defaults.bzl", "copy_to_bin") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files([ + "LICENSE", + "tsconfig.json", + "tsconfig-test.json", + "tsconfig-build-ng.json", + "tsconfig-build.json", + "package.json", +]) + +npm_link_all_packages() + +rules_js_tsconfig( + name = "tsconfig", + src = "tsconfig.json", + visibility = [ + "//:__pkg__", + ], +) + +rules_js_tsconfig( + name = "build-tsconfig", + src = "tsconfig-build.json", + deps = [ + ":tsconfig", + "//:node_modules/@types/node", + ], +) + +rules_js_tsconfig( + name = "test-tsconfig", + src = "tsconfig-test.json", + deps = [ + ":tsconfig", + "//:node_modules/@types/jasmine", + "//:node_modules/@types/node", + ], +) + +rules_js_tsconfig( + name = "build-tsconfig-esm", + src = "tsconfig-build-esm.json", + deps = [ + ":tsconfig", + ], +) + +rules_js_tsconfig( + name = "test-tsconfig-esm", + src = "tsconfig-test-esm.json", + deps = [ + ":build-tsconfig-esm", + "//:node_modules/@types/jasmine", + "//:node_modules/@types/node", + ], +) + +# Files required by e2e tests +copy_to_bin( + name = "config-files", + srcs = [ + "package.json", + ], +) + +# Detect if the build is running under --stamp +config_setting( + name = "stamp", + values = {"stamp": "true"}, +) + +# If set will replace dependency versions with tarballs for packages in this repo +bool_flag( + name = "enable_package_json_tar_deps", + build_setting_default = False, +) + +config_setting( + name = "package_json_use_tar_deps", + flag_values = { + ":enable_package_json_tar_deps": "true", + }, +) + +# If set will replace dependency versions with snapshot repos for packages in this repo +bool_flag( + name = "enable_snapshot_repo_deps", + build_setting_default = False, +) + +config_setting( + name = "package_json_use_snapshot_repo_deps", + flag_values = { + ":enable_snapshot_repo_deps": "true", + }, +) + +validate_ts_version_matching( + module_lock_file = "MODULE.bazel.lock", + package_json = "package.json", +) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000000..c9760339d143 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17042 @@ + + +# 21.1.0-next.3 (2025-12-18) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [348096623](https://github.com/angular/angular-cli/commit/348096623326857a5d8cf77d56712776e1190180) | fix | enhance list_projects MCP tool file system traversal and symlink handling | +| [032257a6d](https://github.com/angular/angular-cli/commit/032257a6d00360d2c4e6d5406409dcfa5b27d1d5) | fix | improve signal forms lesson examples in AI tutor | +| [18d74dde8](https://github.com/angular/angular-cli/commit/18d74dde8938dbe566df80753d5c148c19040179) | fix | rename mcp devserver tools to comply with naming spec | +| [a15db28b2](https://github.com/angular/angular-cli/commit/a15db28b29f6f43bef1ed1ca7c6a963d9943f801) | perf | cache resolved specific version in package manager abstraction | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [52ace04a7](https://github.com/angular/angular-cli/commit/52ace04a7ca1c102fdf1addf5ab6fe400c0eab0e) | fix | improve VS Code background compilation start/end detection | +| [288a9225c](https://github.com/angular/angular-cli/commit/288a9225c83edec9560e2b39901740e792c54d27) | fix | remove `inlineSources` from library tsconfig template | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [98c207bc0](https://github.com/angular/angular-cli/commit/98c207bc0e44caccd6fffa5b8d3a013a2a3a871a) | fix | add browser condition to resolver for vitest | +| [f39f7ee95](https://github.com/angular/angular-cli/commit/f39f7ee9529113f7c75d0e0e3ffa628fed9ce92f) | fix | allow non-prefixed requests when using SSR and base href | +| [7c7e6a614](https://github.com/angular/angular-cli/commit/7c7e6a6142a9d294e04c612463449d2a4360e692) | fix | conditionally manage Vitest UI option | +| [edeb41c0e](https://github.com/angular/angular-cli/commit/edeb41c0e01881c21dec4d7f63fe8d302ce0521d) | fix | ensure tests run when compilation error is resolved | +| [9744af1f8](https://github.com/angular/angular-cli/commit/9744af1f82a8e9c2816adf636e4e8a1a8be06c60) | fix | remove LmdbCacheStore export from private API | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [e5651224b](https://github.com/angular/angular-cli/commit/e5651224b5086335d48b133e1d0b9c8536c22e5f) | fix | add leading slash to well-known non-Angular URLs | +| [081e31337](https://github.com/angular/angular-cli/commit/081e3133764c9a23f70969bfd182259be34a411e) | fix | propagate status code to redirect | +| [2d56a319d](https://github.com/angular/angular-cli/commit/2d56a319d8d45f36d9e5d958cbbd96e195c2c15e) | fix | skip SSR processing for well-known non-Angular URLs like favicon.ico | + + + + + +# 21.0.4 (2025-12-18) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [b671245b9](https://github.com/angular/angular-cli/commit/b671245b9d3ba98ac0f66dbd34f272539113be61) | fix | improve VS Code background compilation start/end detection | +| [85a28dec7](https://github.com/angular/angular-cli/commit/85a28dec771cce77a3ffee35f419b5fedca807b8) | fix | remove `inlineSources` from library tsconfig template | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [deb4fff61](https://github.com/angular/angular-cli/commit/deb4fff6196d2eb147e358a7143e2ada2b6114c9) | fix | add browser condition to resolver for vitest | +| [570ce8d3e](https://github.com/angular/angular-cli/commit/570ce8d3eeb280eeb6dca6ba54593c9325674741) | fix | allow non-prefixed requests when using SSR and base href | +| [4dd3c1a32](https://github.com/angular/angular-cli/commit/4dd3c1a324c8e90808cc1c5febf65c8fa49dd3b9) | fix | conditionally manage Vitest UI option | +| [4b8b7caec](https://github.com/angular/angular-cli/commit/4b8b7caece41f86746321a98786dfdff499582b6) | fix | ensure tests run when compilation error is resolved | +| [bef4fcecb](https://github.com/angular/angular-cli/commit/bef4fcecb6d116f9f022da845f06708cf29be02a) | fix | remove LmdbCacheStore export from private API | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [bb54747da](https://github.com/angular/angular-cli/commit/bb54747da69fb15b6c2ebb52b45a83cbff3231c8) | fix | add leading slash to well-known non-Angular URLs | +| [0cfe2e749](https://github.com/angular/angular-cli/commit/0cfe2e749f50b832c64bbba322eb0cef7ad40365) | fix | propagate status code to redirect | +| [eadadb848](https://github.com/angular/angular-cli/commit/eadadb848ca7fa45c4dda835af39400e017bbe1c) | fix | skip SSR processing for well-known non-Angular URLs like favicon.ico | + + + + + +# 21.1.0-next.2 (2025-12-10) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [d8b76e93d](https://github.com/angular/angular-cli/commit/d8b76e93d3e9e4e7bd7ad6e12fdf59cd663cbb8e) | fix | correctly handle yarn classic tag manifest fetching | +| [7ab5c0b0a](https://github.com/angular/angular-cli/commit/7ab5c0b0a1c637f3e0adb71486e5e7e8716561e4) | fix | correctly spawn package managers on Windows in new abstraction | +| [240588b7e](https://github.com/angular/angular-cli/commit/240588b7e3c8698c83110793ab98d20caee4e1a4) | perf | optimize `ng add` version discovery | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [985aa18d0](https://github.com/angular/angular-cli/commit/985aa18d0b6cf728c498c0873793e131a4c416c1) | fix | conditionally provide Zone.js change detection in the built-in test main file | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------- | +| [30b5d81b4](https://github.com/angular/angular-cli/commit/30b5d81b4adafca32c94672a39574daa2e3fc8b7) | fix | Add custom middleware for to present an Angular-tailored message | +| [2e7227b8d](https://github.com/angular/angular-cli/commit/2e7227b8dc04d4b2ca20e18baaeebaa65d3c2aac) | fix | Ensure disposal of close-javascript-transformer | +| [38b16ea01](https://github.com/angular/angular-cli/commit/38b16ea0108c48835dc0d81863eca84f7b8cea6e) | fix | ensure locale base href retains leading slash ([#32040](https://github.com/angular/angular-cli/pull/32040)) | +| [385165cbc](https://github.com/angular/angular-cli/commit/385165cbc6ff087e6bc1fb6f686d4929e83a075a) | fix | inject testing polyfills in Karma unit-test executor | +| [6d212206f](https://github.com/angular/angular-cli/commit/6d212206fdfc94e661a25bed1287c0bc15219b63) | fix | support NODE_EXTRA_CA_CERTS in SSR SSL plugin | + + + + + +# 21.0.3 (2025-12-10) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [5d85f416f](https://github.com/angular/angular-cli/commit/5d85f416f43b6bcd07b28ab920cb40c61a83ebdd) | fix | conditionally provide Zone.js change detection in the built-in test main file | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------- | +| [778b4cffc](https://github.com/angular/angular-cli/commit/778b4cffc03e7c137940e3b8c89f290fd226cf17) | fix | Add custom middleware for to present an Angular-tailored message | +| [9b02ab2ee](https://github.com/angular/angular-cli/commit/9b02ab2ee0a36aa6aafd94ea8059b48679845860) | fix | Ensure disposal of close-javascript-transformer | +| [0fc7d576e](https://github.com/angular/angular-cli/commit/0fc7d576e53f45601fdbeb95f4a853ebceae4fad) | fix | ensure locale base href retains leading slash ([#32040](https://github.com/angular/angular-cli/pull/32040)) | +| [b141670a2](https://github.com/angular/angular-cli/commit/b141670a2453dd0ea5fe6aa22ddae7175893d813) | fix | inject testing polyfills in Karma unit-test executor | +| [88c18ce68](https://github.com/angular/angular-cli/commit/88c18ce68585726652b88b10ce090039fbe1829f) | fix | support NODE_EXTRA_CA_CERTS in SSR SSL plugin | + + + + + +# 21.1.0-next.1 (2025-12-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------ | +| [d635a6c63](https://github.com/angular/angular-cli/commit/d635a6c6335d0838fc0977f6742f6aa9f769c527) | feat | add signal forms lessons | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [e71a72ffd](https://github.com/angular/angular-cli/commit/e71a72ffdc426e26bfb4f0bb92e8f5795a621c18) | feat | generate detailed migration report for `refactor-jasmine-vitest` | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- | +| [98e10fa0f](https://github.com/angular/angular-cli/commit/98e10fa0f29cc8f6cf6a25c45c6888a79465eaf7) | fix | remove lazy imports in node tasks | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [63c3e3f64](https://github.com/angular/angular-cli/commit/63c3e3f6406d345e777ca18bfad7d6d701e5c4ea) | fix | add filename truncation to test discovery | +| [8d8ba4f61](https://github.com/angular/angular-cli/commit/8d8ba4f61fc07dd670b705c48e82cf63424b3cce) | fix | allow overriding Vitest coverage `reportsDirectory` option | + + + + + +# 21.0.2 (2025-12-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [f1a7116cd](https://github.com/angular/angular-cli/commit/f1a7116cdff1bd83b26b0d64cea14ec4e8084583) | fix | update `@modelcontextprotocol/sdk` to v1.24.0 | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- | +| [dc6d9469e](https://github.com/angular/angular-cli/commit/dc6d9469ea494bbfee7da191774e9fa3c0baf30a) | fix | remove lazy imports in node tasks | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [f8a1939fd](https://github.com/angular/angular-cli/commit/f8a1939fdf5abbc1de439d288d9357d4f92a72a9) | fix | add filename truncation to test discovery | +| [86dd3297f](https://github.com/angular/angular-cli/commit/86dd3297f7ad81788713cfd8dae48c0fad4b89ab) | fix | allow overriding Vitest coverage `reportsDirectory` option | + + + + + +# 20.3.13 (2025-12-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [cfbb61602](https://github.com/angular/angular-cli/commit/cfbb61602daf32c5b942ea84702fc3638aa111e7) | fix | update `@modelcontextprotocol/sdk` to v1.24.0 | + + + + + +# 21.1.0-next.0 (2025-11-26) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------- | +| [c3c9ac506](https://github.com/angular/angular-cli/commit/c3c9ac5067275461e2d8caefba81ac9701949776) | feat | Add MCP tools for building and running devservers | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [36cf3afb4](https://github.com/angular/angular-cli/commit/36cf3afb485a01f86c4c90f136b38a3cf338e313) | feat | add browserMode option to jasmine-vitest schematic | +| [18cf6c51b](https://github.com/angular/angular-cli/commit/18cf6c51b72ce5c7f23012585ed992cf91cef5ed) | fix | add MCP configuration file to new workspaces | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------- | +| [ad99e00ad](https://github.com/angular/angular-cli/commit/ad99e00ad7edd17e369777c8d38b4137ea736121) | fix | simplify SSL handling for `ng serve` with SSR ([#31722](https://github.com/angular/angular-cli/pull/31722)) | + + + + + +# 21.0.1 (2025-11-26) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- | +| [363496ae0](https://github.com/angular/angular-cli/commit/363496ae0d2850545274cd7fe4dc6902ccb64e10) | fix | ensure dependencies are resolved correctly for node modules directory check | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [2f58705cb](https://github.com/angular/angular-cli/commit/2f58705cb5389019ceb49616a0e4ec3f92a558ed) | fix | add missing imports for lifecycle hooks in jasmine-vitest migration | +| [c973bb9ca](https://github.com/angular/angular-cli/commit/c973bb9cafc8d59b901a9d763347f4b615257867) | fix | add mock names to createSpyObj transformation | +| [4534c9848](https://github.com/angular/angular-cli/commit/4534c9848745eea516bdb58d86914252c35b5b9c) | fix | do not set `esModuleInterop` and `moduleResolution` when module is `preserve` | +| [16d898e75](https://github.com/angular/angular-cli/commit/16d898e7587036d68786cebe764da08304559d41) | fix | fix migration of `jasmine.clock().mockDate()` | +| [21c3eac72](https://github.com/angular/angular-cli/commit/21c3eac726c198132af760ffacc0dab9dfccb430) | fix | handle createSpyObj without base name on refactor-jasmine-vitest | +| [b8c99aa4c](https://github.com/angular/angular-cli/commit/b8c99aa4c909647285d1dcc61a2bb97a36100c63) | fix | improve safety of done callback transformation | +| [4a71e06fc](https://github.com/angular/angular-cli/commit/4a71e06fcafaadbcb820d285c0c186aa0e92f158) | fix | silently skip when the build target already uses one of the new builders | +| [2ffdae421](https://github.com/angular/angular-cli/commit/2ffdae42149b0f3da44f96271af1bca6d09b7ed5) | fix | support testRunner option in library schematic | +| [145de4a58](https://github.com/angular/angular-cli/commit/145de4a584ce8f72746704547282299306d9bafb) | fix | warn about loose matching in arrayWithExactContents | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [d097df2d7](https://github.com/angular/angular-cli/commit/d097df2d7088dd2bb97643c3acfc1f977a767dd9) | fix | correct Vitest coverage path resolution for JSDOM on Windows | +| [cdb607ada](https://github.com/angular/angular-cli/commit/cdb607ada4bf9aaec6ed8aafd8826d782fd13109) | fix | correctly configure per-browser headless mode in Vitest runner | +| [244931ece](https://github.com/angular/angular-cli/commit/244931ece877a1cacd1cfce64314e04a52526f80) | fix | correctly invoke `isTTY` as a function | +| [54d542738](https://github.com/angular/angular-cli/commit/54d542738e23c275ac6827f19da92213c405f9e2) | fix | ensure correct URL joining for prerender routes | +| [a28b38bbe](https://github.com/angular/angular-cli/commit/a28b38bbeba0977e99142a15d1ecc77c15abc416) | fix | force dev-server to use HTTP/1.1 when using SSR with SSL | +| [59ff867f0](https://github.com/angular/angular-cli/commit/59ff867f0d2e7f7f88480deefa0ee470c037197a) | fix | normalize `--include` paths to posix | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [03e231216](https://github.com/angular/angular-cli/commit/03e231216d3b8ba0de81da53a446eff0c701658d) | fix | handle `X-Forwarded-Prefix` and `APP_BASE_HREF` in redirects | +| [3cac01882](https://github.com/angular/angular-cli/commit/3cac0188271175e12cc238c6610b542f3ae14db3) | fix | prevent redirect loop with encoded query parameters | + + + + + +# 20.3.12 (2025-11-25) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [25bb7e65c](https://github.com/angular/angular-cli/commit/25bb7e65c4fc7e401c658126c53b0b7a13d62965) | fix | ensure correct URL joining for prerender routes | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [cceb86296](https://github.com/angular/angular-cli/commit/cceb862969e541a5f54b689a6439e32773eafe65) | fix | handle `X-Forwarded-Prefix` and `APP_BASE_HREF` in redirects | +| [1abe68ad8](https://github.com/angular/angular-cli/commit/1abe68ad87f9b892734117a087b5775068bd232b) | fix | prevent redirect loop with encoded query parameters | + + + + + +# 21.0.0 (2025-11-19) + +## Breaking Changes + +### @angular/cli + +- The `ng` commands will no longer automatically detect and use `cnpm` as the package manager. As an alternative use the `.npmrc` file to ensure npm uses the cnpm registry. + +### @angular/build + +- - TypeScript versions older than 5.9 are no longer supported. +- The `javascriptEnabled` option for Less is no longer supported. Projects relying on inline JavaScript within Less files will need to refactor their stylesheets to remove this dependency. + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [e417c89f9](https://github.com/angular/angular-cli/commit/e417c89f9e9cfe0ce50ffbc72ef555793605aea1) | feat | Add `addTypeToClassName` option to relevant schematics | +| [ede5e52bc](https://github.com/angular/angular-cli/commit/ede5e52bc701c42948bd98826cd4fa901350015c) | feat | add `include` option to jasmine-to-vitest schematic | +| [c119910f4](https://github.com/angular/angular-cli/commit/c119910f4505e280eea83ea6647b6d279a46f36d) | feat | add AGENTS.md support to ai-config schematic | +| [d0d2a17b8](https://github.com/angular/angular-cli/commit/d0d2a17b8adb2c1ce6eee70494f5d2298622c40e) | feat | add Jasmine spy API transformations to jasmine-to-vitest schematic | +| [e7d955bed](https://github.com/angular/angular-cli/commit/e7d955bedd5ca6957903cb73f8ebe06823a808da) | feat | add matcher transformations to jasmine-to-vitest schematic | +| [629f5cb18](https://github.com/angular/angular-cli/commit/629f5cb181fee562645baf02b44ebb3b39f3fb06) | feat | add misc transformations to jasmine-to-vitest schematic | +| [4912f3990](https://github.com/angular/angular-cli/commit/4912f39906b11a3212f11d5a00d577e2a0bacab4) | feat | add Tailwind CSS option to application schematic and `ng new` | +| [2a518016d](https://github.com/angular/angular-cli/commit/2a518016d9585dd4d16f90102d5409459ebba024) | feat | Applications are zoneless by default | +| [2ffc527b1](https://github.com/angular/angular-cli/commit/2ffc527b1bbe237e9f732d3506ce3a75ca1ca9d0) | feat | configure Vitest for new projects and allow runner choice | +| [58474ec7d](https://github.com/angular/angular-cli/commit/58474ec7dd85fc34639c138d9b8d545affb50e3e) | feat | introduce initial jasmine-to-vitest unit test refactor schematic | +| [9f255f2b3](https://github.com/angular/angular-cli/commit/9f255f2b3cc435f3bea2f0266a137176ca599aef) | feat | set `packageManager` in `package.json` on new projects | +| [4e6c94f21](https://github.com/angular/angular-cli/commit/4e6c94f21e882c593cf11197900c29d693af9297) | feat | support different file name style guides in `ng new` | +| [77741f5ee](https://github.com/angular/angular-cli/commit/77741f5eec735f23b0f2865101471e045e4889b8) | fix | add 'update-typescript-lib' migration | +| [f89750b27](https://github.com/angular/angular-cli/commit/f89750b27866c307da546fe4f33da980693ca5c1) | fix | add `addImports` option to jasmine-vitest schematic | +| [9dab5780a](https://github.com/angular/angular-cli/commit/9dab5780a1befbd76ee9ba4c4e6ac2d3fd714bb9) | fix | add fixture.whenStable in spec files when zoneless apps | +| [8f0f6a5f1](https://github.com/angular/angular-cli/commit/8f0f6a5f113ffc9e81d99eeeba71f8054e2d3686) | fix | add migration to update `moduleResolution` to `bundler` | +| [e8feba9ee](https://github.com/angular/angular-cli/commit/e8feba9ee163f688c51d6463336474591e886647) | fix | add missing typeSeparator to main.ts.template file | +| [515b09c4f](https://github.com/angular/angular-cli/commit/515b09c4f28ef1c2eb911cb73135a6086dcab3c9) | fix | add Vitest config generation and runner checks | +| [0e83fe1a8](https://github.com/angular/angular-cli/commit/0e83fe1a87cc3dcbc9daa4440a050ae6aafc8042) | fix | add warnings and improve Karma config generation | +| [b91fa31f2](https://github.com/angular/angular-cli/commit/b91fa31f20b49ead021c72c271f67da38b340584) | fix | align Karma project generation with unified unit-test builder | +| [c967a447c](https://github.com/angular/angular-cli/commit/c967a447ce755fbf582ec35aa24bb6e0fa0043cf) | fix | correct spacing in application spec tsconfig | +| [00d941c43](https://github.com/angular/angular-cli/commit/00d941c433de718cf3c38033d5d68dd86f790291) | fix | correct style guide paths for standalone components | +| [e33e77d12](https://github.com/angular/angular-cli/commit/e33e77d12984446fe7bc77deb7438806817ba8a7) | fix | flag '--file-name-style-guide=2016' - wrong import in main.ts | +| [f35b9f331](https://github.com/angular/angular-cli/commit/f35b9f3310995b05d501f2abaec58dcd283e3aa0) | fix | improve comment preservation in jasmine-to-vitest | +| [6615fcf03](https://github.com/angular/angular-cli/commit/6615fcf037686cd96e97b469937b7f0736afaa77) | fix | issues in apps generated with '--file-name-style-guide=2016' flag | +| [e304821d5](https://github.com/angular/angular-cli/commit/e304821d5d789fab2725d3152612d3e5b6bd0dc7) | fix | make ai-config schematic non-destructive | +| [512ad282a](https://github.com/angular/angular-cli/commit/512ad282aecbfdf1e5c9e9700cc722addb949b68) | fix | preserve blank lines in jasmine-to-vitest schematic | +| [b524ba426](https://github.com/angular/angular-cli/commit/b524ba42625cd690177a300ca4843ef4edce035f) | fix | remove empty i18n-extract target for new projects | +| [8e6e0a293](https://github.com/angular/angular-cli/commit/8e6e0a2931bfb178e77cf2c9ca7f92a56c673449) | fix | remove explicit flag for host bindings | +| [afb4d3e37](https://github.com/angular/angular-cli/commit/afb4d3e377b11315a03563cb8c143c35d37f113a) | fix | remove extra space before async in spec templates | +| [b983ea8e5](https://github.com/angular/angular-cli/commit/b983ea8e5107420a910dbbc05c6b74f0ff6fbddd) | fix | respect skip-install for tailwind schematic | +| [54c4eae2a](https://github.com/angular/angular-cli/commit/54c4eae2aa49c6b45c41f0718a5915a10d426cb4) | fix | transform Jasmine type annotations in jasmine-to-vitest schematic | +| [14c0a9bac](https://github.com/angular/angular-cli/commit/14c0a9bacbb66b1db714ea7906c7d33f49c710fc) | perf | optimize AST traversal utilities | + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [58d101d5e](https://github.com/angular/angular-cli/commit/58d101d5e78cf4c158dbaf52103639d56b84f9ed) | feat | add `--json` output to `ng version` | +| [d014630fa](https://github.com/angular/angular-cli/commit/d014630fad765ae3928b698122038cbe00d37102) | feat | add advanced filtering to MCP example search | +| [6d3a3c579](https://github.com/angular/angular-cli/commit/6d3a3c5799bde1bab5c3878e0783ffa6854e36ad) | feat | add ai-tutor mcp tool | +| [1c06b16a9](https://github.com/angular/angular-cli/commit/1c06b16a962d3c2cc122dc40e01c64bc8a8d754d) | feat | add builder info to `list_projects` MCP tool | +| [301b50da4](https://github.com/angular/angular-cli/commit/301b50da4cf99b3cd87940606121d076b4f241c6) | feat | add fallback support for packages without direct `ng add` functionality | +| [3040b777e](https://github.com/angular/angular-cli/commit/3040b777e40bc90fd1ed961e3d134875b3f9b464) | feat | add style language detection to list_projects tool | +| [45024e836](https://github.com/angular/angular-cli/commit/45024e836b4006cc48b18bb99d025ae1a572db12) | feat | add unit test framework detection to list_projects tool | +| [104c90768](https://github.com/angular/angular-cli/commit/104c90768000b3e0052ee7e7de2c5e04c1bffdaf) | feat | enhance `ng version` output with more details | +| [286b6204c](https://github.com/angular/angular-cli/commit/286b6204c825c990761a0d5e5b91bb439dd13655) | feat | make documentation search tool version-aware | +| [406315d09](https://github.com/angular/angular-cli/commit/406315d0939c62d9f4f39ce64b168e72bbdd588c) | feat | make find_examples tool version-aware | +| [68e711307](https://github.com/angular/angular-cli/commit/68e711307eae88a621698c2a9cc2abc30d44efc8) | feat | make get_best_practices tool version-aware | +| [50453fdee](https://github.com/angular/angular-cli/commit/50453fdeec4a00d88deada49d2dd0867bdb784fb) | feat | overhaul `ng version` command output | +| [1ee9ce3c9](https://github.com/angular/angular-cli/commit/1ee9ce3c93caff419f8095a91cf58601e3df3f74) | feat | promote MCP `find_examples` tool to a stable tool | +| [0d53e82d5](https://github.com/angular/angular-cli/commit/0d53e82d5ed8986603c2005fc06041dd076b08c6) | feat | provide detailed peer dependency conflict errors in ng add | +| [f513089e2](https://github.com/angular/angular-cli/commit/f513089e276acf5a7c4f6879a95e2d6ed78ae67d) | feat | remove direct support for `cnpm` | +| [c17d7a929](https://github.com/angular/angular-cli/commit/c17d7a929adccb77f3c2c33e70005f50032d8cae) | fix | add schema versioning and metadata to example database | +| [dbf1aaf70](https://github.com/angular/angular-cli/commit/dbf1aaf70bc3e3dd0de05d760bafacc43b34dce8) | fix | add snippet support to example search MCP tool | +| [dfb4242b3](https://github.com/angular/angular-cli/commit/dfb4242b347365f3a2c6d006f07a16c982ff4dbe) | fix | add vitest to version command output | +| [11cee1acb](https://github.com/angular/angular-cli/commit/11cee1acb59afbad1ef88d8340b5438f7dbefe57) | fix | correct boolean parsing in MCP example front matter | +| [122a8c0e2](https://github.com/angular/angular-cli/commit/122a8c0e27342db79eb4d38e23032548054709b9) | fix | correct frontmatter parsing in MCP examples tool | +| [431106559](https://github.com/angular/angular-cli/commit/431106559d6e75f5113876a3f92fdf4dc4b2114d) | fix | correct query in find_examples to prevent runtime error | +| [def412a55](https://github.com/angular/angular-cli/commit/def412a558d71cb51fa16d826418bd0ed0a085cf) | fix | enhance find_examples MCP tool with structured output | +| [0922a033f](https://github.com/angular/angular-cli/commit/0922a033f546b38f83d1cae524cf7237dd37a2ac) | fix | improve JSON schema parsing for command options | +| [f099c9157](https://github.com/angular/angular-cli/commit/f099c91570b3cd748d7138bd18a4898a345549db) | fix | improve list_projects MCP tool to find all workspaces in monorepos | +| [1be35b343](https://github.com/angular/angular-cli/commit/1be35b3433179481be85ea1cb892d66170e0aebe) | fix | promote zoneless migration MCP tool to stable | +| [e5aed6d65](https://github.com/angular/angular-cli/commit/e5aed6d655ed92ea6eb3ac03716b8a02a5f731d6) | fix | show planned actions in `ng add` dry run | +| [4deac3ec7](https://github.com/angular/angular-cli/commit/4deac3ec785b1a53156aac90441d0ed129df71ef) | fix | support multi-database search in find_examples MCP tool | +| [aeb49dd52](https://github.com/angular/angular-cli/commit/aeb49dd52bf88785a193fcb6caa0b36aaeef1d37) | perf | cache dependency lookups during `ng add` | +| [5e534090e](https://github.com/angular/angular-cli/commit/5e534090e25e00a9fafbce2867030e7fdb0efbf6) | perf | parallelize peer dependency checks in `ng add` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [6e395fc0c](https://github.com/angular/angular-cli/commit/6e395fc0c4505dd32b3237ea116e8db6bde25758) | fix | ensure vitest code coverage handles virtual files correctly | +| [53899511a](https://github.com/angular/angular-cli/commit/53899511afe5665541984085914a313390af6ce2) | fix | expand `jest` and `jest-environment-jsdom` to allow version 30 | +| [7a8c94615](https://github.com/angular/angular-cli/commit/7a8c94615164e114533fae0f84796a374dc1b47b) | fix | make zone.js optional in server and app-shell builders | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------- | +| [00426e315](https://github.com/angular/angular-cli/commit/00426e3150c846913a5aa31510b5a1126df9e570) | feat | add --list-tests flag to unit-test builder | +| [a908bf3d4](https://github.com/angular/angular-cli/commit/a908bf3d4e8a59f3546f117bcc1f12fd69ad2d0b) | feat | add 'filter' option to unit-test builder | +| [3e0209d0a](https://github.com/angular/angular-cli/commit/3e0209d0a6bc6d7985d6294fc1430cdbe4d2d9a6) | feat | add `browserViewport` option for vitest browser tests | +| [3b7dabbf1](https://github.com/angular/angular-cli/commit/3b7dabbf1df9b2b6ca9ffc6c038abb6e40b6df2b) | feat | add advanced coverage options to unit-test builder | +| [c0b00d78e](https://github.com/angular/angular-cli/commit/c0b00d78ec37426f4474f473ddf9e627a0dd23df) | feat | add reporter output file option for unit-test | +| [66dd6dd83](https://github.com/angular/angular-cli/commit/66dd6dd835d6b489e6b4be2138aa443e11bfa076) | feat | allow options for unit test reporters | +| [a90bea5b5](https://github.com/angular/angular-cli/commit/a90bea5b51c6978441919ed2af85c090fe99fd38) | feat | support `.test.ts` files by default in unit test builder | +| [b2f048773](https://github.com/angular/angular-cli/commit/b2f048773c6014022983e7ccc52cb760619d8a1b) | fix | add --ui option for Vitest runner | +| [530d9270e](https://github.com/angular/angular-cli/commit/530d9270e87786594dd8d1956b0806a28650db25) | fix | add `define` option to dev-server | +| [b554bd73a](https://github.com/angular/angular-cli/commit/b554bd73a9c248d986ed718028edf52ab5da6ccf) | fix | add temporary directory cleanup for Vitest executor | +| [c6176f6df](https://github.com/angular/angular-cli/commit/c6176f6dffdae5c8d8708a1dd20fb51ca72e3c24) | fix | add upfront dependency validation for unit-test runners | +| [63c98741a](https://github.com/angular/angular-cli/commit/63c98741adcd21701b8bc572e9410cc1cf4f91c3) | fix | add webcontainer support for Vitest browser provider | +| [fcdbf6c19](https://github.com/angular/angular-cli/commit/fcdbf6c19b5a8549011aebec9e517feb12a99895) | fix | allow `globals` to be set to false | +| [542d52868](https://github.com/angular/angular-cli/commit/542d528683cc0e51fd5a55ed6dbf43168f7a51a5) | fix | allow custom runner configuration file for unit-test | +| [0505f954d](https://github.com/angular/angular-cli/commit/0505f954dcf3b3339749ff461592d46d8ecc5e23) | fix | allow unit-test progress option passthrough for building | +| [931c62d20](https://github.com/angular/angular-cli/commit/931c62d20915c6c772b61d76ab88657c0858f6bd) | fix | allow unit-test runner config with absolute path | +| [a11dd31f0](https://github.com/angular/angular-cli/commit/a11dd31f0c80a189e7dcb3172c89a731cfc34702) | fix | configure Vitest cache to use Angular cache | +| [abf003268](https://github.com/angular/angular-cli/commit/abf003268c6cb18f0944665b0b3f2794c9469c3e) | fix | correct Vitest builder watch mode execution | +| [f05ffd104](https://github.com/angular/angular-cli/commit/f05ffd104255e86fe93f3736e1430f940cb83007) | fix | correct Vitest coverage include handling for virtual files | +| [cd5c92b99](https://github.com/angular/angular-cli/commit/cd5c92b99a5d8e9cb991a2551f564353c3df0fbe) | fix | correct Vitest coverage reporting for test files | +| [07f712253](https://github.com/angular/angular-cli/commit/07f712253bb6c37d27ae7be9845f497002b0780c) | fix | correctly handle absolute paths and casing in test discovery | +| [bf468e1eb](https://github.com/angular/angular-cli/commit/bf468e1eb1050c60359b6f52692ce0db286982c2) | fix | direct check include file exists in unit-test discovery | +| [50e330d33](https://github.com/angular/angular-cli/commit/50e330d331fc8cfc4c12f7258012305ecb419d2d) | fix | disable glob directory expansion when finding tests | +| [49b65aba8](https://github.com/angular/angular-cli/commit/49b65aba8d7cd2839776e987366b981d7762760c) | fix | disable Vitest test isolation by default | +| [1529595d4](https://github.com/angular/angular-cli/commit/1529595d4a8d8ff9251d1680b1a23bf4ef817db0) | fix | drop support for TypeScript 5.8 | +| [a44f8fa94](https://github.com/angular/angular-cli/commit/a44f8fa94bbf6ce8cdee05552dc56124507c6971) | fix | dynamically select Vitest DOM environment | +| [ae35543af](https://github.com/angular/angular-cli/commit/ae35543af7f5b3a5328c39fd4617d61b48067357) | fix | enhance Vitest config merging and validation | +| [fec106b60](https://github.com/angular/angular-cli/commit/fec106b60553394aab51d713e5437a713085089b) | fix | enhance Vitest dependency externalization and pre-bundling | +| [f7c4a4c1d](https://github.com/angular/angular-cli/commit/f7c4a4c1dcd575dec82cc06597e3d6620b1be729) | fix | enhance Vitest resolution for optimal package loading | +| [ee5e127d5](https://github.com/angular/angular-cli/commit/ee5e127d551269fa9a3e39b9b28e38d7ab35806f) | fix | ensure `ɵgetOrCreateAngularServerApp` is always defined after errors | +| [0830f4fb5](https://github.com/angular/angular-cli/commit/0830f4fb549e2c45b1ef752dd42f002a1347d7c8) | fix | ensure TestBed cleanup hooks are always registered | +| [41b12509a](https://github.com/angular/angular-cli/commit/41b12509a9db8bca637e0c67d21301a75774129c) | fix | ensure TestBed setup is robust in non-isolated Vitest | +| [55145f582](https://github.com/angular/angular-cli/commit/55145f582253b4ecb47add7ff2ef459b7535dfdb) | fix | ensure Vitest setup files are executed in order | +| [3478aa332](https://github.com/angular/angular-cli/commit/3478aa332ef0241c04e7eeef9dd74b017292b2c4) | fix | exclude .angular from coverage instrumentation | +| [7c529c1bc](https://github.com/angular/angular-cli/commit/7c529c1bc606101ab8c506e0b7845d2e9f509db4) | fix | externalize Angular dependencies in Vitest runner | +| [69c3b1226](https://github.com/angular/angular-cli/commit/69c3b1226880835fd8087cea5684ababb92b1c05) | fix | improve error handling in unit-test builder | +| [bab5806c2](https://github.com/angular/angular-cli/commit/bab5806c281fd4cdd63b7969e691d703ed1e7680) | fix | introduce vitest-base.config for test configuration | +| [73621998f](https://github.com/angular/angular-cli/commit/73621998f91db189ad9b1ab006681404e30f7900) | fix | normalize paths for Vitest runner output files | +| [fa5c92346](https://github.com/angular/angular-cli/commit/fa5c92346d14a6ad03aa30ad6392fc649038605e) | fix | prioritize string type for runnerConfig schema | +| [d0787c11d](https://github.com/angular/angular-cli/commit/d0787c11d68841c36ef28bc3f15963406d1209a9) | fix | provide default excludes for vitest coverage | +| [ac10f323e](https://github.com/angular/angular-cli/commit/ac10f323ece9f4a35068e510f10786fbcb15adbb) | fix | relax requirement for files to be in TS compilation | +| [139758586](https://github.com/angular/angular-cli/commit/13975858683421a5712bbfccee57cf141a0b96f6) | fix | remove deprecated `javascriptEnabled` option for Less | +| [6576bb598](https://github.com/angular/angular-cli/commit/6576bb5985c18dca7cecd9509939c2a78bf9758a) | fix | remove explicit test isolation configuration | +| [9132e6af9](https://github.com/angular/angular-cli/commit/9132e6af9fd573d8b39c69a50b4b93e256145fd4) | fix | resolve browser provider packages using project resolver | +| [26127bd3b](https://github.com/angular/angular-cli/commit/26127bd3bb2c4b9aacf2a8f4c2cbdf732512bafb) | fix | resolve PostCSS plugins relative to config file | +| [dae732059](https://github.com/angular/angular-cli/commit/dae732059d17e9e374ac7635fbca9480751f70b3) | fix | serve build assets and styles in vitest | +| [705af2278](https://github.com/angular/angular-cli/commit/705af22788102eeade08404d357582c39de8900b) | fix | set coverage report directory to coverage/project-name | +| [0851d2eae](https://github.com/angular/angular-cli/commit/0851d2eae1e5b854a0a8a7df3a47b00693508a0f) | fix | show full aggregate errors from vitest | +| [cc2668f57](https://github.com/angular/angular-cli/commit/cc2668f5744588f9c3d847d2450dd1361e73c690) | fix | simplify SSL handling for `ng serve` with SSR ([#31723](https://github.com/angular/angular-cli/pull/31723)) | +| [907eabdd3](https://github.com/angular/angular-cli/commit/907eabdd3c7447ed2c211b6d6c2719b04443c545) | fix | support ESM PostCSS plugins | +| [62938e799](https://github.com/angular/angular-cli/commit/62938e79977d14045b7883d459d786dbb8d4d7ee) | fix | update vitest to 4.0.6 and remove coverage workaround | + + + + + +# 20.3.11 (2025-11-19) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [8053f2d92](https://github.com/angular/angular-cli/commit/8053f2d92a68a8bd01854eb81aa472249f5a83b2) | fix | ensure `ɵgetOrCreateAngularServerApp` is always defined after errors | + + + + + +# 20.3.10 (2025-11-12) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [c854a719b](https://github.com/angular/angular-cli/commit/c854a719bb3a8d92fe42c8fba4b0b1789081b21c) | fix | correct `tsconfig.spec.json` include for spec files | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------- | +| [b3908f68e](https://github.com/angular/angular-cli/commit/b3908f68ed2f05cee56cbf0d9895dcfc3dc0ac25) | fix | do not remove `@angular/localize` when having external packages ([#31721](https://github.com/angular/angular-cli/pull/31721)) | + + + + + +# 20.3.9 (2025-11-05) + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [08e07e338](https://github.com/angular/angular-cli/commit/08e07e338edd799400e18cd62cd131b6d408f4cf) | fix | improve locale handling in app-engine | +| [683697ebc](https://github.com/angular/angular-cli/commit/683697ebc5e129d2b17bded9e4ff318d598e0bd3) | fix | improve route matching for wildcard routes | + + + + + +# 20.3.8 (2025-10-29) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [813cba9b9](https://github.com/angular/angular-cli/commit/813cba9b9bfe60e874595ce25608ca85a890f6bf) | fix | expand jest and jest-environment-jsdom to allow version 30 | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [542973ab0](https://github.com/angular/angular-cli/commit/542973ab074ccd9a5f09f73ee7f2706a21db45fc) | fix | add adapters to new reporter | +| [f0885691d](https://github.com/angular/angular-cli/commit/f0885691d18b6575351774fcc50d746d981285f6) | fix | ensure locale data plugin runs before other plugins | +| [45e498f95](https://github.com/angular/angular-cli/commit/45e498f9576ff83eebe02deb235d36498ce06bde) | fix | handle redirects from guards during prerendering | + + + + + +# 19.2.19 (2025-10-29) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [4d8ea27a1](https://github.com/angular/angular-cli/commit/4d8ea27a1726709b8398a26915395e7611571dae) | fix | update vite to v6.4.1 | + + + + + +# 20.3.7 (2025-10-22) + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [a31533cf4](https://github.com/angular/angular-cli/commit/a31533cf492048f62a41b9c09e53779269ee172d) | fix | respect `--force` option when schematic contains `host.create` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [8cdda111c](https://github.com/angular/angular-cli/commit/8cdda111cc0b343aa5eb6a7ccbad93302a543226) | fix | resolve Angular locale data namespace in esbuild | +| [5847ccc54](https://github.com/angular/angular-cli/commit/5847ccc545e54eb77a78b2435db7970faf748156) | fix | update `vite` to `7.11.1` | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------- | +| [3a28fb6a1](https://github.com/angular/angular-cli/commit/3a28fb6a13061215b881c49232db979fc3c2f641) | fix | correctly handle routes with matrix parameters | +| [5db6d6487](https://github.com/angular/angular-cli/commit/5db6d64870c7ce0b883722a07c828946b7d2217d) | fix | ensure server-side navigation triggers a redirect | + + + + + +# 20.3.6 (2025-10-15) + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [5271547c8](https://github.com/angular/angular-cli/commit/5271547c80662de10cb3bcb648779a83f6efedfb) | fix | prevent malicious URL from overriding host | + + + + + +# 19.2.18 (2025-10-15) + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [9136a5d13](https://github.com/angular/angular-cli/commit/9136a5d1302bb224ea245460ae29474bd2a3a10b) | fix | prevent malicious URL from overriding host | + + + + + +# 20.3.5 (2025-10-08) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [7f7140680](https://github.com/angular/angular-cli/commit/7f7140680b75ff6b41f7f04349fe10cd928f1a23) | fix | cleanup karma temporary directory after process exit | + + + + + +# 20.3.4 (2025-10-02) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [c94bf7ff0](https://github.com/angular/angular-cli/commit/c94bf7ff0845fe325c39737057ff1ed4ea553011) | fix | Out of the box support for PM2 | +| [465436c9f](https://github.com/angular/angular-cli/commit/465436c9fa21173befe5e39b61afb7f29435c2aa) | fix | use bracket notation for `process.env['pm_id']` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [bc6b63114](https://github.com/angular/angular-cli/commit/bc6b631146c719a337c937e95c7cc5ebca29254b) | fix | mark `InjectionToken` as pure for improved tree-shaking | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [e510ff828](https://github.com/angular/angular-cli/commit/e510ff828f033478d8e1720050a7b3d75d551e16) | fix | mark `InjectionToken` as pure for improved tree-shaking | + + + + + +# 20.3.3 (2025-09-24) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [b7f92da78](https://github.com/angular/angular-cli/commit/b7f92da7835c14b568d07dfb3313802704f28cfd) | fix | add `__screenshots__/` to `.gitignore` | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [a4c9a2007](https://github.com/angular/angular-cli/commit/a4c9a2007ab3e33b2c97fa63f0df8f8662427031) | fix | avoid retaining rendered HTML in memory post-request | + + + + + +# 20.3.2 (2025-09-17) + + + + + +# 19.2.17 (2025-09-17) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------ | +| [365d525b5](https://github.com/angular/angular-cli/commit/365d525b596b437ad0b1a74b1417eaae6aa8694e) | fix | update `vite` to `6.3.6` | + + + + + +# 20.3.1 (2025-09-11) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [be60be499](https://github.com/angular/angular-cli/commit/be60be4997ea0f7be3a4fb993f87b1bd29fc1493) | fix | add timestamp to bundle generation log | +| [d60f4e53d](https://github.com/angular/angular-cli/commit/d60f4e53d8f511d313e517161dc26eb3cc005f1c) | fix | update vite to version `7.1.5` | + + + + + +# 18.2.21 (2025-09-10) + +## Breaking Changes + +### @angular/ssr + +- The server-side bootstrapping process has been changed to eliminate the reliance on a global platform injector. + + Before: + + ```ts + const bootstrap = () => bootstrapApplication(AppComponent, config); + ``` + + After: + + ```ts + const bootstrap = (context: BootstrapContext) => + bootstrapApplication(AppComponent, config, context); + ``` + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ | +| [700e6bc01](https://github.com/angular/angular-cli/commit/700e6bc0177a3e345a88e31be22496cc3054349b) | fix | avoid extra tick in SSR builds | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [cccc91b91](https://github.com/angular/angular-cli/commit/cccc91b919b4a8365efce9ee691940e351349075) | fix | avoid extra tick in SSR dev-server builds | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [4af385201](https://github.com/angular/angular-cli/commit/4af385201bf8ba05352faec26c6efa866b69d999) | feat | introduce BootstrapContext for isolated server-side rendering | + + + + + +# 19.2.16 (2025-09-10) + +## Breaking Changes + +### @angular/ssr + +- The server-side bootstrapping process has been changed to eliminate the reliance on a global platform injector. + + Before: + + ```ts + const bootstrap = () => bootstrapApplication(AppComponent, config); + ``` + + After: + + ```ts + const bootstrap = (context: BootstrapContext) => + bootstrapApplication(AppComponent, config, context); + ``` + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ | +| [b0f4330a9](https://github.com/angular/angular-cli/commit/b0f4330a9a2f598b71f12d07e49b6c7c6891febd) | fix | avoid extra tick in SSR builds | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [ee5c5f823](https://github.com/angular/angular-cli/commit/ee5c5f823c87a36c9bcb92db2fc9b4e652dc16c2) | fix | avoid extra tick in SSR dev-server builds | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [32980f7e7](https://github.com/angular/angular-cli/commit/32980f7e7a5821bc9bd311dda6e134970e735722) | feat | introduce BootstrapContext for isolated server-side rendering | + + + + + +# 20.3.0 (2025-09-10) + +## Breaking Changes + +### @angular/ssr + +- The server-side bootstrapping process has been changed to eliminate the reliance on a global platform injector. + + Before: + + ```ts + const bootstrap = () => bootstrapApplication(AppComponent, config); + ``` + + After: + + ```ts + const bootstrap = (context: BootstrapContext) => + bootstrapApplication(AppComponent, config, context); + ``` + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------- | +| [ef20a278d](https://github.com/angular/angular-cli/commit/ef20a278d1455b9cdffc5102b13d0b2206ef1ecb) | fix | align labels in ai-config schema | + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [f6ad41c13](https://github.com/angular/angular-cli/commit/f6ad41c134c7ae938ccda908967e7cc863b3db16) | fix | improve bun lockfile detection and optimize lockfile checks | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ | +| [1a7890873](https://github.com/angular/angular-cli/commit/1a789087344aa94d061839122e6a63efbfc9c905) | fix | avoid extra tick in SSR builds | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [5d46d6ec1](https://github.com/angular/angular-cli/commit/5d46d6ec114052715a8bd17761a4f258961ad26b) | fix | preserve names in esbuild for improved debugging in dev mode | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [7eacb4187](https://github.com/angular/angular-cli/commit/7eacb41878f5fdac8d40aedfcca6794b77eda5ff) | feat | introduce BootstrapContext for isolated server-side rendering | + + + + + +# 20.2.2 (2025-09-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [a793bbc47](https://github.com/angular/angular-cli/commit/a793bbc473dfaddf3fe6ed15805dc4fc84f52865) | fix | don't set a default for array options when length is 0 | +| [2736599e2](https://github.com/angular/angular-cli/commit/2736599e2f6c61032810d8e336c1646db4066392) | fix | set process title when running architect commands | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [5c2abffea](https://github.com/angular/angular-cli/commit/5c2abffea6cf3f672ee256a944dba56dd257665b) | fix | avoid extra tick in SSR dev-server builds | +| [f3c826853](https://github.com/angular/angular-cli/commit/f3c826853501c9cf6d07a1c8ee3363eb79f53005) | fix | maintain media output hashing with vitest unit-testing | + + + + + +# 20.2.1 (2025-08-27) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- | +| [3b693e09e](https://github.com/angular/angular-cli/commit/3b693e09e8148ef22031aab8f6bc70c928aabc03) | fix | correctly set default array values | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [6937123a3](https://github.com/angular/angular-cli/commit/6937123a393e2ba9221962b0174056c14437a988) | fix | directly resolve karma config template in migration | +| [5d6dd4425](https://github.com/angular/angular-cli/commit/5d6dd44259a0d89098c2a0c784e726b43ce32316) | fix | prevent AI config schematic from failing when 'none' and other AI tools are selected | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- | +| [e93919dea](https://github.com/angular/angular-cli/commit/e93919dea7df55a3aac2fa5c93c4560c50a2d749) | fix | correctly set default array values | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [06a6ddc10](https://github.com/angular/angular-cli/commit/06a6ddc102f5dc9018ec982f6e4cf56259cc4b52) | fix | correct JS/TS file paths when running under Bazel | +| [b6816b0cb](https://github.com/angular/angular-cli/commit/b6816b0cbaf1262d7015b9d7f7fb425f53995947) | fix | ensure karma polyfills reporter factory returns a value | + + + + + +# 20.2.0 (2025-08-20) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [b4de9a1bf](https://github.com/angular/angular-cli/commit/b4de9a1bf50a35404fb79eb3f120faafd0ce825a) | feat | add --experimental-tool option to mcp command | +| [755ba70fd](https://github.com/angular/angular-cli/commit/755ba70fd7ef38793d15797ba402020c375c3295) | feat | add --local-only option to mcp command | +| [59d7ef343](https://github.com/angular/angular-cli/commit/59d7ef343b6f1feea37a019935578c560d3d5e41) | feat | add --read-only option to mcp command | +| [4e92eb6f1](https://github.com/angular/angular-cli/commit/4e92eb6f17cb30259bc8e8d1979bbd9989bc5ad0) | feat | add modernize tool to the MCP server | +| [a3b25f675](https://github.com/angular/angular-cli/commit/a3b25f675283fdd8cc5689e3ec88f27aa1386390) | fix | add choices to command line parser when type is array and has an enum | +| [e19eee614](https://github.com/angular/angular-cli/commit/e19eee61404a9ca6268ebbc69f671a422d81df9b) | fix | address Node.js deprecation DEP0190 | +| [4ee6f327a](https://github.com/angular/angular-cli/commit/4ee6f327a206f8ff2ad5eeab43193df56b92b5e0) | fix | apply default to array types | +| [8ba6b0bcc](https://github.com/angular/angular-cli/commit/8ba6b0bcc8c8087875d14a0aefc6b7b52f39ce2a) | fix | use correct path for MCP get_best_practices tool | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [2e3cfd598](https://github.com/angular/angular-cli/commit/2e3cfd598c9366d0036a52cd18024317b33e6fca) | feat | add migration to remove default Karma configurations | +| [d80dae276](https://github.com/angular/angular-cli/commit/d80dae276e9554c13e0c37640d0db8acafc9d48b) | feat | add schematics to generate ai context files. | +| [ffe6fb916](https://github.com/angular/angular-cli/commit/ffe6fb916d496da1c6c20942f6e6b05a679b0f7d) | fix | allow AI config prompt to be skipped without selecting a value | +| [ae2802b7d](https://github.com/angular/angular-cli/commit/ae2802b7db358c5a3f0590feea212a768a710353) | fix | improve AI config prompt wording | +| [b017f84fd](https://github.com/angular/angular-cli/commit/b017f84fdaf36bc0fcad2241846665c73b52b6d8) | fix | improve coverage directory handling for Karma configuration comparisons | +| [6a79f9a75](https://github.com/angular/angular-cli/commit/6a79f9a75cdcbb0761c4044066748f4eb788a57f) | fix | zoneless is now stable | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------- | +| [c43504d8d](https://github.com/angular/angular-cli/commit/c43504d8d96a4436ce71c23d957aec2d080106b8) | fix | address Node.js deprecation DEP0190 | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [fb06bb505](https://github.com/angular/angular-cli/commit/fb06bb5050e92eb4d0f95d7774552d0902163f6a) | feat | add headless mode for vitest browser mode | + + + + + +# 20.1.6 (2025-08-13) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [584bc1d41](https://github.com/angular/angular-cli/commit/584bc1d4173e7f129aa20e829f1dfb03e1e0dc9e) | fix | add extra prettier config | +| [02b0506fd](https://github.com/angular/angular-cli/commit/02b0506fde638b89510e5a78b3d190ba60a8d6ba) | fix | correct configure the `typeSeparator` in the library schematic | + + + + + +# 20.1.5 (2025-08-06) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [48ca04474](https://github.com/angular/angular-cli/commit/48ca044745f49bc7fc365a621827294f4cc82c50) | fix | cache MCP best practices content and add tool annotations | + + + + + +# 20.1.4 (2025-07-30) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [2d753cc62](https://github.com/angular/angular-cli/commit/2d753cc62c9a801c40923a43e4af5f74b22700e0) | fix | skip workspace-specific tools when outside a workspace | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [42d72ef4d](https://github.com/angular/angular-cli/commit/42d72ef4d99380dbb1c0e03e3e3abfb2223fa539) | fix | skip vite transformation of CSS-like assets | + + + + + +# 20.1.3 (2025-07-24) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------ | +| [ea5cd0e81](https://github.com/angular/angular-cli/commit/ea5cd0e81196467ea66f50c106cffec1cd8a1a56) | fix | update `vite` to `7.0.6` | + + + + + +# 20.1.2 (2025-07-23) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [96785224f](https://github.com/angular/angular-cli/commit/96785224f55291cd60553aead07ead10d9d2fbda) | fix | `define` option is being included multiple times in the JSON help | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [0d0040bdf](https://github.com/angular/angular-cli/commit/0d0040bdf58a82e18f7669363b6f149313524bfc) | fix | use crypto.randomUUID instead of Date.now for unique string in tmp file names | + + + + + +# 20.1.1 (2025-07-16) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [541b33f8d](https://github.com/angular/angular-cli/commit/541b33f8d977c1fe8f609099a8b8ed1c5f8e827e) | fix | emit a warning when `outputHashing` is set to `all` or `bundles` when HMR is enabled | +| [558a0fe92](https://github.com/angular/angular-cli/commit/558a0fe9275e68e0b768de3ee2e5bee0d6d84a6e) | fix | normalize code coverage include paths to POSIX | + + + + + +# 20.1.0 (2025-07-09) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------- | +| [dc45c186e](https://github.com/angular/angular-cli/commit/dc45c186ec16e345b75ffcd57961a8e0cfd4b649) | feat | add initial MCP server implementation | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------- | +| [1c19e0dcd](https://github.com/angular/angular-cli/commit/1c19e0dcd4a87fbf542201e09a402a8fccdfcd88) | feat | use signal in app component | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [49a09737d](https://github.com/angular/angular-cli/commit/49a09737d5412c302d09b40de198251bb99789d1) | feat | provide partial custom postcss configuration support | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [1159cf081](https://github.com/angular/angular-cli/commit/1159cf08103081d2b851e59bc1c5fb200f114982) | feat | add code coverage reporters option for unit-test | +| [8f305ef0b](https://github.com/angular/angular-cli/commit/8f305ef0ba91ec9bf6417b7084965205cf5488e7) | feat | add dataurl, base64 loaders | +| [adfeee0a4](https://github.com/angular/angular-cli/commit/adfeee0a4c95a03d430054eeecd4cca1bdb0efeb) | fix | adjust coverage includes/excludes for unit-test vitest runner | +| [c19cd2985](https://github.com/angular/angular-cli/commit/c19cd2985cbf1ea8c1c15f020bc530d6768cb0fa) | fix | coverage reporter option | +| [8879716ca](https://github.com/angular/angular-cli/commit/8879716cac9b2134db2795b1810595ea56e9d421) | fix | expose unit test and karma builder API | +| [a415a4999](https://github.com/angular/angular-cli/commit/a415a4999f337f5bc3c0ee626aaba58b6c5ad4e1) | fix | improve default coverage reporter handling for vitest | +| [e0de8680d](https://github.com/angular/angular-cli/commit/e0de8680d1ea25aa71024d7b89beaa1e75889c47) | fix | inject zone.js/testing before karma builder execution | +| [2672f6ec1](https://github.com/angular/angular-cli/commit/2672f6ec17de6e05b19acda0e0b09a6715c9f83f) | fix | json and json-summary as vitest coverage reporters | +| [b67fdfd6b](https://github.com/angular/angular-cli/commit/b67fdfd6bc422bd6a46db923470579c760c5ec27) | fix | resolve "Controller is already closed" error in Karma | +| [2784883ec](https://github.com/angular/angular-cli/commit/2784883ecfb63e4aa6a6c69fd10e457316b4958c) | fix | support extra test setup files with unit-test vitest runner | +| [f177f5508](https://github.com/angular/angular-cli/commit/f177f5508adb23f604d9abb5f4a33f3af5f32561) | fix | support injecting global styles into vitest unit-tests | +| [130c65014](https://github.com/angular/angular-cli/commit/130c650146595f237bc3285302d0075ba0387546) | fix | use an empty array as default value for vitest exclude | +| [917af12ae](https://github.com/angular/angular-cli/commit/917af12aeb82b1437e7b43a03ae80b58a09f0224) | fix | use date/time based output path for vitest unit-test | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [21b5852f1](https://github.com/angular/angular-cli/commit/21b5852f120dd42ea4ae9fce043e04ec61da16dd) | fix | ensure `loadChildren` runs in correct injection context during route extraction | + + + + + +# 20.0.6 (2025-07-09) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------- | +| [5542445d3](https://github.com/angular/angular-cli/commit/5542445d30685a2ebbf66d15848a5abc657863c8) | fix | remove constructor from service template | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [0836ad28f](https://github.com/angular/angular-cli/commit/0836ad28f8e4702f8ba12beef615d60c01a7947c) | fix | correctly remap Angular diagnostics | +| [c475e546b](https://github.com/angular/angular-cli/commit/c475e546bfdfee0c098e5198325b52a53882d034) | fix | exclude `@vitest/browser/context` from esbuild bundling | +| [1a2da161e](https://github.com/angular/angular-cli/commit/1a2da161e73f4f1fe876329adf8ed89f9044b404) | fix | failed to proxy error for assets | + + + + + +# 20.0.5 (2025-07-01) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [1ebd53df7](https://github.com/angular/angular-cli/commit/1ebd53df7168307f699a9f9ae8f5ef5b9bcf352c) | fix | remove unused `@vitejs/plugin-basic-ssl` dependency | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------- | +| [05cebdbcd](https://github.com/angular/angular-cli/commit/05cebdbcd1466bf5c95eb724a784aeb8c7ac083f) | fix | proxy karma request from `/` to `/base` | + + + + + +# 20.0.4 (2025-06-25) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------- | +| [2316fe29d](https://github.com/angular/angular-cli/commit/2316fe29de57c593e5ccb8be612d3918b60d9761) | fix | add missing prettier config | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [309742289](https://github.com/angular/angular-cli/commit/30974228988d7ff96741fe0515c35275e8a6bc0a) | fix | avoid preloading unnecessary dynamic bundles | +| [82691b98f](https://github.com/angular/angular-cli/commit/82691b98fa458febf40a16beb91b24c4b6c519c9) | fix | ensure correct referer header handling in web request conversion | + + + + + +# 20.0.3 (2025-06-18) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [e90a808c0](https://github.com/angular/angular-cli/commit/e90a808c0100beb319bae36ca3b771ee2da89435) | fix | include `main.server.ts` in `tsconfig.files` when present | +| [5c48b8e0a](https://github.com/angular/angular-cli/commit/5c48b8e0ac38a108740ebb290dc1e666ce390806) | fix | reset module `typeSeparator` when generating applications | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [56f426e25](https://github.com/angular/angular-cli/commit/56f426e2548b86c00d4da19b9f7b5cf97dc79104) | fix | include custom bundle name scripts with karma | +| [dfe3a8b73](https://github.com/angular/angular-cli/commit/dfe3a8b7342dd492e42ec48052612255ba76c09b) | fix | increase worker idle timeout | +| [e6d27bd5e](https://github.com/angular/angular-cli/commit/e6d27bd5e3fe64f597621e0d5c08060cea64a302) | fix | set scripts option output as classic script for karma | + + + + + +# 20.0.2 (2025-06-11) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [bf64a0f2d](https://github.com/angular/angular-cli/commit/bf64a0f2dcc2cbd5dc56e575dd337c16f2a3342b) | fix | add `less` as a devDependency when selected as the style preprocessor | +| [cb258a3e1](https://github.com/angular/angular-cli/commit/cb258a3e1525cda985109692fb88449259119ff2) | fix | correctly detect modules using new file extension format | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [424f1cbbf](https://github.com/angular/angular-cli/commit/424f1cbbfb709b4d6f480e6321ec1a152813cf5c) | fix | do not consider internal Angular files as external imports | + + + + + +# 19.2.15 (2025-06-11) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [b120e1411](https://github.com/angular/angular-cli/commit/b120e1411c28c99defb34274a11f0fb54972178a) | fix | update dependency webpack-dev-server to v5.2.2 | + + + + + +# 18.2.20 (2025-06-11) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| ------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [f048078](https://github.com/angular/angular-cli/commit/f048078ab6012b5da4dff024c107f42f79693682) | fix | update dependency webpack-dev-server to v5.2.2 | + + + + + +# 20.0.1 (2025-06-04) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [0883248cb](https://github.com/angular/angular-cli/commit/0883248cbdebcad09393349a0a5d9487b2a452ae) | fix | improve Node.js version check and error messages | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [525ddcbd2](https://github.com/angular/angular-cli/commit/525ddcbd290525e4dac2547c352cf6c774d728a2) | fix | only overwrite JSON file if actually changed | +| [83c820e5a](https://github.com/angular/angular-cli/commit/83c820e5ab55d01662417a51e4cc8d094e409fc6) | fix | remove karma config devkit package usages during application migration | +| [87266b38a](https://github.com/angular/angular-cli/commit/87266b38a09ce783ac6d18f532ebe1f8ae5954c0) | fix | skip zone.js dependency for zoneless applications | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [e5efdc577](https://github.com/angular/angular-cli/commit/e5efdc577be913870b29173345b8194b87420474) | fix | also disable outputMode in vitest unit-tests | +| [5814393db](https://github.com/angular/angular-cli/commit/5814393dbb2f9227ce10f1df77a8deee06c7d1c5) | fix | resolve junit karma reporter output to workspace root | + + + + + +# 20.0.0 (2025-05-28) + +## Breaking Changes + +### @angular/cli + +- Node.js v18 is no longer supported with Angular. + + Before updating a project to Angular v20, the Node.js version must be + at least 20.11.1. For the full list of supported Node.js versions, + see https://angular.dev/reference/versions. + +- Node.js versions from 22.0 to 22.10 are no longer supported + +### @schematics/angular + +- `--server-routing` option has been removed from several schematics. Server routing will be used when using the application builder. + +### @angular-devkit/schematics + +- The `NodePackageLinkTask` has been removed without a replacement. Create a custom task if needed. + + Note: This does not affect application developers. + +### @angular/build + +- TypeScript versions less than 5.8 are no longer supported. + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------- | +| [e03f2b899](https://github.com/angular/angular-cli/commit/e03f2b89992cb1e34a57f9cd5beef77674c116b6) | feat | Add global error listeners to new app generation | +| [1e137ca84](https://github.com/angular/angular-cli/commit/1e137ca848839402bc214fbccdc04243862d01d0) | feat | add migration to update `moduleResolution` to `bundler` | +| [26fd4ea73](https://github.com/angular/angular-cli/commit/26fd4ea73ad2a0148ae587d582134c68a0bf4b86) | feat | add migrations for server rendering updates | +| [5876577af](https://github.com/angular/angular-cli/commit/5876577af163b534846e720b0184558197dce741) | feat | Add prompt for new apps to be zoneless | +| [fdc6291dd](https://github.com/angular/angular-cli/commit/fdc6291dda4903f418667d415b05367390cf829d) | feat | add update migration to keep previous style guide generation behavior | +| [093c5a315](https://github.com/angular/angular-cli/commit/093c5a3152c4282d4afb51df40945283cc94d281) | feat | directly use `@angular/build` in new projects | +| [d6f594fe0](https://github.com/angular/angular-cli/commit/d6f594fe0f8f21d9c0e2abedb5c8433a1aa5c157) | feat | generate applications using TypeScript project references | +| [0ab1ddf63](https://github.com/angular/angular-cli/commit/0ab1ddf632b7305db28a2f87f5c6b099a44669f6) | feat | generate libraries using TypeScript project references | +| [18e13e2ce](https://github.com/angular/angular-cli/commit/18e13e2ceed931d29aa5582980c7d6d1f66c9787) | feat | remove `--server-routing` option | +| [03180fe03](https://github.com/angular/angular-cli/commit/03180fe0358662f8fd3255ad546994da3e3bda9c) | feat | use TypeScript module preserve option for new projects | +| [86d241629](https://github.com/angular/angular-cli/commit/86d241629ff51f0bb5988e81cac8658b01704d49) | fix | add `@angular/ssr` dependency only when `provideServerRendering` import has been updated | +| [9e6b9b537](https://github.com/angular/angular-cli/commit/9e6b9b5379d0448578b3bfb6100852dea7febe75) | fix | add type checking of host bindings to strict config | +| [8654b3fea](https://github.com/angular/angular-cli/commit/8654b3fea4e2ba5af651e6c2a4afddaf6fc42802) | fix | application migration should migrate karma builder package | +| [c557a19ef](https://github.com/angular/angular-cli/commit/c557a19ef4eed9f2d805bb235d3819c69a1aaef6) | fix | avoid empty polyfill option for new zoneless application | +| [90615a88b](https://github.com/angular/angular-cli/commit/90615a88b10535d7f0197008b9d48ceac4409c23) | fix | default component templates to not use `.ng.html` extension | +| [672ae14cd](https://github.com/angular/angular-cli/commit/672ae14cd21d02a3b4727e2febd88747b9e4c684) | fix | drop composite in tsconfig | +| [da6ef626f](https://github.com/angular/angular-cli/commit/da6ef626f960b187a7862f0caa3d8aed38224ac2) | fix | ensure app-shell schematic consistently uses `withAppShell` | +| [f126f8d34](https://github.com/angular/angular-cli/commit/f126f8d34b087dd3a916dfb93cd255aac4d6c309) | fix | ensure module discovery checks for an NgModule decorator | +| [dc2f65999](https://github.com/angular/angular-cli/commit/dc2f65999a64453a26b61c96080b732fdc4147c8) | fix | generate component templates with a `.ng.html` file extension | +| [23fc8e1e1](https://github.com/angular/angular-cli/commit/23fc8e1e176f23442876b086bff52dd5f35abbc0) | fix | generate components without a `.component` extension/type | +| [8d715fa94](https://github.com/angular/angular-cli/commit/8d715fa948d432b18d06bcf42eed3a7681383523) | fix | generate directives without a .directive extension/type | +| [5fc595144](https://github.com/angular/angular-cli/commit/5fc5951440c9306c4349fa3f8dbcb1b584441fe8) | fix | generate guards with a dash type separator | +| [040282d8f](https://github.com/angular/angular-cli/commit/040282d8fd5838266785997442c4f5a269666cf3) | fix | generate interceptors with a dash type separator | +| [070d60fb3](https://github.com/angular/angular-cli/commit/070d60fb383bb14d39f969942641253e54980fcf) | fix | generate modules with a dash type separator | +| [e6083b57b](https://github.com/angular/angular-cli/commit/e6083b57bb5b38db14264253095a9729738d22f2) | fix | generate pipes with a dash type separator | +| [92e193c0b](https://github.com/angular/angular-cli/commit/92e193c0b9a2b85b68d83c5f378d30fc8d10f13e) | fix | generate resolvers with a dash type separator | +| [bc0f07b48](https://github.com/angular/angular-cli/commit/bc0f07b484300848ee81c5719c58909b40f99deb) | fix | generate services without a .service extension/type | +| [ea1143ddd](https://github.com/angular/angular-cli/commit/ea1143ddd801b775828f0b62788f4cce0dd7e9ce) | fix | infer app component name and path in server schematic | +| [bcc0892a6](https://github.com/angular/angular-cli/commit/bcc0892a65f00e68709e84c380f448a5e0fd05e7) | fix | migrate `provideServerRoutesConfig` to `provideServerRendering` | +| [5e8c6494d](https://github.com/angular/angular-cli/commit/5e8c6494d3eb5a0f61e8b07de4c53233147e9d46) | fix | relative tsconfig paths in references | +| [381d35fe4](https://github.com/angular/angular-cli/commit/381d35fe40f062713eac550a12b58c30c1ec33a9) | fix | remove empty `scripts` option value from new applications | +| [148498c2b](https://github.com/angular/angular-cli/commit/148498c2bcd0feb495dc0aa14b6a4555ac01facb) | fix | Remove experimental from zoneless | +| [a910fe9ae](https://github.com/angular/angular-cli/commit/a910fe9ae0423146f6509c5b9c45c88415365c9f) | fix | remove explicit `outputPath` option value from generated applications | +| [901ab60d9](https://github.com/angular/angular-cli/commit/901ab60d9f63fcff17213dbf7fe17e4a46835974) | fix | remove explicit index option from new applications | +| [be6f13ec1](https://github.com/angular/angular-cli/commit/be6f13ec16f01851d38b900dbfc4df7ccfb94d16) | fix | remove setting files tsconfig field with SSR/Server generation | +| [661609e3e](https://github.com/angular/angular-cli/commit/661609e3e583198828baf236338db17b6222f4d8) | fix | set explicit type in library schematic | +| [0f7dc2cd8](https://github.com/angular/angular-cli/commit/0f7dc2cd8f76f928e64e734563a433ff6a0d478c) | fix | skip spec project reference for minimal ng new | +| [3cf6ab0f7](https://github.com/angular/angular-cli/commit/3cf6ab0f77b23b8717e79b7125ea930cb018ebc5) | fix | support using default browser option when not present | +| [b13805a77](https://github.com/angular/angular-cli/commit/b13805a77a5654a352a6c6f760965c326977ff14) | fix | use protected for class member | + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [e513cd4aa](https://github.com/angular/angular-cli/commit/e513cd4aa218e5ab634f05c18b6aa90f223e096c) | fix | add Node.js 24 as supported version | +| [5e90c1b4e](https://github.com/angular/angular-cli/commit/5e90c1b4ec3f1d05ad00f2f854347a5bf8cb0860) | fix | remove Node.js v18 support | +| [787e510dc](https://github.com/angular/angular-cli/commit/787e510dccabf30589194fcefdb74a687dfa3945) | fix | update min Node.js support to 20.19, 22.12, and 24.0 | +| [64732534e](https://github.com/angular/angular-cli/commit/64732534ecb84d702bde2469466a05e765879f9a) | fix | update minimum supported Node.js 22 version to 22.11.0 | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [a42e045ba](https://github.com/angular/angular-cli/commit/a42e045bab3bfbeb0bb69c3406fd0a76ae263cdf) | fix | respect i18nDuplicateTranslation option when duplicates exist | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------- | +| [e6be37601](https://github.com/angular/angular-cli/commit/e6be37601d57f884a1ddf2cc1ddecf51819b9f51) | refactor | remove deprecated `NodePackageLinkTask` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | +| [d6ea6b09f](https://github.com/angular/angular-cli/commit/d6ea6b09f182433f859a78d4a4d38a9db521e593) | feat | add experimental vitest browser support to unit-testing | +| [12def3a2e](https://github.com/angular/angular-cli/commit/12def3a2e907ca8e7d530cea1b39bba90e153144) | feat | add experimental vitest unit-testing support | +| [c1de63300](https://github.com/angular/angular-cli/commit/c1de633007c423cfd9113cc781b5647e59306146) | feat | allow control of source map sources content for application builds | +| [31c81e9c6](https://github.com/angular/angular-cli/commit/31c81e9c6859e68d00828b345d996d1aff431b25) | feat | drop support for TypeScript older than 5.8 | +| [e80963036](https://github.com/angular/angular-cli/commit/e8096303659f4f02ac05fe8f655bb29bc12fda28) | feat | expand browser support policy to widely available Baseline | +| [3c9172159](https://github.com/angular/angular-cli/commit/3c9172159c72f3c8ea116557ba5bf917a15d2f07) | feat | integrate Chrome automatic workspace folders | +| [9b682e625](https://github.com/angular/angular-cli/commit/9b682e62519e761477e6266650239bf58026a9f4) | feat | support a default outputPath option for applications | +| [d067cedf0](https://github.com/angular/angular-cli/commit/d067cedf05051e3a0f237d50306e1e4c881a0328) | feat | support custom resolution conditions with applications | +| [f4be83119](https://github.com/angular/angular-cli/commit/f4be831197010a17394264bc74b1eb385ba95028) | feat | Support Sass package importers | +| [f36a27272](https://github.com/angular/angular-cli/commit/f36a27272f3f7e2673d692d73286280f4c6d357a) | fix | allow a default application `browser` option | +| [f42f5c14c](https://github.com/angular/angular-cli/commit/f42f5c14c0c51d7705bee7b67afc317c45fb9230) | fix | allow component HMR for templates with i18n | +| [e36bf964a](https://github.com/angular/angular-cli/commit/e36bf964a776b04f6a9193387692274865e1630b) | fix | allow TestBed provider configuration with vitest unit-testing | +| [769961e4a](https://github.com/angular/angular-cli/commit/769961e4a9a67f88f8fb4b7de80dea67825219f9) | fix | allow vitest-based unit testing to use watch option | +| [3e24a59a9](https://github.com/angular/angular-cli/commit/3e24a59a9db9f11a80fa616c68be4380c4816ed5) | fix | disable TypeScript `composite` option with Angular compiler | +| [b155ba1dc](https://github.com/angular/angular-cli/commit/b155ba1dcdbc3c506311e4434c37f1b4c77c7572) | fix | enable unit-test builder watch outside CI | +| [7bb1f8747](https://github.com/angular/angular-cli/commit/7bb1f87478d441e35b73b920c8bfcd4376a3422d) | fix | enable unit-test reporters option | +| [05485ede7](https://github.com/angular/angular-cli/commit/05485ede7b472f98120c51f28bd485eeb635bac2) | fix | ensure `com.chrome.devtools.json` is consistently served after initial run | +| [7877d9a97](https://github.com/angular/angular-cli/commit/7877d9a971dbef5025fdb9a40f49f36e9b42569d) | fix | ensure disabled vitest config loading | +| [c8c73185a](https://github.com/angular/angular-cli/commit/c8c73185a66c7c7825e30f7fcedbaacc9ca1c593) | fix | ensure matching coverage excludes with karma on Windows | +| [aec95042b](https://github.com/angular/angular-cli/commit/aec95042b4d690c25645af590788c608b4b353dc) | fix | exclude only source test files with unit-test vitest support | +| [5bea3de4c](https://github.com/angular/angular-cli/commit/5bea3de4cb2ffa26ad04aced22be3ff11f519f92) | fix | invalidate `com.chrome.devtools.json` if project is moved | +| [1cd65a08d](https://github.com/angular/angular-cli/commit/1cd65a08d5278134115f33ff0e666aee420faf8a) | fix | perform testing module cleanup when using Vitest | +| [c51a540ce](https://github.com/angular/angular-cli/commit/c51a540ce4fb191811d2be06a9937f11826b38a5) | fix | provide direct debugging support for unit test builder | +| [c7f2cb596](https://github.com/angular/angular-cli/commit/c7f2cb59684a264bb4ecab2024d8a8c58efbefa7) | fix | provide vitest globals in unit-test builder | +| [d2bfc6bd4](https://github.com/angular/angular-cli/commit/d2bfc6bd4eb0892e9eb6205838158142b716d21c) | fix | revert setup unit-test polyfills before TestBed init | +| [0d40cdecd](https://github.com/angular/angular-cli/commit/0d40cdecd0fdc1b03d2cafcdd5321db0d31b56ee) | fix | setup unit-test polyfills before TestBed init | +| [fa3dc6387](https://github.com/angular/angular-cli/commit/fa3dc6387db971be265c1c5391c71a23c62df15c) | fix | show unit-test error for missing vitest browser package | +| [247cd3352](https://github.com/angular/angular-cli/commit/247cd335217d9997995321b4b235c40480adadb3) | fix | show unit-test error for missing vitest package | +| [eee816f79](https://github.com/angular/angular-cli/commit/eee816f79b4464286dcecc16f53c06be8afd4ccf) | fix | use global unit-test hooks during TestBed init | +| [566de64cb](https://github.com/angular/angular-cli/commit/566de64cbeebeb532db3c0f4ed1dd607c31dedf1) | fix | use virtual module for Karma TestBed initialization | +| [52fbffcd7](https://github.com/angular/angular-cli/commit/52fbffcd7bb129720a10e6bf865e4e3a01f939d6) | fix | warn and remove jsdom launcher when used with karma | +| [5ff6188c4](https://github.com/angular/angular-cli/commit/5ff6188c4330b009201a64a23d0090bfcec0612f) | perf | directly check code for Angular partial linking | + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [fa0a06f9f](https://github.com/angular/angular-cli/commit/fa0a06f9f92b28929fc775074245a0b97c3d9adc) | fix | support using default index option when not present | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------- | +| [33b9de3eb](https://github.com/angular/angular-cli/commit/33b9de3eb1fa596a4d5a975d05275739f2f7b8ae) | feat | expose `provideServerRendering` and remove `provideServerRouting` | +| [cdfc50c29](https://github.com/angular/angular-cli/commit/cdfc50c29a2aa6f32d172b505a0ef09e563dfc59) | feat | stabilize `AngularNodeAppEngine`, `AngularAppEngine`, and `provideServerRouting` APIs | +| [319b8e0c2](https://github.com/angular/angular-cli/commit/319b8e0c2a0cd30ab96576464b4172a1f76a97a6) | fix | manage unhandled errors in zoneless applications | +| [2d11e8e45](https://github.com/angular/angular-cli/commit/2d11e8e45b29cf879ee72ffbcf438198d73ffaba) | fix | return 302 when redirectTo is a function | +| [059c10eb4](https://github.com/angular/angular-cli/commit/059c10eb4df72b0d67f73783826e2bbae611ad35) | fix | SSR should work without `@angular/router` | +| [63428f3f1](https://github.com/angular/angular-cli/commit/63428f3f1e2ffd427011ea8a17b70f8829ae0bdf) | perf | flush headers prior to start rendering the HTML | +| [280693231](https://github.com/angular/angular-cli/commit/280693231e143aa09f841e3179317573a3576545) | perf | optimize response times by introducing header flushing | +| [6bd7b9b4a](https://github.com/angular/angular-cli/commit/6bd7b9b4a59240caa4f19185570aec8263d8a0a7) | perf | optimized request handling performance | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [427bd846f](https://github.com/angular/angular-cli/commit/427bd846f552b393cb969472a05488ac11d47e9f) | fix | disable TypeScript composite option with Angular compiler | + + + + + +# 19.2.14 (2025-05-28) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [a3504fd45](https://github.com/angular/angular-cli/commit/a3504fd45602ec73ce1781e46e6c92b6042a51da) | fix | HMR requires AOT do not show HMR enabled when using JIT | +| [5ce9f96a4](https://github.com/angular/angular-cli/commit/5ce9f96a4efeb4efabe3c161ab596d049a1edd97) | fix | include full metadata for AOT unit-testing | + + + + + +# 19.2.13 (2025-05-21) + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [ad2fb2959](https://github.com/angular/angular-cli/commit/ad2fb29597e22767618d046fef3fb54bf8e95b5d) | fix | remove `background_color` and `theme_color` from manifest | + + + + + +# 19.2.12 (2025-05-14) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [0098c38c6](https://github.com/angular/angular-cli/commit/0098c38c6d77310effa8c647e1bbfb32fb92afc5) | fix | properly handle Node.js `require()` errors with ESM modules | + + + + + +# 19.2.11 (2025-05-07) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [9eaf34405](https://github.com/angular/angular-cli/commit/9eaf344056b8772b623b0bfc27a66ad985941ae6) | fix | correctly set i18n subPath in webpack browser builder | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [cba66a85c](https://github.com/angular/angular-cli/commit/cba66a85c0bb26813d320281072495473a2d14e3) | fix | avoid attempting to watch bundler internal files | +| [009fc3776](https://github.com/angular/angular-cli/commit/009fc377636817a4dc178908245695d5cff29e75) | fix | avoid internal karma request cache for assets | +| [b43da3949](https://github.com/angular/angular-cli/commit/b43da39499ca477a896f7f957debb05ceed1372a) | perf | fix unnecessary esbuild rebuilds | + + + + + +# 19.2.10 (2025-04-30) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------- | +| [067f1cba0](https://github.com/angular/angular-cli/commit/067f1cba031361f71c79b70af143c53c777e9f7d) | fix | update vite to 6.2.7 | + + + + + +# 17.3.17 (2025-04-30) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [743d85bac](https://github.com/angular/angular-cli/commit/743d85bacce03bcc454574e0ffa9f243ff6631dd) | fix | update http-proxy-middleware to v2.0.8 | + + + + + +# 19.2.9 (2025-04-23) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [de52cc2c8](https://github.com/angular/angular-cli/commit/de52cc2c813e49a06828ff9e9ef0543fa63a9929) | fix | update http-proxy-middleware to v3.0.5 | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [cc5229a45](https://github.com/angular/angular-cli/commit/cc5229a4507848d4d2bcf7409ffa56a7c4b2a136) | fix | pass `preserveSymlinks` option to Karma esbuild builder | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [a4e415ea6](https://github.com/angular/angular-cli/commit/a4e415ea6ab204b6d5f5974c6f0a073d66c40faf) | fix | support `getPrerenderParams` for wildcard routes | + + + + + +# 18.2.19 (2025-04-23) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [01cc617bc](https://github.com/angular/angular-cli/commit/01cc617bc0e0a5a30c3b86f679494500a914c574) | fix | update http-proxy-middleware to v3.0.5 | + + + + + +# 19.2.8 (2025-04-16) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [4a8a4a083](https://github.com/angular/angular-cli/commit/4a8a4a0837af6a095a1e4ad6ae07436073324a7a) | fix | include `module` value check when adding custom conditions | +| [00cd0d123](https://github.com/angular/angular-cli/commit/00cd0d1235ed13781689ae4c4636371dab46b493) | fix | prevent nested CSS in components | +| [a297c4153](https://github.com/angular/angular-cli/commit/a297c4153fd72581cbcf8136c9524c415c561f53) | fix | properly resolve transitive external dependencies in vite-dev-server | +| [8ab033e8e](https://github.com/angular/angular-cli/commit/8ab033e8e56d26c75d8871f81291e702b8985adc) | fix | update vite to 6.2.6 | + + + + + +# 19.2.7 (2025-04-09) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [7f1e8c677](https://github.com/angular/angular-cli/commit/7f1e8c6777dbf60e2a3864774a8c4140bb76f640) | fix | include component test metadata in development builds | +| [74cd4edd5](https://github.com/angular/angular-cli/commit/74cd4edd5bbf5ae03a910be036f6e7fa7db35642) | fix | skip normalization of relative externals | + + + + + +# 18.2.18 (2025-04-09) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [4245ca7b4](https://github.com/angular/angular-cli/commit/4245ca7b434e0aa859c805c459ce50238601b940) | fix | update vite to 5.4.17 | + + + + + +# 17.3.16 (2025-04-09) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------- | +| [5aa53b40c](https://github.com/angular/angular-cli/commit/5aa53b40c34e1555548d201f840a5ffc01f14601) | fix | remove undici from dependencies | +| [fce61564d](https://github.com/angular/angular-cli/commit/fce61564ded8c476ef1c257d2844b1a1560af732) | fix | update vite to 5.4.17 | + + + + + +# 19.2.6 (2025-04-02) + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | +| [e5aec562f](https://github.com/angular/angular-cli/commit/e5aec562feb0d293e88d560ea4ec0720e90dbc11) | fix | properly resolve relative schematics when executed from a nested directory | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [76cfd364a](https://github.com/angular/angular-cli/commit/76cfd364a8b398153c09ce29c5672272ac0bce23) | fix | correctly handle `false` value in server option | +| [d69188c6b](https://github.com/angular/angular-cli/commit/d69188c6be2b851e3dfb84e2bd8d209062d7a283) | fix | update vite to 6.2.4 due to a security issues | + + + + + +# 18.2.17 (2025-04-02) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [247ceff7f](https://github.com/angular/angular-cli/commit/247ceff7f7d71901f51dbab1c1a5235d59e45847) | fix | update vite to 5.4.16 due to a security issues | + + + + + +# 17.3.15 (2025-04-02) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [0525fec61](https://github.com/angular/angular-cli/commit/0525fec6183c2972b97a6ad4d57e89aaa478a2de) | fix | update vite to 5.4.16 due to a security issues | + + + + + +# 19.2.5 (2025-03-26) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [20455e2a6](https://github.com/angular/angular-cli/commit/20455e2a64558fcbb11906cb414a99d3976645d6) | fix | correct handling of response/request errors | +| [32b1dcd91](https://github.com/angular/angular-cli/commit/32b1dcd91b9f351bb6baa54f52c81c465185e01b) | fix | handle undefined `getOrCreateAngularServerApp` during error compilation | +| [7552a9fec](https://github.com/angular/angular-cli/commit/7552a9fec971f64ff27d78754ed13654e9a56b43) | fix | normalize karma asset paths before lookup | +| [1eb5b4357](https://github.com/angular/angular-cli/commit/1eb5b43575ab9908122606b94c0aaa53718678aa) | fix | update vite to 6.2.3 | + + + + + +# 18.2.16 (2025-03-26) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [4267a80c5](https://github.com/angular/angular-cli/commit/4267a80c5cd1e9e6aaae0f9090e21c2d71a6887f) | fix | remove `@vitejs/plugin-basic-ssl` from dependencies | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [9c2904d0d](https://github.com/angular/angular-cli/commit/9c2904d0d3a7b2790b27d21c1ff23e6d8a01c4f0) | fix | update vite to 5.4.15 | + + + + + +# 17.3.14 (2025-03-26) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [cb8f859f1](https://github.com/angular/angular-cli/commit/cb8f859f181a325c15b91791c78f5326f22bb7f5) | fix | update vite to 5.4.15 | + + + + + +# 19.2.4 (2025-03-19) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------- | +| [0a4e96bda](https://github.com/angular/angular-cli/commit/0a4e96bda054876332c5603a3bc972c3ec1eb0bf) | fix | replace `@angular/platform-browser-dynamic` with `@angular/platform-browser` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [b0b643e46](https://github.com/angular/angular-cli/commit/b0b643e46f1009be66423fdff568d042717c5e2b) | fix | ensure errors for missing component resources | +| [2cd763e89](https://github.com/angular/angular-cli/commit/2cd763e893788cfb38260d48eef40afa574a6a70) | fix | ensure relative karma stack traces for test failures | + + + + + +# 17.3.13 (2025-03-13) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [22901df02](https://github.com/angular/angular-cli/commit/22901df0261812a3408ff9d7a7690bf6b87ec2a3) | fix | update babel packages | + + + + + +# 19.2.3 (2025-03-13) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [5a739820b](https://github.com/angular/angular-cli/commit/5a739820be5cc7cb25e159a1f2283db92e741f78) | fix | update babel packages | + + + + + +# 18.2.15 (2025-03-13) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [255c8a50d](https://github.com/angular/angular-cli/commit/255c8a50d2214747c8121e963afcd96cbff39293) | fix | update babel packages | + + + + + +# 19.2.2 (2025-03-12) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [0ee24e29b](https://github.com/angular/angular-cli/commit/0ee24e29b9bb24e92ca3159a13a21fac78974fd7) | fix | record analytics for nested schematics | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [4575265f0](https://github.com/angular/angular-cli/commit/4575265f0b6dcfe81a729f60264e148d93302a10) | fix | exclude all entrypoints of a library from prebundling | +| [83fcffbb7](https://github.com/angular/angular-cli/commit/83fcffbb7d2ede1b08b4145dcedd46ef328bb2f8) | fix | handle postcss compilation errors gracefully | +| [78297ee47](https://github.com/angular/angular-cli/commit/78297ee47c9c381b08cd3649d369765c0b73d4f9) | fix | provide `extract-i18n` does not respect | +| [b18b9c8f2](https://github.com/angular/angular-cli/commit/b18b9c8f249df7b79caebc5ffca07198c14b9a72) | fix | remove duplicate prebundling warning | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [e6e8ce960](https://github.com/angular/angular-cli/commit/e6e8ce960a8048e7bfbaafa4ea013bb05d9897aa) | fix | prevent stream draining if `write` does not return a boolean | + + + + + +# 19.2.1 (2025-03-05) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [4c35b5721](https://github.com/angular/angular-cli/commit/4c35b5721b146d3c27f200c2688073c20dbe0a19) | fix | prevent accidental deletion of `main.ts` during application builder migration | +| [d7f9cb578](https://github.com/angular/angular-cli/commit/d7f9cb578d164aba830751cffb035bf8d962eca2) | fix | prevent error when tsconfig file is missing in application builder migration | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [3ebd7ca7c](https://github.com/angular/angular-cli/commit/3ebd7ca7caeb266308856f47af06bea641b1f8e8) | fix | improve error message when configuration is missing | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [c07330967](https://github.com/angular/angular-cli/commit/c0733096797d45a5cd3ffc18f89a5c75a521accb) | fix | allow component HMR with a service worker | +| [c989c91c3](https://github.com/angular/angular-cli/commit/c989c91c37cab9571bdfaa91cbd806acd9cf9d19) | fix | exclude component styles from 'any' and 'all' budget calculations | +| [96e5dcb5f](https://github.com/angular/angular-cli/commit/96e5dcb5f14b8d16520974b80bb531a190be2343) | fix | handle undefined `less` stylesheet sourcemap values | + + + + + +# 19.2.0 (2025-02-26) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [fe8d83a1f](https://github.com/angular/angular-cli/commit/fe8d83a1f6b5e212d6d51d8f042141c3792ed1cf) | fix | add additional checks for application builder usage | +| [adf4ea5d4](https://github.com/angular/angular-cli/commit/adf4ea5d4ccb252132301111153619178c5bdabe) | fix | remove animations module from ng new app | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [ef7ea536f](https://github.com/angular/angular-cli/commit/ef7ea536feae128b9fabaa124cde2bdad3802cba) | feat | add aot option to jest | +| [523d539c6](https://github.com/angular/angular-cli/commit/523d539c6633ab223723162f425e0ef2b7b4ff71) | feat | add aot option to karma | +| [a00a49a65](https://github.com/angular/angular-cli/commit/a00a49a65ae68e6e0f9856d8d0f4d9914031cd05) | feat | add aot to WTR schema | +| [2bae1a9c0](https://github.com/angular/angular-cli/commit/2bae1a9c0c9eff8087b67c7890b87dc1c279c809) | fix | support aot option for karma browser builder | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [11fab9c7d](https://github.com/angular/angular-cli/commit/11fab9c7dde950e46b2a23d239bb9e29b20f5eff) | feat | add application builder karma testing to package | +| [a5fcf8044](https://github.com/angular/angular-cli/commit/a5fcf804428b835cd79bd8fad55c16e614c2be3a) | fix | provide karma stack trace sourcemap support | +| [964fb778b](https://github.com/angular/angular-cli/commit/964fb778b7d9e4811a6987eddc4f0a010bb713f6) | fix | support per component updates of multi-component files | +| [f836be9e6](https://github.com/angular/angular-cli/commit/f836be9e676575fccd4d74eddbc5bf647f7ff1bd) | fix | support Vite `allowedHosts` option for development server | +| [0ddf6aafa](https://github.com/angular/angular-cli/commit/0ddf6aafaa65b3323dc4ee6251d75794ae862ec7) | fix | utilize bazel stamp instead of resolving peer dependency versions | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [9726cd084](https://github.com/angular/angular-cli/commit/9726cd084b76fe605405d562a18d8af91d6657d8) | feat | Add support for route matchers with fine-grained render mode control | + + + + + +# 19.1.9 (2025-02-26) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [2d361e9b0](https://github.com/angular/angular-cli/commit/2d361e9b0ae5409d7ab23f50b089da16497623c1) | fix | always disable JSON stats with dev-server | + + + + + +# 19.1.8 (2025-02-19) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | +| [f76cee637](https://github.com/angular/angular-cli/commit/f76cee6378d1fb103a47c4c9006df344029491c9) | fix | correctly parse and resolve relative schematic collection names on Windows | +| [ceba7739c](https://github.com/angular/angular-cli/commit/ceba7739cc72835d080a3c2246209a635212a607) | fix | prefer installed package as fallback when listing package groups | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [c54b9996a](https://github.com/angular/angular-cli/commit/c54b9996adb23ebc0a5e1e159ac4a9b54cbf2f1a) | fix | pass missing options to Karma esbuild builder | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [2f60a24dd](https://github.com/angular/angular-cli/commit/2f60a24dd76b3345aef666e7a84099863349c53e) | fix | suppress asset missing warning for `/index.html` requests | +| [b8f7952b7](https://github.com/angular/angular-cli/commit/b8f7952b783a83649364107c78f0fb87ac7b3cf3) | fix | update critical CSS inlining to support `autoCsp` | + + + + + +# 19.1.7 (2025-02-12) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [de73b1c0c](https://github.com/angular/angular-cli/commit/de73b1c0c2d5748818d2e94f93f2640d4c6b949c) | fix | include default export for Express app | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [8890a5f76](https://github.com/angular/angular-cli/commit/8890a5f76c252fe383a632880df476e5f63ef931) | fix | always provide Vite client helpers with development server | +| [df1d38846](https://github.com/angular/angular-cli/commit/df1d388465b6f0d3aab5fb4f011cbbe74d3058f4) | fix | configure Vite CORS option | +| [a13a49d95](https://github.com/angular/angular-cli/commit/a13a49d95be61d2a2458962d57318f301dede502) | fix | exclude unmodified files from logs with `--localize` | +| [0826315fa](https://github.com/angular/angular-cli/commit/0826315fac1c3fd2d22aa0ea544bd59ef9ed8781) | fix | handle unlocalizable files correctly in localized prerender | +| [d2e1c8e9f](https://github.com/angular/angular-cli/commit/d2e1c8e9f5c03a410d8204a5f9b11b4ad9cc9eaa) | perf | cache translated i18n bundles for faster builds | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [f5d974576](https://github.com/angular/angular-cli/commit/f5d97457622897b41e73a859dd1f218fa962be15) | fix | accurately calculate content length for static pages with `\r\n` | +| [c26ea1619](https://github.com/angular/angular-cli/commit/c26ea1619095102b21176435af826cf53f0054b1) | fix | properly handle baseHref with protocol | + + + + + +# 17.3.12 (2025-02-12) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- | +| [d83237028](https://github.com/angular/angular-cli/commit/d832370285adccbf955963a5115cf9b9bf54a08d) | fix | update vite to version 5.4.14 | + + + + + +# 19.1.6 (2025-02-05) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [3f7042672](https://github.com/angular/angular-cli/commit/3f704267223d1881ea40e9de4e6381b9d0e43fe6) | fix | remove additional newline after standalone property | +| [e9778dba0](https://github.com/angular/angular-cli/commit/e9778dba0d75e7f528b600d51504a583485bd033) | fix | skip ssr migration when `@angular/ssr` is not a dependency | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [27f833186](https://github.com/angular/angular-cli/commit/27f8331865de35044ddeda7a8c05bb2700b0be6a) | fix | avoid pre-transform errors with Vite pre-bundling | +| [8f6ee7ed9](https://github.com/angular/angular-cli/commit/8f6ee7ed933ea7394e14fe46d141427839008040) | fix | ensure full rebuild after initial error build in watch mode | +| [2b9c00f68](https://github.com/angular/angular-cli/commit/2b9c00f686145a8613dc2ce7f494193622e02625) | fix | prevent fallback to serving main.js for unknown requests | +| [45abd15b7](https://github.com/angular/angular-cli/commit/45abd15b781bb5bb067a7a52e7a809bb9d141c75) | fix | prevent server manifest generation when no server features are enabled | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [5bf5e5fd2](https://github.com/angular/angular-cli/commit/5bf5e5fd20e3c33a274a936dd1ce00e07b860226) | fix | prioritize the first matching route over subsequent ones | + + + + + +# 19.1.5 (2025-01-29) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | +| [14e3a71e4](https://github.com/angular/angular-cli/commit/14e3a71e46e12a556323fff48998794eecab9896) | fix | update library schematic to use `@angular-devkit/build-angular:ng-packagr` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [d53d25fc1](https://github.com/angular/angular-cli/commit/d53d25fc1b80388158643dbdd37aa49b0aa790e0) | fix | allow tailwindcss 4.x as a peer dependency | +| [bd9d379f0](https://github.com/angular/angular-cli/commit/bd9d379f0401a19d527dc896a96b2671b4c4ed76) | fix | disable TypeScript `removeComments` option | +| [e73e9102e](https://github.com/angular/angular-cli/commit/e73e9102e3098882dd76a8dbf800d4ba414e0e27) | fix | handle empty module case to avoid TypeError | +| [bb413456e](https://github.com/angular/angular-cli/commit/bb413456e95a9be49feba95415915ce2ef39b1b5) | fix | keep background referenced HMR update chunks | +| [2011d3428](https://github.com/angular/angular-cli/commit/2011d34286784156b8c09bb8c6d376d8f902bc00) | fix | support template updates that also trigger global stylesheet changes | +| [688019946](https://github.com/angular/angular-cli/commit/688019946358b176eacc872ece72987387a603f1) | fix | update vite to version 6.0.11 | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [94643d54d](https://github.com/angular/angular-cli/commit/94643d54da1ddadcec1c169aa844a716bec612f6) | fix | enhance dynamic route matching for better performance and accuracy | +| [747557aa0](https://github.com/angular/angular-cli/commit/747557aa0aad00f1df2ce7912ab49775e19c60dc) | fix | redirect to locale pathname instead of full URL | +| [bbbc1eb7a](https://github.com/angular/angular-cli/commit/bbbc1eb7a0c295e0bc4aea95b7292ba484f8a360) | fix | rename `provideServerRoutesConfig` to `provideServerRouting` | + + + + + +# 18.2.14 (2025-01-29) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------- | +| [9d34d28ec](https://github.com/angular/angular-cli/commit/9d34d28ec2965e1b9753556b2721d25ab05c655b) | fix | remove unused `vite` dependency | + + + + + +# 18.2.13 (2025-01-29) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [deeaf1883](https://github.com/angular/angular-cli/commit/deeaf18836efddfa1ee56a25e44944ba444d35ac) | fix | correctly select package versions in descending order during `ng add` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- | +| [fdddf2c08](https://github.com/angular/angular-cli/commit/fdddf2c0844081667a09f2ffe0b16f77384959b2) | fix | update vite to version 5.4.14 | + + + + + +# 19.1.4 (2025-01-22) + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [aa6f0d051](https://github.com/angular/angular-cli/commit/aa6f0d051179d31aad2c3be7b79f9fda8de60f34) | fix | ensure collections can be resolved via test runner in pnpm workspaces | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------- | +| [ff8192a35](https://github.com/angular/angular-cli/commit/ff8192a355ca38edb34fb0cfe08ef133629f3f63) | fix | correct path for `/@ng/components` on Windows | +| [14d2f7ca0](https://github.com/angular/angular-cli/commit/14d2f7ca0e930fceeea53d307db79f0e963c1d53) | fix | include extracted routes in the manifest during prerendering | +| [c87a38f5b](https://github.com/angular/angular-cli/commit/c87a38f5b25b3cddd1b2a1ee4b443b10cf03b767) | fix | only issue invalid i18n config error for duplicate `subPaths` with inlined locales | +| [d50788cf9](https://github.com/angular/angular-cli/commit/d50788cf9f799ffbe9ba0edde425e6833623686d) | fix | replace deprecation of `i18n.baseHref` with a warning | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [bcc5fab75](https://github.com/angular/angular-cli/commit/bcc5fab750c0029e16ad91d277f88113a60b7fa1) | fix | prevent route matcher error when SSR routing is not used | +| [9bacf3981](https://github.com/angular/angular-cli/commit/9bacf3981995626cf935cf1620c391338de1c9df) | fix | properly manage catch-all routes with base href | +| [59c757781](https://github.com/angular/angular-cli/commit/59c75778112383816da2f729d5ae80705b5828fa) | fix | unblock route extraction with `withEnabledBlockingInitialNavigation` | + + + + + +# 19.1.3 (2025-01-20) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [7d8c34172](https://github.com/angular/angular-cli/commit/7d8c34172bf29fbf61c0c0114c419903804b6b38) | fix | allow asset changes to reload page on incremental updates | +| [9fa29af37](https://github.com/angular/angular-cli/commit/9fa29af374060a05a19b32d1f0dee954ec70f451) | fix | handle relative `@ng/components` | +| [c4de34703](https://github.com/angular/angular-cli/commit/c4de34703f8b17ac96e66f889fa0e3ffff524831) | fix | perform full reload for templates with `$localize` usage | + + + + + +# 19.1.2 (2025-01-17) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [939d1612a](https://github.com/angular/angular-cli/commit/939d1612add13bab9aed6cc77bce0e17555bfe3b) | fix | perform incremental background file updates with component updates | +| [304027207](https://github.com/angular/angular-cli/commit/30402720707b7a8b9042a6046692d62a768cdc64) | fix | prevent full page reload on HMR updates with SSR enabled | +| [148acbd58](https://github.com/angular/angular-cli/commit/148acbd58a13b1ba8c4a3349bd6042c24a9f93b5) | fix | reset component updates on dev-server index request | + + + + + +# 19.1.1 (2025-01-16) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [298506751](https://github.com/angular/angular-cli/commit/298506751f2b3788fa2def7f7b4012e9e5465047) | fix | resolve HMR-prefixed files in SSR with Vite | + + + + + +# 19.1.0 (2025-01-15) + +## Deprecations + +### @angular/build + +- The `baseHref` option under `i18n.locales` and `i18n.sourceLocale` in `angular.json` is deprecated in favor of `subPath`. + + The `subPath` defines the URL segment for the locale, serving as both the HTML base HREF and the directory name for output. By default, if not specified, `subPath` will use the locale code. + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [02825eec5](https://github.com/angular/angular-cli/commit/02825eec53456384ba5b9c19f25dde3cfc95d796) | feat | use `@angular/build` package in library generation schematic | +| [88431b756](https://github.com/angular/angular-cli/commit/88431b7564d6757898744597a67fcdc178413128) | fix | application migration should migrate ng-packagr builder package | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [2b8a02bac](https://github.com/angular/angular-cli/commit/2b8a02bac098d4ac4f31b0e74bedfc739171e30b) | feat | require build schemas from modules | +| [fe1ae6933](https://github.com/angular/angular-cli/commit/fe1ae6933998104c144b2c8854f362289c8d91c6) | fix | avoid Node.js resolution for relative builder schema | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [ce7c4e203](https://github.com/angular/angular-cli/commit/ce7c4e203d0312d21d4a3d1955f9c97bdf3e06d2) | fix | handle Windows drive letter case insensitivity in path functions | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [2f55209dd](https://github.com/angular/angular-cli/commit/2f55209dd24602bdf8c27ef083f96b5f55166971) | fix | update `Rule` type to support returning a `Promise` of `Tree` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [2c9d7368f](https://github.com/angular/angular-cli/commit/2c9d7368fc30f3488152e35ac468db5f2a9432f2) | feat | add ng-packagr builder to the package | +| [0a570c0c2](https://github.com/angular/angular-cli/commit/0a570c0c2e64c61ce9969975a21c0d9aac8d9f3b) | feat | add support for customizing URL segments with i18n | +| [298b554a7](https://github.com/angular/angular-cli/commit/298b554a7a40465444b4c508e2250ecbf459ea47) | feat | enable component template hot replacement by default | +| [d350f357b](https://github.com/angular/angular-cli/commit/d350f357b2a74df828ec022e03754d59cc680848) | fix | correctly validate locales `subPath` | +| [8aa1ce608](https://github.com/angular/angular-cli/commit/8aa1ce60808c073634237d03045626d379a51183) | fix | handle loaders correctly in SSR bundles for external packages | +| [3b7e6a8c6](https://github.com/angular/angular-cli/commit/3b7e6a8c6e2e018a85b437256040fd9c8161d537) | fix | invalidate component template updates with dev-server SSR | +| [8fa682e57](https://github.com/angular/angular-cli/commit/8fa682e571dbba4bf249ceb3ca490c4ddd4d7fe5) | fix | remove deleted assets from output during watch mode | +| [48cae815c](https://github.com/angular/angular-cli/commit/48cae815cfd0124217c1b5bc8c92dfdb0b150101) | fix | skip vite SSR warmup file configuration when SSR is disabled | +| [ba16ad6b5](https://github.com/angular/angular-cli/commit/ba16ad6b56e9a1ae0f380141bc1e1253a75fcf6b) | fix | support incremental build file results in watch mode | +| [955acef3d](https://github.com/angular/angular-cli/commit/955acef3d504ac924bd813f401fa9b49edbd337b) | fix | trigger browser reload on asset changes with Vite dev server | +| [e74300a2c](https://github.com/angular/angular-cli/commit/e74300a2cbc666482992fa8d6dbfeef37f3a9db5) | fix | use component updates for component style HMR | +| [6a19c217e](https://github.com/angular/angular-cli/commit/6a19c217eaebf9c0bffba8482545efc375fd2a8a) | fix | warn when using both `isolatedModules` and `emitDecoratorMetadata` | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------- | +| [8d7a51dfc](https://github.com/angular/angular-cli/commit/8d7a51dfc9658aa2f0f0c527435c05c2b10f34e5) | feat | add `modulepreload` for lazy-loaded routes | +| [41ece633b](https://github.com/angular/angular-cli/commit/41ece633b3d42ef110bf6085fe0783ab2a56efcd) | feat | redirect to preferred locale when accessing root route without a specified locale | +| [3feecddbb](https://github.com/angular/angular-cli/commit/3feecddbba0d0559da10a45ad4040faf8e9d5198) | fix | disable component boostrapping when running route extraction | +| [6edb90883](https://github.com/angular/angular-cli/commit/6edb90883733040d77647cf24dea7f53b1b6ceaa) | fix | throw error when using route matchers | + + + + + +# 19.0.7 (2025-01-08) + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [95c22aeff](https://github.com/angular/angular-cli/commit/95c22aeff4560a199416a20c3622301c5c690119) | fix | provide better error when builder is not defined | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- | +| [028652992](https://github.com/angular/angular-cli/commit/028652992f0f9cc6fec5de35be7ecf74ec3947c5) | fix | preserve css type for jasmine.css | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [f7522342a](https://github.com/angular/angular-cli/commit/f7522342a8dd99543422629db6dcf1228c5d7279) | fix | add asset tracking to application builder watch files | +| [e973643bf](https://github.com/angular/angular-cli/commit/e973643bfbe47447ca522ca59b07a94fe6c03e0b) | fix | do not mark Babel \_defineProperty helper function as pure | +| [881095eec](https://github.com/angular/angular-cli/commit/881095eec5cdc80fe79de8fdbe05ba985bf8210a) | fix | enable serving files with bundle-like names | +| [db10af0b3](https://github.com/angular/angular-cli/commit/db10af0b3a775619ac71acdd0258cc58e96e948c) | fix | fix incorrect budget calculation | +| [c822f8f15](https://github.com/angular/angular-cli/commit/c822f8f154b75a8b8e48e32953bee7ec2763fd13) | fix | handle relative URLs when constructing new URLs during server fetch | +| [b43c648b0](https://github.com/angular/angular-cli/commit/b43c648b02c181c1d98cd3293d89ad8b131a3f51) | fix | mitigate JS transformer worker execArgv errors | +| [1f2481a4f](https://github.com/angular/angular-cli/commit/1f2481a4f5155368aa571fc6679e3ef8af0ce56f) | fix | pass `define` option defined in application builder to Vite prebundling | +| [c94f568a4](https://github.com/angular/angular-cli/commit/c94f568a412384bb8e4b66928f41b60220c8b7f4) | fix | warn when `@angular/localize/init` is imported directly | + + + + + +# 19.0.6 (2024-12-18) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ | +| [db7421231](https://github.com/angular/angular-cli/commit/db7421231c3da7bbbfde72dc35642aaf005fbeca) | fix | jasmine.clock with app builder | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [5fbc105ed](https://github.com/angular/angular-cli/commit/5fbc105ed0cb76106916660d99fc53d7480dcbc8) | fix | force HTTP/1.1 in dev-server SSR with SSL | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [2f4df6b2b](https://github.com/angular/angular-cli/commit/2f4df6b2be458b3651df49f3bced923e8df4d547) | fix | correctly resolve pre-transform resources in Vite SSR without AppEngine | +| [0789a9e13](https://github.com/angular/angular-cli/commit/0789a9e133fed4edc29b108630b2cf91e157127e) | fix | ensure correct `Location` header for redirects behind a proxy | + + + + + +# 19.0.5 (2024-12-12) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [6c319e44c](https://github.com/angular/angular-cli/commit/6c319e44c707b93e430da93fe815ba839ab18ea1) | fix | fix webpack config transform for karma | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------- | +| [251bd9f22](https://github.com/angular/angular-cli/commit/251bd9f226f73529e824b131fa8d08b77aa00d09) | fix | Fixing auto-csp edge cases where | +| [1047b8635](https://github.com/angular/angular-cli/commit/1047b8635699d55886fff28cbf02d36df224958d) | fix | handle external `@angular/` packages during SSR ([#29094](https://github.com/angular/angular-cli/pull/29094)) | +| [376ee9966](https://github.com/angular/angular-cli/commit/376ee996699a9610984f3d3e36b3331557dbeaca) | fix | provide component HMR update modules to dev-server SSR | +| [5ea9ce376](https://github.com/angular/angular-cli/commit/5ea9ce3760a191d13db08f5ae7448ce089e8eacd) | fix | use consistent path separators for template HMR identifiers | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [b3c6c7eb2](https://github.com/angular/angular-cli/commit/b3c6c7eb2cc796796d99758368706b0b8682ae69) | fix | include `Content-Language` header when locale is set | +| [4203efb90](https://github.com/angular/angular-cli/commit/4203efb90a38fe2f0d45fabab80dc736e8ca2b7b) | fix | disable component bootstrapping during route extraction | + + + + + +# 19.0.4 (2024-12-05) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ | +| [23667ed4a](https://github.com/angular/angular-cli/commit/23667ed4aa0bedbb591dc0284116402dc42ed95c) | fix | handle windows spec collisions | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [fc41f50b5](https://github.com/angular/angular-cli/commit/fc41f50b53bbffead017b420105eed5bd8573ac1) | fix | show error when Node.js built-ins are used during `ng serve` | +| [14451e275](https://github.com/angular/angular-cli/commit/14451e2754caff2c9800cca17e11ffa452575f09) | perf | reuse TS package.json cache when rebuilding | + + + + + +# 19.0.3 (2024-12-04) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [4e82ca180](https://github.com/angular/angular-cli/commit/4e82ca180b330199b3dffadd9d590c8245dc7785) | fix | correctly select package versions in descending order during `ng add` | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------- | +| [28a51cc5e](https://github.com/angular/angular-cli/commit/28a51cc5e4a08f9e9627a1ec160ce462d18b88d2) | fix | add required type to `CanDeactivate` guard ([#29004](https://github.com/angular/angular-cli/pull/29004)) | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [f26e1b462](https://github.com/angular/angular-cli/commit/f26e1b462ab012b0863f0889bcd60f5e07ca6fd2) | fix | add timeout to route extraction | +| [ab4e77c75](https://github.com/angular/angular-cli/commit/ab4e77c75524d42485ac124f4786ab54bc6c404a) | fix | allow .json file replacements with application builds | +| [06690d87e](https://github.com/angular/angular-cli/commit/06690d87eb590853eed6166857c9c1559d38d260) | fix | apply define option to JavaScript from scripts option | +| [775e6f780](https://github.com/angular/angular-cli/commit/775e6f7808e6edb89d29b72ee5bdc6d2b26cb30e) | fix | avoid deploy URL usage on absolute preload links | +| [21f21eda3](https://github.com/angular/angular-cli/commit/21f21eda39c62e284c6cbee0d0ebfe271f605239) | fix | ensure correct handling of `index.output` for SSR | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [75cf47e71](https://github.com/angular/angular-cli/commit/75cf47e71b0584e55750d5350932494f689a7e96) | fix | apply HTML transformation to CSR responses | +| [5880a0230](https://github.com/angular/angular-cli/commit/5880a02306d9f81f030fcdc91fc6aaeb1986e652) | fix | correctly handle serving of prerendered i18n pages | +| [277b8a378](https://github.com/angular/angular-cli/commit/277b8a3786d40cb8477287dcb3ef191ec8939447) | fix | ensure compatibility for `Http2ServerResponse` type | + + + + + +# 19.0.2 (2024-11-25) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------- | +| [2f53e2af5](https://github.com/angular/angular-cli/commit/2f53e2af55794795979232b0f3e95359299e1aee) | fix | skip SSR routing prompt in webcontainer | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [f9da163f8](https://github.com/angular/angular-cli/commit/f9da163f8852800763844ae89e85eaafe0c37f2b) | fix | minimize reliance on esbuild `inject` to prevent code reordering | +| [c497749e6](https://github.com/angular/angular-cli/commit/c497749e670e916e17a4e7fb0acb1abe26d9bd9a) | fix | prevent errors with parameterized routes when `getPrerenderParams` is undefined | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [c8cd90e0f](https://github.com/angular/angular-cli/commit/c8cd90e0f601a6baa05b84e45bbd37b4bf6049f5) | fix | handle nested redirects not explicitly defined in router config | + + + + + +# 19.0.1 (2024-11-21) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [b63123f20](https://github.com/angular/angular-cli/commit/b63123f20702bd53ea99888b83b4253810ae0a09) | fix | use stylePreprocessorOptions | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [74461da64](https://github.com/angular/angular-cli/commit/74461da6439b70b5348c99682842ae20043d9b61) | fix | ensure accurate content length for server assets | +| [1b4dcedd5](https://github.com/angular/angular-cli/commit/1b4dcedd594b5d9a1701cd8d1e9874742c05e47f) | fix | use `sha256` instead of `sha-256` as hash algorithm name | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [8bd2b260e](https://github.com/angular/angular-cli/commit/8bd2b260e2008f1ffc71af0e55b27884c3660c54) | fix | handle baseHref that start with `./` | + + + + + +# 19.0.0 (2024-11-19) + +## Breaking Changes + +### @schematics/angular + +- The app-shell schematic is no longer compatible with Webpack-based builders. + +### @angular-devkit/build-angular + +- The `browserTarget` option has been removed from the DevServer and ExtractI18n builders. `buildTarget` is to be used instead. +- Protractor is no longer supported. + + Protractor was marked end-of-life in August 2023 (see https://protractortest.org/). Projects still relying on Protractor should consider migrating to another E2E testing framework, several support solid migration paths from Protractor. + - https://angular.dev/tools/cli/end-to-end + - https://blog.angular.dev/the-state-of-end-to-end-testing-with-angular-d175f751cb9c + +### @angular-devkit/core + +- The deprecated `fileBuffer` function is no longer available. Update your code to use `stringToFileBuffer` instead to maintain compatibility. + + **Note:** that this change does not affect application developers. + +### @angular/build + +- The `@angular/localize/init` polyfill will no longer be added automatically to projects. To prevent runtime issues, ensure that this polyfill is manually included in the "polyfills" section of your "angular.json" file if your application relies on Angular localization features. + +### @angular/ssr + +- The `CommonEngine` API now needs to be imported from `@angular/ssr/node`. + + **Before** + + ```ts + import { CommonEngine } from '@angular/ssr'; + ``` + + **After** + + ```ts + import { CommonEngine } from '@angular/ssr/node'; + ``` + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [37693c40e](https://github.com/angular/angular-cli/commit/37693c40e3afc4c6dd7c949ea658bdf94146c9d8) | feat | add package manager option to blank schematic | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | +| [a381a3db1](https://github.com/angular/angular-cli/commit/a381a3db187f7b20e5ec8d1e1a1f1bd860426fcd) | feat | add option to export component as default | +| [755f3a07f](https://github.com/angular/angular-cli/commit/755f3a07f5fe485c1ed8c0c6060d6d5c799c085c) | feat | add option to setup new workspace or application as zoneless mode | +| [cfca5442e](https://github.com/angular/angular-cli/commit/cfca5442ec01cc4eff4fe75822eb7ef780ccfef1) | feat | integrate `withEventReplay()` in `provideClientHydration` for new SSR apps | +| [292a4b7c2](https://github.com/angular/angular-cli/commit/292a4b7c2f62828606c42258db524341f4a6391e) | feat | update app-shell and ssr schematics to adopt new Server Rendering API | +| [b1504c3bc](https://github.com/angular/angular-cli/commit/b1504c3bcca4d4c313e5d795ace8b074fb1f8890) | fix | component spec with export default | +| [4b4e000dd](https://github.com/angular/angular-cli/commit/4b4e000dd60bb43df5c8694eb8a0bc0b45d1cf8d) | fix | don't show server routing prompt when using `browser` builder | +| [4e2a5fe15](https://github.com/angular/angular-cli/commit/4e2a5fe155006e7154326319ed39e77e5693d9b3) | fix | enable opt-in for new `@angular/ssr` feature | +| [fcf7443d6](https://github.com/angular/angular-cli/commit/fcf7443d626d1f3e828ebfad464598f7b9ddef12) | fix | explicitly set standalone:false | +| [7992218a9](https://github.com/angular/angular-cli/commit/7992218a9c22ea9469bd3386c7dc1d5efc6e51f9) | fix | remove `declaration` and `sourceMap` from default tsconfig | +| [9e6ab1bf2](https://github.com/angular/angular-cli/commit/9e6ab1bf231b35aceb989337fac55a6136594c5d) | fix | use default import for `express` | + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [201b60e1d](https://github.com/angular/angular-cli/commit/201b60e1dd25b4abb7670e21d103b67d4eda0e14) | feat | handle string key/value pairs, e.g. --define | +| [b847d4460](https://github.com/angular/angular-cli/commit/b847d4460c352604e1935d494efd761915caaa3f) | fix | recommend optional application update migration during v19 update | +| [f249e7e85](https://github.com/angular/angular-cli/commit/f249e7e856bf16e8c5f154fdb8aff36421649a1b) | perf | enable Node.js compile code cache when available | +| [ecc107d83](https://github.com/angular/angular-cli/commit/ecc107d83bfdfd9d5dd1087e264892d60361625c) | perf | enable Node.js compile code cache when available | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- | +| [78f76485f](https://github.com/angular/angular-cli/commit/78f76485fe315ffd0262c1a3732092731235828b) | feat | merge object options from CLI | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------- | +| [0a4ef3026](https://github.com/angular/angular-cli/commit/0a4ef302635e4665ae9881746867dd80ca0d2dc7) | feat | karma-coverage w/ app builder | +| [dcbdca85c](https://github.com/angular/angular-cli/commit/dcbdca85c7fe1a7371b8f6662e0f68e24d56102e) | feat | karma+esbuild+watch | +| [54594b5ab](https://github.com/angular/angular-cli/commit/54594b5abfa4c9301cc369e5dea5f76b71e51ab0) | feat | support karma with esbuild | +| [ea5ae68da](https://github.com/angular/angular-cli/commit/ea5ae68da9e7f2b598bae2ca9ac8be9c20ce7888) | fix | bring back style tags in browser builder | +| [476f94f51](https://github.com/angular/angular-cli/commit/476f94f51a3403d03ceb9f58ffb4a3564cc52e5a) | fix | fix --watch regression in karma | +| [25d928b4f](https://github.com/angular/angular-cli/commit/25d928b4fde00ae8396f6b9cfcd92b5254fc49aa) | fix | fix hanging terminal when `browser-sync` is not installed | +| [2ec877dd0](https://github.com/angular/angular-cli/commit/2ec877dd0dede8f3ee849fe83b4a4427bab96447) | fix | handle basename collisions | +| [ab6e19e1f](https://github.com/angular/angular-cli/commit/ab6e19e1f9a8781334821048522abe86b149c9c3) | fix | handle main field | +| [43e7aae22](https://github.com/angular/angular-cli/commit/43e7aae2284ff15e0282c9d9597c4f31cf1f60a4) | fix | remove double-watch in karma | +| [1e37b5939](https://github.com/angular/angular-cli/commit/1e37b59396a2f815d1671ccecc03ff8441730391) | fix | serve assets | +| [9d7613db9](https://github.com/angular/angular-cli/commit/9d7613db9bf8b397d5896fcdf6c7b0efeaffa5d5) | fix | zone.js/testing + karma + esbuild | +| [e40384e63](https://github.com/angular/angular-cli/commit/e40384e637bc6f92c28f4e572f986ca902938ba2) | refactor | remove deprecated `browserTarget` | +| [62877bdf2](https://github.com/angular/angular-cli/commit/62877bdf2b0449d8c12a167c59d0c24c77467f37) | refactor | remove Protractor builder and schematics | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------ | +| [0d8a1006d](https://github.com/angular/angular-cli/commit/0d8a1006d4629d8af1144065ea237ab30916e66e) | refactor | remove deprecated `fileBuffer` function in favor of `stringToFileBuffer` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- | +| [b6951f448](https://github.com/angular/angular-cli/commit/b6951f4482418f65e4bd1c15d5f7f051c91d59db) | feat | add `sass` to `stylePreprocessorOptions` in application builder | +| [efb434136](https://github.com/angular/angular-cli/commit/efb434136d8c8df207747ab8fd87b7e2116b7106) | feat | Auto-CSP support as a part of angular.json schema | +| [816e3cb86](https://github.com/angular/angular-cli/commit/816e3cb868961c57a68783601b919370076c41dc) | feat | enable component stylesheet hot replacement by default | +| [3b00fc908](https://github.com/angular/angular-cli/commit/3b00fc908d4f07282e89677928e00665c8578ab5) | feat | introduce `outputMode` option to the application builder | +| [7d883a152](https://github.com/angular/angular-cli/commit/7d883a152e978112245a98f2f737764caa76ec0f) | feat | introduce `ssr.experimentalPlatform` option | +| [c48d6947e](https://github.com/angular/angular-cli/commit/c48d6947ed17eab19822a97492e3686bcf059494) | feat | set development/production condition | +| [f63072668](https://github.com/angular/angular-cli/commit/f63072668e44254da78170445ac2417c7bc1aa18) | feat | utilize `ssr.entry` during prerendering to enable access to local API routes | +| [bbc290133](https://github.com/angular/angular-cli/commit/bbc290133fc93186980ca3c43f221847ba8e858a) | feat | utilize `ssr.entry` in Vite dev-server when available | +| [5a7a2925b](https://github.com/angular/angular-cli/commit/5a7a2925b1f649eabbeb0a75452978cddb3f243d) | fix | add missing redirect in SSR manifest | +| [06e5176c2](https://github.com/angular/angular-cli/commit/06e5176c2d3b27aaeb117374a8ae402c6a4c6319) | fix | add warning when `--prerendering` or `--app-shell` are no-ops | +| [ecaf870b5](https://github.com/angular/angular-cli/commit/ecaf870b5cddd5d43d297f1193eb11b8f73757c0) | fix | always clear dev-server error overlay on non-error result | +| [f8677f6a9](https://github.com/angular/angular-cli/commit/f8677f6a9ba155b04c692814a1bc13f5cc47d94d) | fix | always record component style usage for HMR updates | +| [099e477a8](https://github.com/angular/angular-cli/commit/099e477a8f1bbcf9d0f415dc6fd4743107c967f7) | fix | avoid hashing development external component stylesheets | +| [3602bbb77](https://github.com/angular/angular-cli/commit/3602bbb77b8924e89978427d9115f0b1fd7d46b7) | fix | avoid overwriting inline style bundling additional results | +| [71534aadc](https://github.com/angular/angular-cli/commit/71534aadc403404e2dc9bc12054f32c3ed157db9) | fix | check referenced files against native file paths | +| [fed31e064](https://github.com/angular/angular-cli/commit/fed31e064611894934c86ed36e8b0808029d4143) | fix | correctly use dev-server hmr option to control stylesheet hot replacement | +| [b86bb080e](https://github.com/angular/angular-cli/commit/b86bb080e3a58a3320b2f68fb79edcdc98bfa7e9) | fix | disable dev-server websocket when live reload is disabled | +| [7c50ba9e2](https://github.com/angular/angular-cli/commit/7c50ba9e2faca445c196c69e972ac313547dda54) | fix | ensure `index.csr.html` is always generated when prerendering or SSR are enabled | +| [efb2232df](https://github.com/angular/angular-cli/commit/efb2232df5475699a44d0f76a70e2d7de4a71f5a) | fix | ensure accurate content size in server asset metadata | +| [18a8584ea](https://github.com/angular/angular-cli/commit/18a8584ead439d044760fe2abb4a7f657a0b10e3) | fix | ensure SVG template URLs are considered templates with external stylesheets | +| [7502fee28](https://github.com/angular/angular-cli/commit/7502fee28a057b53e60b97f55b5aff5281019f1b) | fix | Exclude known `--import` from execArgv when spawning workers | +| [2551df533](https://github.com/angular/angular-cli/commit/2551df533d61400c0fda89db77a93378480f5047) | fix | fully disable component style HMR in JIT mode | +| [c41529cc1](https://github.com/angular/angular-cli/commit/c41529cc1d762cf508eccf46c44256df21afe24f) | fix | handle `APP_BASE_HREF` correctly in prerendered routes | +| [87a90afd4](https://github.com/angular/angular-cli/commit/87a90afd4600049b184b32f8f92a0634e25890c0) | fix | incomplete string escaping or encoding | +| [1bb68ba68](https://github.com/angular/angular-cli/commit/1bb68ba6812236a135c1599031bf7e1b7e0d1d79) | fix | move lmdb to optionalDependencies | +| [a995c8ea6](https://github.com/angular/angular-cli/commit/a995c8ea6d17778af031c2f9797e52739ea4dc81) | fix | prevent prerendering of catch-all routes | +| [1654acf0f](https://github.com/angular/angular-cli/commit/1654acf0ff3010b619a22d11f17eec9975d8e2a2) | fix | relax constraints on external stylesheet component id | +| [0d4558ea5](https://github.com/angular/angular-cli/commit/0d4558ea516a4b8716f2442290e05354c502a49e) | fix | set `ngServerMode` during vite prebundling | +| [55d7f01b6](https://github.com/angular/angular-cli/commit/55d7f01b66f4867aad4598574582e8505f201c82) | fix | simplify disabling server features with `--no-server` via command line | +| [cf0228b82](https://github.com/angular/angular-cli/commit/cf0228b828fc43b1b33d48dc0977ff59abb597c2) | fix | skip wildcard routes from being listed as prerendered routes | +| [af52fb49b](https://github.com/angular/angular-cli/commit/af52fb49bdd913af8af9bfbe36be279fce70de39) | fix | synchronize import/export conditions between bundler and TypeScript | +| [6c618d495](https://github.com/angular/angular-cli/commit/6c618d495c54394eb2b87aee36ba5436c06557bd) | fix | update logic to support both internal and external SSR middlewares | +| [bfa8fec9b](https://github.com/angular/angular-cli/commit/bfa8fec9b17d421925a684e2b642dee70165a879) | fix | use named export `reqHandler` for server.ts request handling | +| [c8e1521a2](https://github.com/angular/angular-cli/commit/c8e1521a2bd5b47c811e5d7f9aea7f57e92a4552) | fix | workaround Vite CSS ShadowDOM hot replacement | +| [d6a34034d](https://github.com/angular/angular-cli/commit/d6a34034d7489c09753090642ade4c606cd98ece) | refactor | remove automatic addition of `@angular/localize/init` polyfill and related warnings | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- | +| [92209dd2e](https://github.com/angular/angular-cli/commit/92209dd2e93af450e3fc657609efe95c6a6b3963) | feat | add `createRequestHandler` and `createNodeRequestHandler `utilities | +| [41fb2ed86](https://github.com/angular/angular-cli/commit/41fb2ed86056306406832317178ca5d94aa110e2) | feat | Add `getHeaders` Method to `AngularAppEngine` and `AngularNodeAppEngine` for handling pages static headers | +| [f346ee8a8](https://github.com/angular/angular-cli/commit/f346ee8a8819bb2eaf0ffb3d5523b00093be09e5) | feat | add `isMainModule` function | +| [d66aaa3ca](https://github.com/angular/angular-cli/commit/d66aaa3ca458e05b535bec7c1dcb98b0e9c5202e) | feat | add server routing configuration API | +| [bca568389](https://github.com/angular/angular-cli/commit/bca56838937f942c5ef902f5c98d018582188e84) | feat | dynamic route resolution using Angular router | +| [30c25bf68](https://github.com/angular/angular-cli/commit/30c25bf6885fefea6094ec1815e066e4c6ada097) | feat | export `AngularAppEngine` as public API | +| [455b5700c](https://github.com/angular/angular-cli/commit/455b5700c29845829235e17efec320e634553108) | feat | expose `writeResponseToNodeResponse` and `createWebRequestFromNodeRequest` in public API | +| [9692a9054](https://github.com/angular/angular-cli/commit/9692a9054c3cdbf151df01279c2d268332b1a032) | feat | improve handling of aborted requests in `AngularServerApp` | +| [576ff604c](https://github.com/angular/angular-cli/commit/576ff604cd739a9f41d588fa093ca2568e46826c) | feat | introduce `AngularNodeAppEngine` API for Node.js integration | +| [3c9697a8c](https://github.com/angular/angular-cli/commit/3c9697a8c34a5e0f3470bde73f11f9f32107f42e) | feat | introduce new hybrid rendering API | +| [4b09887a9](https://github.com/angular/angular-cli/commit/4b09887a9c82838ccb7a6c95d66225c7875e562b) | feat | move `CommonEngine` API to `/node` entry-point | +| [d43180af5](https://github.com/angular/angular-cli/commit/d43180af5f3e7b29387fd06625bd8e37f3ebad95) | fix | add missing peer dependency on `@angular/platform-server` | +| [74b3e2d51](https://github.com/angular/angular-cli/commit/74b3e2d51c6cf605abd05da81dc7b4c3ccd9b3ea) | fix | add validation to prevent use of `provideServerRoutesConfig` in browser context | +| [2640bf7a6](https://github.com/angular/angular-cli/commit/2640bf7a680300acf18cf6502c57a00e0a5bfda9) | fix | correct route extraction and error handling | +| [44077f54e](https://github.com/angular/angular-cli/commit/44077f54e9a95afa5c1f85cf198aaa3412ee08d8) | fix | designate package as side-effect free | +| [df4e1d360](https://github.com/angular/angular-cli/commit/df4e1d3607c2d5bf71d1234fa730e63cd6ab594b) | fix | enable serving of prerendered pages in the App Engine | +| [0793c78cf](https://github.com/angular/angular-cli/commit/0793c78cfcbfc5d55fe6ce2cb53cada684bcb8dc) | fix | ensure wildcard RenderMode is applied when no Angular routes are defined | +| [65b6e75a5](https://github.com/angular/angular-cli/commit/65b6e75a5dca581a57a9ac3d61869fdd20f7dc2e) | fix | export `RESPONSE_INIT`, `REQUEST`, and `REQUEST_CONTEXT` tokens | +| [4ecf63a77](https://github.com/angular/angular-cli/commit/4ecf63a777871bf214bf42fe1738c206bde3201c) | fix | export PrerenderFallback | +| [50df63196](https://github.com/angular/angular-cli/commit/50df631960550049e7d1779fd2c8fbbcf549b8ef) | fix | improve handling of route mismatches between Angular server routes and Angular router | +| [3cf7a5223](https://github.com/angular/angular-cli/commit/3cf7a522318e34daa09f29133e8c3444f154ca0b) | fix | initialize the DI tokens with `null` to avoid requiring them to be set to optional | +| [85df4011b](https://github.com/angular/angular-cli/commit/85df4011ba27254ddb7f22dae550272c9c4406dd) | fix | resolve `bootstrap is not a function` error | +| [e9c9e4995](https://github.com/angular/angular-cli/commit/e9c9e4995e39d9d62d10fe0497e0b98127bc8afa) | fix | resolve circular dependency issue from main.server.js reference in manifest | +| [64c52521d](https://github.com/angular/angular-cli/commit/64c52521d052f850aa7ea1aaadfd8a9fcee9c387) | fix | show error when multiple routes are set with `RenderMode.AppShell` | +| [280ebbda4](https://github.com/angular/angular-cli/commit/280ebbda4c65e19b83448a1bb0de056a2ee5d1c6) | fix | support for HTTP/2 request/response handling | +| [fb05e7f0a](https://github.com/angular/angular-cli/commit/fb05e7f0abd9d68ac03f243c7774260619b8a623) | fix | use wildcard server route configuration on the '/' route when the app router is empty | +| [12ff37adb](https://github.com/angular/angular-cli/commit/12ff37adbed552fc0db97251c30c889ef00e50f3) | perf | cache generated inline CSS for HTML | +| [1d70e3b46](https://github.com/angular/angular-cli/commit/1d70e3b4682806a55d6f7ddacbafcbf615b2a10c) | perf | cache resolved entry-points | +| [f460b91d4](https://github.com/angular/angular-cli/commit/f460b91d46ea5b0413596c4852c80d71d5308910) | perf | integrate ETags for prerendered pages | +| [e52ae7f6f](https://github.com/angular/angular-cli/commit/e52ae7f6f5296a9628cc4a517e82339ac54925bb) | perf | prevent potential stampede in entry-points cache | + + + + + +# 18.2.12 (2024-11-14) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [c3925ed7f](https://github.com/angular/angular-cli/commit/c3925ed7f8e34fd9816cf5a4e8d63c2c45d31d53) | fix | support default options for multiselect list x-prompt | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [c8bee8415](https://github.com/angular/angular-cli/commit/c8bee8415099dfa03d5309183ebbbaab73b2a0eb) | fix | allow .js file replacements in all configuration cases | +| [93f552112](https://github.com/angular/angular-cli/commit/93f552112c2bbd10bc0cee4afcae5b012242636c) | fix | improve URL rebasing for hyphenated Sass namespaced variables | + + + + + +# 18.2.11 (2024-10-30) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [87ec15ba2](https://github.com/angular/angular-cli/commit/87ec15ba266436b7b99b0629beaea3e487434115) | fix | show error message when error stack is undefined | + + + + + +# 18.2.10 (2024-10-23) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [7b775f4e0](https://github.com/angular/angular-cli/commit/7b775f4e008652777bbe7b788dabed02bcc70cc7) | fix | update `http-proxy-middleware` to `3.0.3` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [b1e5f51f9](https://github.com/angular/angular-cli/commit/b1e5f51f9111d7da56ebe64cad51936ad659782d) | fix | Address build issue in Node.js LTS versions with prerendering or SSR | + + + + + +# 17.3.11 (2024-10-23) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [8bad9cee0](https://github.com/angular/angular-cli/commit/8bad9cee08982fffa5ce8244148b491e66191ed8) | fix | update `http-proxy-middleware` to `2.0.7` | + + + + + +# 18.2.9 (2024-10-16) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [237f7c5d0](https://github.com/angular/angular-cli/commit/237f7c5d0355e0a90b23156d3aa97f4328c869e7) | fix | update browserslist config to include last 2 Android major versions | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [d749ba6a3](https://github.com/angular/angular-cli/commit/d749ba6a3c3dd7a90317bd9b46e858a842f27696) | fix | allow direct bundling of TSX files with application builder | +| [b91c82d89](https://github.com/angular/angular-cli/commit/b91c82d8997c0009ed4bbf5e9cd9c82cb1f7f755) | fix | avoid race condition in sass importer | + + + + + +# 18.2.8 (2024-10-09) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [b522002ff](https://github.com/angular/angular-cli/commit/b522002fff763cda2ae1c746efcb2638d0099184) | fix | add validation for component and directive class name | +| [dfd2d5c05](https://github.com/angular/angular-cli/commit/dfd2d5c0500777fa5aea91519f6657aed7f3b7d7) | fix | include `index.csr.html` in resources asset group | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [9445916f9](https://github.com/angular/angular-cli/commit/9445916f9b5b9da69623bf86735264d8a5f26fb3) | fix | `Ctrl + C` not terminating dev-server with SSR | +| [9b5cfaa8c](https://github.com/angular/angular-cli/commit/9b5cfaa8ce9d12bf450e7527d479ce7a879ea1b8) | fix | always generate a new hash for optimized chunk | + + + + + +# 18.2.7 (2024-10-02) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [3f98193d6](https://github.com/angular/angular-cli/commit/3f98193d6963464bd04b510c2d045938f1418ff3) | fix | support single quote setting in JetBrains IDEs | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- | +| [8274184e1](https://github.com/angular/angular-cli/commit/8274184e1c6fa43cc5101018b6fa86fd636a90ba) | fix | add `animate` to valid self-closing elements | +| [2648e811e](https://github.com/angular/angular-cli/commit/2648e811e7c71e8a68d1eb418d7dcdae42ebf9ff) | fix | add few more SVG elements animateMotion, animateTransform, and feBlend etc. to valid self-closing elements | +| [736e126e4](https://github.com/angular/angular-cli/commit/736e126e4836e1c3df32c0ab9ee40e58c16503c0) | fix | separate Vite cache by project | + + + + + +# 18.2.6 (2024-09-25) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [9d0b67124](https://github.com/angular/angular-cli/commit/9d0b67124e4855c5c4a2101b64f8ed86f8624561) | fix | allow missing HTML file request to fallback to index | +| [5fea635b2](https://github.com/angular/angular-cli/commit/5fea635b20b29429e355072c5adc5bf2a597a267) | fix | update rollup to 4.22.4 | + + + + + +# 17.3.10 (2024-09-25) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------- | +| [30489d8fd](https://github.com/angular/angular-cli/commit/30489d8fd1cf738162d95333fe462eea58ba460f) | fix | update vite to 4.1.8 | + + + + + +# 18.2.5 (2024-09-18) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [707431625](https://github.com/angular/angular-cli/commit/7074316257bd736e0d3393368fc93dec9604b49e) | fix | support HTTP HEAD requests for virtual output files | +| [1032b3da1](https://github.com/angular/angular-cli/commit/1032b3da1a0f3aaf63d2fd3cd8c6fd3b0d0b578c) | fix | update vite to `5.4.6` | + + + + + +# 16.2.16 (2024-09-18) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------- | +| [12aca0060](https://github.com/angular/angular-cli/commit/12aca0060492c73cec1bbc231119dde6a4b52607) | fix | update vite to 4.5.5 | + + + + + +# 18.2.4 (2024-09-11) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [765309a2e](https://github.com/angular/angular-cli/commit/765309a2e1bcd3bb07ff87062fc2dc04e4bce16f) | fix | prevent transformation of Node.js internal dependencies by Vite | + + + + + +# 18.2.3 (2024-09-04) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [482076612](https://github.com/angular/angular-cli/commit/482076612cac4b6565fc1b5e89ff9ca207653f87) | fix | update `webpack-dev-middleware` to `7.4.2` | + + + + + +# 18.2.2 (2024-08-29) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [504b00b93](https://github.com/angular/angular-cli/commit/504b00b93b80eec4185838b426c0f6acaa3a148e) | fix | clear context in Karma by default for single run executions | +| [82b76086e](https://github.com/angular/angular-cli/commit/82b76086eb519c224981038dfa55b2ec3cfec0b4) | fix | update webpack to `5.94.0` | + + + + + +# 17.3.9 (2024-08-29) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [e2c5c034d](https://github.com/angular/angular-cli/commit/e2c5c034d96962fe6f358285e376630c71ac9673) | fix | clear context in Karma by default for single run executions | +| [88501f3d5](https://github.com/angular/angular-cli/commit/88501f3d5586f72ee0900b8d351af3d72bdc0dee) | fix | upgrade webpack to `5.94.0` | + + + + + +# 16.2.15 (2024-08-29) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [f596a3d5d](https://github.com/angular/angular-cli/commit/f596a3d5def009b5130440113e3c9b450eb98040) | fix | clear context in Karma by default for single run executions | +| [56fa051bd](https://github.com/angular/angular-cli/commit/56fa051bd92ad47ea089499a488f3566a93375e6) | fix | upgrade webpack to `5.94.0` | + + + + + +# 18.2.1 (2024-08-21) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [05a274a01](https://github.com/angular/angular-cli/commit/05a274a01365c21f69c0412f3455acd14cc6ddc5) | fix | prevent bypassing select/checkbox prompts on validation failure | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [94e27c88b](https://github.com/angular/angular-cli/commit/94e27c88bb968589bc8b9b5d6536ce6c0ba0b24f) | fix | prevent bypassing select/checkbox prompts on validation failure | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [ddeb2b2b9](https://github.com/angular/angular-cli/commit/ddeb2b2b93eaa9d8b659d17357aa2b7a9dc509ce) | fix | remove outdated browser-esbuild option warning | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------- | +| [83b2699ab](https://github.com/angular/angular-cli/commit/83b2699abbf58a7c90d2339fa4a01d67aa2d2d33) | fix | improve error message when an unhandled exception occurs during prerendering | +| [0be4038a5](https://github.com/angular/angular-cli/commit/0be4038a503626e2e9f44d68fe5599cc6028dd8e) | fix | support reading on-disk files during i18n extraction | + + + + + +# 18.2.0 (2024-08-14) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------- | +| [4da922e4f](https://github.com/angular/angular-cli/commit/4da922e4f4e905a1274e70adca1d875c025b8b46) | feat | use isolatedModules in generated project | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [24aaf1e37](https://github.com/angular/angular-cli/commit/24aaf1e37f49735ce97fe72fced3d53b51d6b631) | feat | support import attribute based loader configuration | + + + + + +# 18.1.4 (2024-08-07) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [f8b092711](https://github.com/angular/angular-cli/commit/f8b092711481a5754ea93bce65d706d261873810) | fix | allow explicitly disabling TypeScript incremental mode | +| [f3a5970fc](https://github.com/angular/angular-cli/commit/f3a5970fca0a196b1ac905306257d65bab3b1325) | fix | lazy load Node.js inspector for dev server | + + + + + +# 18.1.3 (2024-07-31) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [a28615d7d](https://github.com/angular/angular-cli/commit/a28615d7dd3f6503f257756058fe182ce6f6b052) | fix | add CSP `nonce` attribute to script tags when inline critical CSS is disabled | +| [747a1447c](https://github.com/angular/angular-cli/commit/747a1447c1855a1bb918e5f55e4ba4182235f88a) | fix | prevent build failures with remote CSS imports when Tailwind is configured | +| [c0933f2c0](https://github.com/angular/angular-cli/commit/c0933f2c0354a13ba3f752f29b24054177697faa) | fix | resolve error with `extract-i18n` builder for libraries | + + + + + +# 18.1.2 (2024-07-24) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [5b9378a3b](https://github.com/angular/angular-cli/commit/5b9378a3be3a1c4f465ba471a120a2edd3a4d2f8) | fix | account for HTML base HREF for dev-server externals | +| [3e4ea77d7](https://github.com/angular/angular-cli/commit/3e4ea77d755edce2c88d55b76860e6e91fb03f3c) | fix | correctly detect comma in Sass URL lexer | +| [d868270f1](https://github.com/angular/angular-cli/commit/d868270f1baf0fd5f2c5677691cc9c4e88b47d8f) | fix | prevent redirection loop | +| [3573ac655](https://github.com/angular/angular-cli/commit/3573ac6555ead2afc34979e284426a0204fff42c) | fix | serve HTML files directly | + + + + + +# 18.1.1 (2024-07-17) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------- | +| [4f6cee272](https://github.com/angular/angular-cli/commit/4f6cee2721b52427624370f3f81f3edc140774e7) | fix | skip undefined files when generating budget stats | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [96dc7e6ed](https://github.com/angular/angular-cli/commit/96dc7e6ed3317e354fae82d1b42b31250e96d89d) | fix | remove Vite "/@id/" prefix for explicit external dependencies | +| [bdef39801](https://github.com/angular/angular-cli/commit/bdef3980160db8c27ae103444a41275351434b78) | fix | resolve only ".wasm" files | + + + + + +# 18.1.0 (2024-07-10) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [6d266c146](https://github.com/angular/angular-cli/commit/6d266c146c9e141396236b93f2bfafcb261fd7aa) | fix | add fallbacks for migration package resolution | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [22e05dcb4](https://github.com/angular/angular-cli/commit/22e05dcb4a9ae963997c58fad86125ca51b19a36) | fix | generate new projects with ECMAScript standard class field behavior | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [687a6c7ec](https://github.com/angular/angular-cli/commit/687a6c7eca740a98129196908689a44c181b33a5) | feat | add `--inspect` option to the dev-server | +| [628d87a94](https://github.com/angular/angular-cli/commit/628d87a9474ad2792b69bfbc501a2c5960b27db9) | feat | support WASM/ES Module integration proposal | +| [3e359da8d](https://github.com/angular/angular-cli/commit/3e359da8dfdbfdb99be13f5c52a7e429c106d4ad) | fix | address rxjs undefined issues during SSR prebundling | +| [4ff914a16](https://github.com/angular/angular-cli/commit/4ff914a16525350050c5e8359fb59f9d6f243d27) | fix | allow additional module preloads up to limit | +| [fb8e3c39a](https://github.com/angular/angular-cli/commit/fb8e3c39a8b265003e98c8c6b5a9ec898223249f) | fix | allow top-level await in zoneless applications | +| [83b75af9f](https://github.com/angular/angular-cli/commit/83b75af9f74af0742a6a78cf7531866b7ba434b6) | fix | check inlineSourceMap option with isolated modules optimization | +| [cd97134a6](https://github.com/angular/angular-cli/commit/cd97134a6e1468c6806c2bd753c934ec91bc3927) | fix | normalize paths during module resolution in Vite | +| [13d2100dd](https://github.com/angular/angular-cli/commit/13d2100ddcc670d69f2d7754890b80eae2e7649a) | fix | read WASM file from script location on Node.js | +| [3091956f5](https://github.com/angular/angular-cli/commit/3091956f503754f313dbf98a8de6d21d3d01ebe3) | fix | support import attributes in JavaScript transformer | +| [dd94a831b](https://github.com/angular/angular-cli/commit/dd94a831b4ce1ca55289fca1818aa26195e81dbc) | perf | enable dependency prebundling for server dependencies | +| [3acb77683](https://github.com/angular/angular-cli/commit/3acb7768317bb05a9cd73fa64e081b5ca0326189) | perf | use direct transpilation with isolated modules | + + + + + +# 18.0.7 (2024-07-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [67bf90131](https://github.com/angular/angular-cli/commit/67bf9013151c4e6a13c91ecf4afd78c863d9e33f) | fix | make `ng update` to keep newline at the end of package.json | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [9b43ecbd0](https://github.com/angular/angular-cli/commit/9b43ecbd0395027548781a19345fbcd82261d4f4) | fix | reduce the number of max workers to available CPUs minus one | +| [03dad6806](https://github.com/angular/angular-cli/commit/03dad680676c4b2272b65a51dd62d74360e20b78) | fix | rollback terser to `5.29.2` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [fc928f638](https://github.com/angular/angular-cli/commit/fc928f6386061f34f7cd3ef6bb6d25aa4a33a800) | fix | correctly name entry points to match budgets | +| [2d51e8607](https://github.com/angular/angular-cli/commit/2d51e86077c4687224e931f49c82a907f5229ae5) | fix | redirect to path with trailing slash for asset directories | +| [16f1c1e01](https://github.com/angular/angular-cli/commit/16f1c1e010090596b7d7c3911f01728e3feecc4d) | fix | reduce the number of max workers to available CPUs minus one | + + + + + +# 18.0.6 (2024-06-26) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [98a8a8a78](https://github.com/angular/angular-cli/commit/98a8a8a781fd7901f2e1c1d2eb22975ac65f0329) | fix | show JavaScript cache store initialization warning | + + + + + +# 18.0.5 (2024-06-20) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [5c705e800](https://github.com/angular/angular-cli/commit/5c705e800c17237d82bc9065f22e30b720ddcec0) | fix | update schematics to use RouterModule when --routing flag is present | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [86e031dc7](https://github.com/angular/angular-cli/commit/86e031dc7ef6c736e431caf973aca1233d912060) | fix | use istanbul-lib-instrument directly for karma code coverage | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [bdd168f37](https://github.com/angular/angular-cli/commit/bdd168f37f7757e0c02971e21e90479555a6e703) | fix | add CSP nonce to script with src tags | +| [405c14809](https://github.com/angular/angular-cli/commit/405c1480917d50c677be178c817b845f30cc8cce) | fix | automatically resolve `.mjs` files when using Vite | +| [7360a346e](https://github.com/angular/angular-cli/commit/7360a346ed1b329c0620301ce0e0464d5569a42f) | fix | use Node.js available parallelism for default worker count | + + + + + +# 18.0.4 (2024-06-13) + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------- | +| [791ef809d](https://github.com/angular/angular-cli/commit/791ef809d8dfec8fde844e916973a05b2eb5c9d9) | fix | do not reference sourcemaps in web workers and global stylesheet bundles when hidden setting is enabled | +| [20fc6ca05](https://github.com/angular/angular-cli/commit/20fc6ca057e5190155474e7377bf9f22aab597dd) | fix | generate module preloads next to script elements in index HTML | +| [3a1bf5c8a](https://github.com/angular/angular-cli/commit/3a1bf5c8a52d6ec1eb337f0937bf073de2ea0b62) | fix | Initiate PostCSS only once | +| [78c611754](https://github.com/angular/angular-cli/commit/78c6117544afa1aa69ef5485f1c3b77b1207f6f1) | fix | issue warning when auto adding `@angular/localize/init` | + + + + + +# 18.0.3 (2024-06-05) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- | +| [b709d2a24](https://github.com/angular/angular-cli/commit/b709d2a243926f1ab01e8c8a78d68fc57ab4cab3) | fix | add `schema.json` options to parsed command, also when a version is passed to `ng add @` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [43a2a7d13](https://github.com/angular/angular-cli/commit/43a2a7d137328c1f6f865b76585a92f4e5058b13) | fix | avoid escaping rebased Sass URL values | +| [9acb5c7ca](https://github.com/angular/angular-cli/commit/9acb5c7ca8e6d14379e39f56d2498c0276214210) | fix | disable JS transformer persistent cache on web containers | +| [346df4909](https://github.com/angular/angular-cli/commit/346df490976e39d791db5ecfa14eab6a5ad8d99d) | fix | improve Sass rebaser ident token detection | +| [6526a5f59](https://github.com/angular/angular-cli/commit/6526a5f590fbc7f26b9e613af3b5c497e30603b5) | fix | watch all related files during a Sass error | + + + + + +# 18.0.2 (2024-05-29) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [9967c04b8](https://github.com/angular/angular-cli/commit/9967c04b86c6928509c80af7144b342616e9681b) | fix | check both application builder packages in SSR schematic | +| [92b48ab14](https://github.com/angular/angular-cli/commit/92b48ab144fbe5b8f89d371b0a8fa94d0d17b72c) | fix | set builders `assets` option correctly for new applications | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [3bb06c37d](https://github.com/angular/angular-cli/commit/3bb06c37dc20d7af358f007b9928de71f39545d2) | fix | disable Worker wait loop for Sass compilations in web containers | +| [c4cf35923](https://github.com/angular/angular-cli/commit/c4cf359233e1044864539383912b9ba0432e149d) | fix | print Sass `@warn` location | +| [352879804](https://github.com/angular/angular-cli/commit/3528798042a232779478bf82b4d4f6521fab4c30) | fix | support valid self-closing MathML tags in HTML index file | +| [476f3084a](https://github.com/angular/angular-cli/commit/476f3084aff333a45c4937950abdba65cd420eba) | fix | support valid self-closing SVG tags in HTML index file | + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [acbffd236](https://github.com/angular/angular-cli/commit/acbffd2368d3c979b26a4541d3f44387fdba0651) | fix | set manifest `icons` location to match `assets` builder option | + + + + + +# 18.0.1 (2024-05-23) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------- | +| [01842f515](https://github.com/angular/angular-cli/commit/01842f5154fe0ec41226d1dd28e099bf57f3d2c9) | fix | use angular.dev in readme | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [7d253e9cd](https://github.com/angular/angular-cli/commit/7d253e9cd0bb6df829fd4229465f4334d5c134bb) | fix | avoid rebasing URLs with function calls | +| [6b6a76a99](https://github.com/angular/angular-cli/commit/6b6a76a998980392d78e1cabc5e5fe4af0ced01c) | fix | disable persistent disk caching inside webcontainers by default | +| [ba70a50b6](https://github.com/angular/angular-cli/commit/ba70a50b6bc45a6b07ff24feed3b36915294063b) | fix | handle esbuild-browser `polyfills` option as `string` during `ng serve` | +| [706423aca](https://github.com/angular/angular-cli/commit/706423acad2c431c4125166d078dbad804719d95) | fix | only import persistent cache store with active caching | + + + + + +# 18.0.0 (2024-05-22) + +## Breaking Changes + +### @angular/cli + +- The `ng doc` command has been removed without a replacement. To perform searches, please visit www.angular.dev +- Node.js support for versions <18.19.1 and <20.11.1 has been removed. + +### @angular-devkit/build-angular + +- By default, the index.html file is no longer emitted in the browser directory when using the application builder with SSR. Instead, an index.csr.html file is emitted. This change is implemented because in many cases server and cloud providers incorrectly treat the index.html file as a statically generated page. If you still require the old behavior, you can use the `index` option to specify the `output` file name. + + ```json + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/my-app", + "index": { + "input": "src/index.html", + "output": "index.html" + } + } + } + } + ``` + +- The support for the legacy Sass build pipeline, previously accessible via `NG_BUILD_LEGACY_SASS` when utilizing webpack-based builders, has been removed. + +## Deprecations + +### @angular-devkit/schematics + +- `NodePackageLinkTask` in `@angular-devkit/schematics`. A custom task should be created instead. + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------- | +| [ac3019570](https://github.com/angular/angular-cli/commit/ac301957093d0689c98f7debe98fbb2546c9b442) | feat | add `ng dev` alias to `ng serve` | +| [4087728c3](https://github.com/angular/angular-cli/commit/4087728c3e6350d85d653e9d053249ff77e639e6) | feat | support for Node.js v22 | +| [41ab6c8c3](https://github.com/angular/angular-cli/commit/41ab6c8c3486d7cf7c41c18ae3b603376f647605) | fix | add `--version` option | +| [df4dde95d](https://github.com/angular/angular-cli/commit/df4dde95daa12d5b08b3c4e937f4b4048d645254) | fix | add `@angular/build` package to update group list | +| [1039f6d79](https://github.com/angular/angular-cli/commit/1039f6d7997523dd4657c5c2a06631e6075b7bc0) | fix | change update guide link to angular.dev | +| [f4670fcb1](https://github.com/angular/angular-cli/commit/f4670fcb1af20a53501b557fc0e6126afce766d5) | fix | eliminate prompts during `ng version` command | +| [a99ec6a54](https://github.com/angular/angular-cli/commit/a99ec6a5453fb732500ef7abff67f76511a74da3) | fix | keep cli package first in update package group metadata | +| [dd786d495](https://github.com/angular/angular-cli/commit/dd786d495ce6e7d759b0b225b2efe25fb5727d08) | fix | only add --version option on default command | +| [03eee0545](https://github.com/angular/angular-cli/commit/03eee0545095ff958ac86cb5dfad44692ef018ae) | refactor | remove `ng doc` command | +| [c7b208555](https://github.com/angular/angular-cli/commit/c7b208555e34cc5ebf9cf2d335d257e72297cae9) | refactor | remove support for Node.js versions <18.19.1 and <20.11.1 | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [b2ac5fac7](https://github.com/angular/angular-cli/commit/b2ac5fac7d66ccd027f766565fa17c6a3bb18e44) | feat | allow application migration to use new build package in projects where possible | +| [6530aa11b](https://github.com/angular/angular-cli/commit/6530aa11bed5ef67d611e8aed268bd20345cf0e6) | feat | replace `assets` with `public` directory | +| [725883713](https://github.com/angular/angular-cli/commit/72588371385bebeea1003dff4d1d0a2ca9854321) | feat | use eventCoalescing option by default (standalone bootstrap) | +| [508d97da7](https://github.com/angular/angular-cli/commit/508d97da76b5359bc8029888ff0e9cfc59a6139c) | feat | use ngZoneEventCoalescing option by default (module bootstrap) | +| [f452589e2](https://github.com/angular/angular-cli/commit/f452589e2c921448b76a138a5f34ba92ad05e297) | feat | use TypeScript bundler module resolution for new projects | +| [95a4d6ee5](https://github.com/angular/angular-cli/commit/95a4d6ee56d80dce012cf2306422bb7fd8e0e32d) | fix | add less dependency in application migration if needed | +| [c46aa084f](https://github.com/angular/angular-cli/commit/c46aa084f53be7ebdb8cc450bd81907222d00275) | fix | add postcss dependency in application migration if needed | +| [157329384](https://github.com/angular/angular-cli/commit/157329384809d723c428a043712a331493826748) | fix | add spaces around eventCoalescing option | +| [23cc337aa](https://github.com/angular/angular-cli/commit/23cc337aa34c919e344ab001f5efbb8fe9ce3c7c) | fix | keep deployUrl option when migrating to application builder | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [ddd08efef](https://github.com/angular/angular-cli/commit/ddd08efefecfe9b74db6a866a1bed0216380a28a) | fix | resolve builder aliases from containing package | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- | +| [53c319aaa](https://github.com/angular/angular-cli/commit/53c319aaa95049b8558df80e57fa0a6318003121) | feat | add support for the `poll` option in the library builder | +| [83d1d233a](https://github.com/angular/angular-cli/commit/83d1d233a2eded71fcdd5fec4b1a90bdd4dbf132) | feat | enhance Sass rebasing importer for resources URL defined in variables and handling of external paths | +| [d51cb598a](https://github.com/angular/angular-cli/commit/d51cb598a74aba313aee212656de506004a041e6) | feat | inject event-dispatch in SSR HTML page | +| [0b03829bc](https://github.com/angular/angular-cli/commit/0b03829bcefea5c250c6a9ff880a737fcc351b2e) | feat | move i18n extraction for application builder to new build system package | +| [4ffe07aa2](https://github.com/angular/angular-cli/commit/4ffe07aa24a0fc9ff48461e9c3664d96e92317cf) | feat | move Vite-based dev-server for application builder to new build system package | +| [d1c632af9](https://github.com/angular/angular-cli/commit/d1c632af9a98d4e8975f198cf205194e2ebff209) | feat | support native async/await when app is zoneless | +| [37fc7f0cc](https://github.com/angular/angular-cli/commit/37fc7f0ccf3b8e6f31a0c5b2eaf4aee52f439472) | fix | disable Vite prebundling when script optimizations are enabled | +| [2acf95a94](https://github.com/angular/angular-cli/commit/2acf95a94993e51876d4004d2c3bc0a04be0a419) | fix | do not generate an `index.html` file in the browser directory when using SSR. | +| [8a54875cb](https://github.com/angular/angular-cli/commit/8a54875cbb654f95d5213b2d84190bd3814d6810) | fix | handle wrapping of class expressions emitted by esbuild | +| [97973059e](https://github.com/angular/angular-cli/commit/97973059ec56a573629f7a367757773a3cfabe17) | refactor | remove Sass legacy implementation | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------- | +| [797584583](https://github.com/angular/angular-cli/commit/797584583138c9223bf238ae8f352e77575bd25a) | refactor | deprecate `NodePackageLinkTask` | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [810d213e1](https://github.com/angular/angular-cli/commit/810d213e1813dd01620173f5f999dca7bccf8ea1) | feat | introduce new official build system package | +| [b7a0792b3](https://github.com/angular/angular-cli/commit/b7a0792b3286fc98d1343f55b5df89ddf13e36bc) | fix | add a maximum rendering timeout for SSG | +| [411115303](https://github.com/angular/angular-cli/commit/41111530349db1ac199c3ac1d4eccbde8b023123) | fix | add console note about development server raw file size | +| [921fa7cf4](https://github.com/angular/angular-cli/commit/921fa7cf4adc69d3cb6ec7dd5c8d7cace33a502e) | fix | add missing `ansi-colors` and `picomatch` dependencies | +| [791cf75af](https://github.com/angular/angular-cli/commit/791cf75afb0b3b5892c41296bc4049a2c10926e8) | fix | check both potential build packages in Angular version check | +| [4d7cd5e3e](https://github.com/angular/angular-cli/commit/4d7cd5e3ed303c53b2cc63720b9a577e2f46f170) | fix | correctly wrap class expressions with static properties or blocks emitted by esbuild | +| [57f448a0f](https://github.com/angular/angular-cli/commit/57f448a0f70c76c1a0ebbe941f82eec1d698e7d4) | fix | decode URL pathname decoding during SSG fetch | +| [940e382db](https://github.com/angular/angular-cli/commit/940e382db27474dba6479f57e4ffefee04cfca66) | fix | disable Vite prebundling when script optimizations are enabled | +| [70dbc7a6e](https://github.com/angular/angular-cli/commit/70dbc7a6e9a7f6d55aeb4e10e8e686b186e6cdf3) | fix | emit error for invalid self-closing element in index HTML | +| [44b401747](https://github.com/angular/angular-cli/commit/44b401747f78bab208ce863f9c08e7a12f01fe27) | fix | ensure input index HTML file triggers rebuilds when changed | +| [dff4deaeb](https://github.com/angular/angular-cli/commit/dff4deaeb366d0ff734ae02abdbaa1fcdcd901aa) | fix | ensure recreated files are watched | +| [17931166d](https://github.com/angular/angular-cli/commit/17931166d83a4b18d2f4eb81f8a445b2365c71aa) | fix | format sizes using decimal byte units consistently | +| [2085365e0](https://github.com/angular/angular-cli/commit/2085365e04c9b08dbf2024036b93609046f2f458) | fix | only generate shallow preload links for initial files | +| [33cd47c85](https://github.com/angular/angular-cli/commit/33cd47c85ea12df57ec7b244beccfa299c927765) | fix | properly configure headers for media resources and HTML page | +| [d10fece2c](https://github.com/angular/angular-cli/commit/d10fece2c17183e18d04733dec22459ced1cc1c8) | fix | properly rebase Sass url() values with leading interpolations | +| [3f2963835](https://github.com/angular/angular-cli/commit/3f2963835759fa3eed1faf64a7b87d5dcf8a6fa3) | perf | add persistent caching of JavaScript transformations | +| [a15eb7d1c](https://github.com/angular/angular-cli/commit/a15eb7d1c6a26f5d94da5566f8b4ac1810ea1361) | perf | improve rebuild time for file loader usage with prebundling | + + + + + +# 17.3.8 (2024-05-22) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [3ada6eb52](https://github.com/angular/angular-cli/commit/3ada6eb5256631ca3a951525fc9814ad0447a41f) | fix | clarify optional migration instructions during ng update | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------------------------------- | +| [4b6ba8df1](https://github.com/angular/angular-cli/commit/4b6ba8df1ab8f4801fba7ddc38812417e274d960) | fix | `SchematicTestRunner.runExternalSchematic` fails with "The encoded data was not valid for encoding utf-8" | + + + + + +# 17.3.7 (2024-05-08) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [998c72036](https://github.com/angular/angular-cli/commit/998c720363087f3f0b1748d3f875e5b536a3119d) | fix | decode URL pathname decoding during SSG fetch | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [1ab1c6c9e](https://github.com/angular/angular-cli/commit/1ab1c6c9e10ce938402355afed4602b76ac08a0e) | fix | use web standard error check for Deno support | + + + + + +# 17.3.6 (2024-04-25) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [dcec59799](https://github.com/angular/angular-cli/commit/dcec59799faac66bf25043984c11944479efcf4d) | fix | properly configure headers for media resources and HTML page | + + + + + +# 17.3.5 (2024-04-17) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [6191d06ca](https://github.com/angular/angular-cli/commit/6191d06ca735a8fd29f048f319f912076abec698) | fix | address `Unable to deserialize cloned data` issue with Yarn PnP | +| [0335d6a5d](https://github.com/angular/angular-cli/commit/0335d6a5df1c0b0706673e6152e3bf5eb65d166a) | fix | remove `type="text/css"` from `style` tag | + + + + + +# 17.3.4 (2024-04-11) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [1128bdd64](https://github.com/angular/angular-cli/commit/1128bdd642c3e60c67485970e5cd354b2fde0f98) | fix | ensure esbuild-based builders exclusively produce ESM output | + + + + + +# 16.2.14 (2024-04-11) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------- | +| [1068c3c73](https://github.com/angular/angular-cli/commit/1068c3c733a7c52e7876d43454d0ff590c99b61b) | fix | update vite to `4.5.3` | + + + + + +# 17.3.3 (2024-04-02) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [a673baf5c](https://github.com/angular/angular-cli/commit/a673baf5ce385415ded23641a2dc5cdcae8f3f5c) | fix | Revert "fix(@schematics/angular): rename SSR port env variable" | + + + + + +# 17.3.2 (2024-03-25) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [935f931ee](https://github.com/angular/angular-cli/commit/935f931eea8ddd1cb86b2275b8e384bf51e9153e) | fix | rename SSR port env variable | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [c9d436000](https://github.com/angular/angular-cli/commit/c9d4360000e6134b936781be3b0d5cf1871d44d7) | fix | `Internal server error: Invalid URL` when using a non localhost IP | +| [59fba38ec](https://github.com/angular/angular-cli/commit/59fba38ec6181c8d9c7a0636fb514c4b25aaf0cd) | fix | ensure proper resolution of linked SCSS files | +| [27dd8f208](https://github.com/angular/angular-cli/commit/27dd8f208911dbb2eda6d64efd6d1ce8c463ce35) | fix | service-worker references non-existent named index output | +| [c12907d92](https://github.com/angular/angular-cli/commit/c12907d92f8a2268541fd3bf28857dbb216ec7c9) | fix | update `webpack-dev-middleware` to `6.1.2` | + + + + + +# 16.2.13 (2024-03-25) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [5ad507e3d](https://github.com/angular/angular-cli/commit/5ad507e3d4cb27fb275d255018b9b6e735835711) | fix | `update webpack-dev-middleware` to `6.1.2` | + + + + + +# 15.2.11 (2024-03-25) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [c6feb0bb0](https://github.com/angular/angular-cli/commit/c6feb0bb0247a1cf17e17325b8c42d0d6a7d1451) | fix | `update webpack-dev-middleware` to `6.1.2` | + + + + + +# 17.3.1 (2024-03-20) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [198ca9afc](https://github.com/angular/angular-cli/commit/198ca9afcc9a043d4329c2f4032f0b9cefa11a2e) | fix | improve Sass format clarity for application style option | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------------------------- | +| [2809971a5](https://github.com/angular/angular-cli/commit/2809971a57966cf79965c84a933f70709334c16b) | fix | only generate `server` directory when SSR is enabled | +| [3f601a14e](https://github.com/angular/angular-cli/commit/3f601a14e70540f37ef6c6559a5cd50bb6b453d7) | fix | typo in allowedHosts warning for unsupported vite options | +| [79c44adac](https://github.com/angular/angular-cli/commit/79c44adac4184408cbd1dc07989796f155cfc70e) | perf | avoid transforming empty component stylesheets | +| [cc3167f30](https://github.com/angular/angular-cli/commit/cc3167f3012aa621a7fc9277a9c8a82601f7d914) | perf | reduce build times for apps with a large number of components when utilizing esbuild-based builders | + + + + + +# 17.3.0 (2024-03-13) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [5ab71fc92](https://github.com/angular/angular-cli/commit/5ab71fc92ba26f6255e5a5c00e374709ff58d19d) | feat | update CSS/Sass import/use specifiers in application migration | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [9ca3a5450](https://github.com/angular/angular-cli/commit/9ca3a54503574674eb107d4d2b507be7ecd727ee) | feat | add `deployUrl` to application builder | +| [3821344da](https://github.com/angular/angular-cli/commit/3821344da663ded52b773752abc805ed04e28f3c) | fix | ensure proper display of build logs in the presence of warnings or errors | +| [de2d05049](https://github.com/angular/angular-cli/commit/de2d050498e16efa75459f526b9973c9e1d67050) | fix | provide better error message when server option is required but missing | + + + + + +# 17.2.3 (2024-03-06) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [7cc8261fd](https://github.com/angular/angular-cli/commit/7cc8261fd2eb0ef1665faebec43cba447a64fd33) | fix | avoid implicit CSS file extensions when resolving | +| [259ec72d5](https://github.com/angular/angular-cli/commit/259ec72d521b3a660c54ec33aaf8bf7c838281e7) | fix | avoid marking component styles as media with no output media directory | +| [faffdfdce](https://github.com/angular/angular-cli/commit/faffdfdcebb3f71f7ef64b02114561985c592add) | fix | disable `deployUrl` when using vite dev-server | + + + + + +# 17.2.2 (2024-02-28) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [3394d3cf1](https://github.com/angular/angular-cli/commit/3394d3cf10996a93777edfb28d83cf4eb85ae40b) | fix | ensure all related stylesheets are rebuilt when an import changes | + + + + + +# 17.2.1 (2024-02-22) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [9e7c47b59](https://github.com/angular/angular-cli/commit/9e7c47b5945b368a6fd5e2544674d5a3afd63d65) | fix | allow `mts` and `cts` file replacement | +| [f2a2e9287](https://github.com/angular/angular-cli/commit/f2a2e92877298a30bc1042772be043d5db9ac729) | fix | provide Vite client code source map when loading | + + + + + +# 17.2.0 (2024-02-14) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [b3e206741](https://github.com/angular/angular-cli/commit/b3e206741c5b59b8563b7c60a1f66c49802549e7) | feat | add support to `bun` package manager | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [03e1aa790](https://github.com/angular/angular-cli/commit/03e1aa7904acfe9d12eaf3717d1b136159893a76) | feat | add support to `bun` package manager | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [7f57123fd](https://github.com/angular/angular-cli/commit/7f57123fd40b345d7880cb9e2eccd4757c0fb6ee) | feat | add define build option to application builder | +| [f4f535653](https://github.com/angular/angular-cli/commit/f4f535653a34c2a7c37c51c98680b6b1766c6d0d) | feat | add JSON build logs when using the application builder | +| [b59f663e5](https://github.com/angular/angular-cli/commit/b59f663e5715e009b05bf560637c1bca3b112784) | feat | allow control of Vite-based development server prebundling | +| [8f47f1e96](https://github.com/angular/angular-cli/commit/8f47f1e965b25f3d976eda701ae2e7b7e8cccfa3) | feat | provide default and abbreviated build target support for dev-server and extract-i18n | +| [7a12074dc](https://github.com/angular/angular-cli/commit/7a12074dc940f1aa8f5347071324fa0d2fd1300b) | feat | provide option to allow automatically cleaning the terminal screen during rebuilds | +| [7c522aa87](https://github.com/angular/angular-cli/commit/7c522aa8742cd936bb0dfd30773d88f3ef92d488) | feat | support using custom postcss configuration with application builder | +| [476a68daa](https://github.com/angular/angular-cli/commit/476a68daa9746d29d3f74f68307d982d1d66f94c) | fix | add output location in build stats | +| [5e6f1a9f4](https://github.com/angular/angular-cli/commit/5e6f1a9f4362e9b12db64c7c2e609a346b17963d) | fix | avoid preloading server chunks | +| [41ea985f9](https://github.com/angular/angular-cli/commit/41ea985f9317b11cfa6627a2d3f6b34ff4dbc134) | fix | display server bundles in build stats | +| [d493609d3](https://github.com/angular/angular-cli/commit/d493609d30e1f148a7efb72bd64227521a326fbb) | fix | downgrade copy-webpack-plugin to workaround Node.js support issue | +| [8d5af1d5c](https://github.com/angular/angular-cli/commit/8d5af1d5c78ce9aa996f6ba138b99d0bb5005d46) | fix | ensure correct `.html` served with Vite dev-server | +| [944cbcdb1](https://github.com/angular/angular-cli/commit/944cbcdb1af62855febc931fce92debf28a3b2a5) | fix | limit the number of lazy chunks visible in the stats table | +| [905e13633](https://github.com/angular/angular-cli/commit/905e13633071b1db25621ae9f2762a48fe010df1) | fix | support string as plugin option in custom postcss plugin config | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------- | +| [da1c38c48](https://github.com/angular/angular-cli/commit/da1c38c486b07d5a1b2933f23f83c6231b512e0f) | fix | add `bun` to known package managers | + +### @angular/create + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [600498f2c](https://github.com/angular/angular-cli/commit/600498f2cd3e3251e7e6e3dd3505c5e943b2986a) | feat | add support to `bun` package manager | + + + + + +# 17.1.4 (2024-02-14) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [6d2168db9](https://github.com/angular/angular-cli/commit/6d2168db92fcba1ebf82498fed85cd2b596547d3) | fix | prevent BOM errors in `package.json` during `ng update` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [bf42d6df2](https://github.com/angular/angular-cli/commit/bf42d6df2f5eda45bec80bb60315152c03f4a3dc) | fix | bypass Vite prebundling for absolute URL imports | + + + + + +# 17.1.3 (2024-02-08) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [3de3aa170](https://github.com/angular/angular-cli/commit/3de3aa170f02352fe2adf61beea221b356a40843) | fix | allow `./` baseHref when using vite based server | +| [17f47a3c9](https://github.com/angular/angular-cli/commit/17f47a3c94b434a73b9fc698872ef6230f61c781) | fix | ensure WebWorker main entry is used in output code | + + + + + +# 17.1.2 (2024-01-31) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [6815f13e3](https://github.com/angular/angular-cli/commit/6815f13e3c87eff773aa914858293c75e4fae7d2) | fix | add `required` modules as externals imports | +| [a0e306098](https://github.com/angular/angular-cli/commit/a0e306098147cf5fb7b51264c18860767fdf6316) | fix | correctly handle glob negation in proxy config when using vite | +| [235c8403a](https://github.com/angular/angular-cli/commit/235c8403a5bf8a2032da72a504e8cee441dd2d82) | fix | handle regular expressions in proxy config when using Vite | +| [5332e5b2e](https://github.com/angular/angular-cli/commit/5332e5b2ea0c9757f717e386fb162392ef2327a4) | fix | resolve absolute `output-path` when using esbuild based builders | +| [3deb0d4a1](https://github.com/angular/angular-cli/commit/3deb0d4a102fb8d8fae7617b81f62706371e03f5) | fix | return 404 for assets that are not found | + + + + + +# 17.1.1 (2024-01-24) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [8ebb754c2](https://github.com/angular/angular-cli/commit/8ebb754c2e865ffd2c38f61d50a5f4be225a0fe5) | fix | update regex to validate the project-name | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [35ebf1efd](https://github.com/angular/angular-cli/commit/35ebf1efdfa438ea713020b847826621bba0ebfc) | fix | retain trailing comma when adding providers to app config | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [88de1da92](https://github.com/angular/angular-cli/commit/88de1da92919834f620a31d8a3e6a4e2ad1e2f07) | fix | `ENOENT: no such file or directory` on Windows during component rebuild | +| [4e2586aeb](https://github.com/angular/angular-cli/commit/4e2586aeb8ec11cf951f30bbfca6422f13cfd5cc) | fix | allow package file loader option with Vite prebundling | +| [aca1cfcda](https://github.com/angular/angular-cli/commit/aca1cfcda520d9a68bc01833453c81f38c133d37) | fix | do not add internal CSS resources files in watch | +| [53258f617](https://github.com/angular/angular-cli/commit/53258f617cf6c9068e069122029ff91c62d2109e) | fix | handle load event for multiple stylesheets and CSP nonces | +| [412fe6ec6](https://github.com/angular/angular-cli/commit/412fe6ec69bfcbb1e9fb09ccbb10a086b5166689) | fix | pre-transform error when using vite with SSR | +| [45dea6f44](https://github.com/angular/angular-cli/commit/45dea6f44cb27431e4767ce16df3e84c5b6d8f9c) | fix | provide actionable error message when server bundle is missing default export | +| [4e2b23f03](https://github.com/angular/angular-cli/commit/4e2b23f0321f3ec6edfd3e20e9bf95d799de5e7f) | fix | update dependency vite to v5.0.12 | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [02d9d84c5](https://github.com/angular/angular-cli/commit/02d9d84c5da3def7e6b307b115e77233cfcf8d4b) | fix | handle load event for multiple stylesheets and CSP nonces | + + + + + +# 16.2.12 (2024-01-24) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------- | +| [5fad40162](https://github.com/angular/angular-cli/commit/5fad401628f7ddbc412d7e761a4300724f078bde) | fix | update dependency vite to v4.5.2 | + + + + + +# 17.1.0 (2024-01-17) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------- | +| [b513d89b7](https://github.com/angular/angular-cli/commit/b513d89b77dd50891a5f02ec59d1a2bffa0d36db) | feat | add optional migration to use application builder | +| [a708dccff](https://github.com/angular/angular-cli/commit/a708dccff34f62b625332555005bbd8f41380ec2) | feat | update SSR and application builder migration schematics to work with new `outputPath` | +| [4469e481f](https://github.com/angular/angular-cli/commit/4469e481fc4f74574fdd028513b57ba2300c3b34) | fix | do not trigger NPM install when using `---skip-install` and `--ssr` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [e0b274b8f](https://github.com/angular/angular-cli/commit/e0b274b8ff4d164061ca7b60248bb85ceee8f65d) | feat | add option to retain CSS special comments in global styles | +| [204794c4f](https://github.com/angular/angular-cli/commit/204794c4f8e87882af974144fff642762930b4d3) | feat | add support for `--no-browsers` in karma builder | +| [4784155bd](https://github.com/angular/angular-cli/commit/4784155bd62cfac9b29327167093e70c9c6bee41) | feat | add wildcard option for `allowedCommonJsDependencies` | +| [3b93df42d](https://github.com/angular/angular-cli/commit/3b93df42daf9eda9215ea65d8ed0efd1ef203a09) | feat | allow configuring loaders for custom file extensions in application builder | +| [cc246d50e](https://github.com/angular/angular-cli/commit/cc246d50ea8d92289c8be8dc58b376358a899ad6) | feat | allow customization of output locations | +| [15a669c1e](https://github.com/angular/angular-cli/commit/15a669c1efdc8ac18507232d6cb29794c82b94cc) | feat | allowing control of index HTML initial preload generation | +| [47a064b14](https://github.com/angular/angular-cli/commit/47a064b146d06ee7498e3aacb2f17a6283be4504) | feat | emit external sourcemaps for component styles | +| [68dae539a](https://github.com/angular/angular-cli/commit/68dae539adfa12d6088f96ac5c9f224d9bb52e17) | feat | initial experimental implementation of `@web/test-runner` builder | +| [f6e67df1c](https://github.com/angular/angular-cli/commit/f6e67df1c4f286fb1fe195b75cdaab4339ad7604) | feat | inline Google and Adobe fonts located in stylesheets | +| [364a16b7a](https://github.com/angular/angular-cli/commit/364a16b7a6d903cb176f7db627fec126b8aa05f9) | feat | move `browser-sync` as optional dependency | +| [ccba849e4](https://github.com/angular/angular-cli/commit/ccba849e48287805bd8253a03f88d5f44b2b23ae) | feat | support keyboard command shortcuts in application dev server | +| [329d80075](https://github.com/angular/angular-cli/commit/329d80075bc788de0c8e757fbd8cd69120fbec99) | fix | alllow `OPTIONS` requests to be proxied when using `vite` | +| [49ed9a26c](https://github.com/angular/angular-cli/commit/49ed9a26cb87ae629d7d4167277f7e5c4ee066f7) | fix | emit error when using prerender and app-shell builders with application builder | +| [6473b0160](https://github.com/angular/angular-cli/commit/6473b01603b55d265489840cbf32697ad663aeeb) | fix | ensure all configured assets can be served by dev server | +| [874e576b5](https://github.com/angular/angular-cli/commit/874e576b523ba675f85011388e4ce3fcc38992fa) | fix | filter explicit external dependencies for Vite prebundling | +| [2a02b1320](https://github.com/angular/angular-cli/commit/2a02b1320449e0562041bbba86e42048665402e5) | fix | fix normalization of the application builder extensions | +| [9906ab7b4](https://github.com/angular/angular-cli/commit/9906ab7b4714e1fca040f875dd91f0279f688abe) | fix | normalize asset source locations in Vite-based development server | +| [ceffafe1a](https://github.com/angular/angular-cli/commit/ceffafe1a3c8cad469b718e466e771e1d396ab14) | fix | provide better error messages for failed file reads | +| [6d7fdb952](https://github.com/angular/angular-cli/commit/6d7fdb952d49dda1301af229af138d834161c2f9) | fix | show diagnostic messages after build stats | +| [4e1f0e44d](https://github.com/angular/angular-cli/commit/4e1f0e44dca106fa299b5dd0e4145c2c3a99ab4f) | fix | the request url "..." is outside of Vite serving allow list for all assets | +| [bd26a18e7](https://github.com/angular/angular-cli/commit/bd26a18e7a9512bdad15784a19f42aaca8aec303) | fix | typo in preloadInitial option description | +| [125fb779f](https://github.com/angular/angular-cli/commit/125fb779ff394f388c2d027c1dda4a33bd8caa62) | perf | reduce TypeScript JSDoc parsing in application builder | + + + + + +# 17.0.10 (2024-01-10) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [ed1e130da](https://github.com/angular/angular-cli/commit/ed1e130dad7f9b6629f7bd31f8f0590814d0eb57) | fix | retain existing EOL when updating JSON files | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [09c32c678](https://github.com/angular/angular-cli/commit/09c32c678221746458db50f1c2f7eb92264abb16) | fix | retain existing EOL when adding imports | +| [a5c339eaa](https://github.com/angular/angular-cli/commit/a5c339eaa73eb73e2b13558a363e058500a2cfba) | fix | retain existing EOL when updating JSON files | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [3dc4db7d7](https://github.com/angular/angular-cli/commit/3dc4db7d78649eef99a2e60b1faec8844815f8e4) | fix | retain existing EOL when updating workspace config | + + + + + +# 17.0.9 (2024-01-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [446dfb76a](https://github.com/angular/angular-cli/commit/446dfb76a5e2a53542fae93b4400133bf7d9552e) | fix | add prerender and ssr-dev-server schemas in angular.json schema | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [88d6ca4a5](https://github.com/angular/angular-cli/commit/88d6ca4a545c2d3e35822923f2aae03f43b2e3e3) | fix | replace template line endings with platform specific | + + + + + +# 17.0.8 (2023-12-21) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [6dba26a0b](https://github.com/angular/angular-cli/commit/6dba26a0b33ee867923c4505decd86f183e0e098) | fix | `ng e2e` and `ng lint` prompt requires to hit Enter twice to proceed on Windows | +| [0b48acc4e](https://github.com/angular/angular-cli/commit/0b48acc4eaa15460175368fdc86e3dd8484ed18b) | fix | re-add `-d` alias for `--dry-run` | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [99b026ede](https://github.com/angular/angular-cli/commit/99b026edece990e7f420718fd4967e21db838453) | fix | add missing property "buildTarget" to interface "ServeBuilderOptions" | +| [313004311](https://github.com/angular/angular-cli/commit/3130043114d3321b1304f99a4209d9da14055673) | fix | do not generate standalone component when using `ng generate module` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [cf11cdf6c](https://github.com/angular/angular-cli/commit/cf11cdf6ce7569e2da5fa3bc76e20d19c719ce4c) | fix | add missing tailwind `@screen` directive in matcher | +| [aa6c757d7](https://github.com/angular/angular-cli/commit/aa6c757d701b7f95896c8f1643968ee030b179af) | fix | construct SSR request URL using server resolvedUrls | +| [0662048d4](https://github.com/angular/angular-cli/commit/0662048d4abbcdc36ff74d647bb7d3056dff42a8) | fix | ensure empty optimized Sass stylesheets stay empty | +| [d1923a66d](https://github.com/angular/angular-cli/commit/d1923a66d9d2ab39831ac4cd012fa0d2df66124b) | fix | ensure external dependencies are used by Web Worker bundling | + + + + + +# 16.2.11 (2023-12-21) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ----- | -------------------------------- | +| [e0e011fc4](https://github.com/angular/angular-cli/commit/e0e011fc47f2383f9be0b432066c1438ddab7103) | build | update dependency vite to v4.5.1 | + + + + + +# 17.0.7 (2023-12-13) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------ | +| [3df3e583c](https://github.com/angular/angular-cli/commit/3df3e583c8788511598bbe406012196a2882ee49) | fix | `baseHref` with trailing slash causes server not to be accessible without trailing slash | +| [ef1178188](https://github.com/angular/angular-cli/commit/ef1178188a145a1277197a33a304910e1024c365) | fix | allow vite to serve JavaScript and TypeScript assets | +| [385eb77d2](https://github.com/angular/angular-cli/commit/385eb77d2645a1407dbc7528e90a506f9bb2952f) | fix | cache loading of component resources in JIT mode | +| [4b3af73ac](https://github.com/angular/angular-cli/commit/4b3af73ac934a24dd2b022604bc01f00389d87a1) | fix | ensure browser-esbuild is used in dev server with browser builder and forceEsbuild | +| [d1b27e53e](https://github.com/angular/angular-cli/commit/d1b27e53ed9e23a0c08c13c22fc0b4c00f3998b2) | fix | ensure port 0 uses random port with Vite development server | +| [f2f7d7c70](https://github.com/angular/angular-cli/commit/f2f7d7c7073e5564ddd8a196b6fcaab7db55b110) | fix | file is missing from the TypeScript compilation with JIT | +| [7b8d6cddd](https://github.com/angular/angular-cli/commit/7b8d6cddd0daa637a5fecdea627f4154fafe23fa) | fix | handle updates of an `npm link` library from another workspace when `preserveSymlinks` is `true` | +| [c08c78cb8](https://github.com/angular/angular-cli/commit/c08c78cb8965a52887f697e12633391908a3b434) | fix | inlining of fonts results in jagged fonts for Windows users | +| [930024811](https://github.com/angular/angular-cli/commit/9300248114282a2a425b722482fdf9676b000b94) | fix | retain symlinks to output platform directories on builds | +| [3623fe911](https://github.com/angular/angular-cli/commit/3623fe9118be14eedd1a04351df5e15b3d7a289a) | fix | update ESM loader to work with Node.js 18.19.0 | + + + + + +# 17.0.6 (2023-12-06) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------- | +| [da5d39471](https://github.com/angular/angular-cli/commit/da5d39471751cd92f6c21936aefc1f7157b4973b) | fix | enable TypeScript `skipLibCheck` in new workspace | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [048512874](https://github.com/angular/angular-cli/commit/048512874bf9cc022cc9a8ab70f35fc60d9982f5) | fix | app-shell generation incorrect content when using the application builder | +| [f9e982c44](https://github.com/angular/angular-cli/commit/f9e982c4458fc022d34039b9c082471c7ce29c07) | fix | check namespaced Sass variables when rebasing URLs | +| [a1e8ffa9d](https://github.com/angular/angular-cli/commit/a1e8ffa9df3a8eb6af2a8851385ed8927e3c0c64) | fix | correctly align error/warning messages when spinner is active | +| [46d88a034](https://github.com/angular/angular-cli/commit/46d88a034343dc93dd0c467afc08c824da427fef) | fix | handle watch updates on Mac OSX when using native FSEvents API | +| [4594407ae](https://github.com/angular/angular-cli/commit/4594407ae214ce49985a5df315cae3ac8107147d) | fix | improve file watching on Windows when using certain IDEs | +| [aa9e7c615](https://github.com/angular/angular-cli/commit/aa9e7c615529cb9dd6dccd862674cadac0372f08) | fix | normalize locale tags with Intl API when resolving in application builder | +| [a8dbf1da2](https://github.com/angular/angular-cli/commit/a8dbf1da27faf772a4df382b1301e95c32d1ba89) | fix | watch symlink when using `preserveSymlinks` option | +| [e3820cb6c](https://github.com/angular/angular-cli/commit/e3820cb6c7cf131d890882f9e94b8f23c4cbb6a3) | perf | only enable advanced optimizations with script optimizations | + + + + + +# 17.0.5 (2023-11-29) + +Rolling back [bbbe13d67](https://github.com/angular/angular-cli/commit/bbbe13d6782ba9d1b80473a98ea95bc301c48597) which appears to break file watching on Mac devices. + + + + + +# 17.0.4 (2023-11-29) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [7a2823080](https://github.com/angular/angular-cli/commit/7a2823080c61df3515d85f7aa35ee83f57e80e2d) | fix | remove CommonModule import from standalone components | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [0634a4e40](https://github.com/angular/angular-cli/commit/0634a4e40f1b2e4c0a076814f3e1b242ccf1a588) | fix | avoid native realpath in application builder | +| [22880d9cb](https://github.com/angular/angular-cli/commit/22880d9cbf70fffb6cc685b3a9ad82ca741a56fe) | fix | correct set locale when using esbuild based builders | +| [a0680672f](https://github.com/angular/angular-cli/commit/a0680672fd369dc6fba2433441d086e53bebb0a2) | fix | correctly watch files when app is in a directory that starts with a dot | +| [bbbe13d67](https://github.com/angular/angular-cli/commit/bbbe13d6782ba9d1b80473a98ea95bc301c48597) | fix | improve file watching on Windows when using certain IDEs | +| [27e7c2e1b](https://github.com/angular/angular-cli/commit/27e7c2e1b4f514843c2c505b7fe1b3cef126a101) | fix | propagate localize errors to full build result | +| [7455fdca0](https://github.com/angular/angular-cli/commit/7455fdca01bd4af00248bb1769945dc088c59063) | fix | serve assets from the provided `serve-path` | +| [657a07bd6](https://github.com/angular/angular-cli/commit/657a07bd6ba138a209c2a1540ea4d200c60e0f66) | fix | treeshake unused class that use custom decorators | +| [77474951b](https://github.com/angular/angular-cli/commit/77474951b59605a2c36a8bd890376f9e28131ee4) | fix | use workspace real path when not preserving symlinks | + + + + + +# 17.0.3 (2023-11-21) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [450dd29a1](https://github.com/angular/angular-cli/commit/450dd29a13da9930fede96732b29c9c04e1c0cf5) | fix | default to watching project root on Windows with application builder | +| [8072b8574](https://github.com/angular/angular-cli/commit/8072b8574a84a97277e8c83ebbbdde076b79a910) | fix | ensure service worker hashes index HTML file for application builder | +| [d99870740](https://github.com/angular/angular-cli/commit/d998707406c7a191a191f71d07a9491481c8ad56) | perf | only create one instance of postcss when needed | + + + + + +# 17.0.2 (2023-11-20) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------- | +| [023645185](https://github.com/angular/angular-cli/commit/02364518571a2b73be945a0036bbfa39e336330c) | fix | always normalize AOT file reference tracker paths | +| [3b99980bd](https://github.com/angular/angular-cli/commit/3b99980bd02c875a37d1603ae7468558fe7ef4c3) | fix | emit root files when `localize` is enabled when using the esbuild based builders | +| [ef3e3abb8](https://github.com/angular/angular-cli/commit/ef3e3abb8e29a9274e9d1f5fc5c18f01de6fd76f) | fix | ensure watch file paths from TypeScript are normalized | +| [d11b36fe2](https://github.com/angular/angular-cli/commit/d11b36fe207d8a38cb4a1001667c63ecd17aba0c) | fix | normalize paths in ssr sourcemaps to posix when using vite | +| [62d51383a](https://github.com/angular/angular-cli/commit/62d51383acfd8cdeedf07b34c2d78f505ff2e3a8) | fix | only include vendor sourcemaps when using the dev-server when the option is enabled | +| [d28ba8a73](https://github.com/angular/angular-cli/commit/d28ba8a7311ea3345b112a47d6f1e617fb691643) | fix | remove browser-esbuild usage warning | + + + + + +# 17.0.1 (2023-11-15) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------- | +| [5267e6055](https://github.com/angular/angular-cli/commit/5267e605567aba798ee00322f14e3a48eae68b48) | fix | handle packages with no version | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [d9f7d439e](https://github.com/angular/angular-cli/commit/d9f7d439eba879f8fffaacd258d832c407dfd90f) | fix | add helper script to spawn SSR server from `dist` | +| [a80926cdb](https://github.com/angular/angular-cli/commit/a80926cdb6b4d99a65549fcfba2ab094a5835480) | fix | html indentation | +| [f7f62c9d6](https://github.com/angular/angular-cli/commit/f7f62c9d6988e6801981592f56137cd02bfe2316) | fix | remove `downlevelIteration` from `tsconfig.json` for new workspaces | +| [7cb57317d](https://github.com/angular/angular-cli/commit/7cb57317d2b78e9a1f947c9f11175a7d381275fc) | fix | use href property binding for links | +| [731917cd0](https://github.com/angular/angular-cli/commit/731917cd00b366bbec4f184ee9064b307eba59ce) | fix | use styleUrl | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [15dd71aba](https://github.com/angular/angular-cli/commit/15dd71abac77ec5e1c092bebb86edffa3999937a) | fix | `deleteOutputPath` when using `esbuild-builder` | +| [fa4d8ff31](https://github.com/angular/angular-cli/commit/fa4d8ff31ef64738e45078c0e7be471591361442) | fix | add actionable error when file replacement is missing | +| [160a91160](https://github.com/angular/angular-cli/commit/160a91160ff3677d9e2d3d413ae360c4e1957c53) | fix | add support for vendor sourcemaps when using the dev-server | +| [5623c193e](https://github.com/angular/angular-cli/commit/5623c193e4cccbf6783f7e3faaf0a6c2fb086b34) | fix | cache stylesheet load errors with application builder | +| [1a5538e0c](https://github.com/angular/angular-cli/commit/1a5538e0c9cc121fa1608eb99e941bc3a5f59ad6) | fix | disable Worker wait loop for TS/NG parallel compilation in web containers | +| [883771946](https://github.com/angular/angular-cli/commit/883771946a36a42ebfe23d32b393513309b16c82) | fix | do not process ssr entry-point when running `ng serve` | +| [d3b549167](https://github.com/angular/angular-cli/commit/d3b54916705e57f017597917d9aea1f71f2ba95a) | fix | empty output directory instead of removing | +| [596f7639a](https://github.com/angular/angular-cli/commit/596f7639a6c7fe00c9088e32739578cc374a31e2) | fix | ensure compilation errors propagate to all bundle actions | +| [d900a5217](https://github.com/angular/angular-cli/commit/d900a5217a75accf434a95ad90300ec5005a23a8) | fix | maintain current watch files after build errors | +| [21549bdeb](https://github.com/angular/angular-cli/commit/21549bdeb97b23f7f37110d579513f3102dc60e8) | fix | prerender default view when no routes are defined | +| [4c251647b](https://github.com/angular/angular-cli/commit/4c251647b8fdb3b128ca3252c83aaa71ecc48e88) | fix | rewire sourcemap back to original source root | + + + + + +# 17.0.0 (2023-11-08) + +## Breaking Changes + +### @schematics/angular + +- Routing is enabled by default for new applications when using `ng generate application` and `ng new`. The `--no-routing` command line option can be used to disable this behaviour. +- `ng g interceptor` now generate a functional interceptor by default. or guard by default. To generate a class-based interceptor the `--no-functional` command flag should be used. +- `rootModuleClassName`, `rootModuleFileName` and `main` options have been removed from the public `pwa` and `app-shell` schematics. +- App-shell and Universal schematics deprecated unused `appId` option has been removed. + +### @angular-devkit/build-angular + +- Node.js v16 support has been removed + + Node.js v16 is planned to be End-of-Life on 2023-09-11. Angular will stop supporting Node.js v16 in Angular v17. + For Node.js release schedule details, please see: https://github.com/nodejs/release#release-schedule + +### @angular-devkit/schematics + +- deprecated `runExternalSchematicAsync` and `runSchematicAsync` methods have been removed in favor of `runExternalSchematic` and `runSchematic`. + +## Deprecations + +### @angular-devkit/build-angular + +- The `browserTarget` in the dev-server and extract-i18n builders have been deprecated in favor of `buildTarget`. + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [f4e7fa873](https://github.com/angular/angular-cli/commit/f4e7fa87350ea1162287114796e0e04e2af101c4) | fix | add `@angular/ssr` as part of the ng update `packageGroup` | +| [1f7156b11](https://github.com/angular/angular-cli/commit/1f7156b112606410ab9ea1cd3f178a762566b96b) | fix | add Node.js 20 as supported version | +| [4b9a87c90](https://github.com/angular/angular-cli/commit/4b9a87c90469481dc3dd0da4d1506521b4203255) | fix | ignore peer mismatch when updating @nguniversal/builders | +| [f66f9cf61](https://github.com/angular/angular-cli/commit/f66f9cf612bed49b961f1f8a8e4deef05fd5ef40) | fix | remove Node.js 16 from supported checks | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------- | +| [741cca73c](https://github.com/angular/angular-cli/commit/741cca73c129ff05e7229081d50762a054c09a8d) | feat | add `ng new --ssr` | +| [3938863b9](https://github.com/angular/angular-cli/commit/3938863b9900fcfe574b3112d73a8f34672f38bd) | feat | add migration to migrate from `@nguniversal` to `@angular/ssr` | +| [dc6b6eaf6](https://github.com/angular/angular-cli/commit/dc6b6eaf6f8af0d2b3f31cea77dc9a63ff845e3c) | feat | add migration to replace usages of `@nguniversal/builders` | +| [6979eba3c](https://github.com/angular/angular-cli/commit/6979eba3c9d46fd5fc2622d28636c48dbcbbe1c6) | feat | enable hydration when adding SSR, SSG or AppShell | +| [1a6a139aa](https://github.com/angular/angular-cli/commit/1a6a139aaf8d5a6947b399bbbd48bbfd9e52372c) | feat | enable routing by default for new applications | +| [ac0db6697](https://github.com/angular/angular-cli/commit/ac0db6697593196692e5b87e1e724be6de0ef0a0) | feat | enable standalone by default in new applications | +| [a189962a5](https://github.com/angular/angular-cli/commit/a189962a515051fd77e20bf8dd1815086a0d12ef) | feat | generate functional interceptors by default | +| [ae45c4ab8](https://github.com/angular/angular-cli/commit/ae45c4ab8103ba8ebc2686e71dbf7d0394b1ee92) | feat | update `ng new` generated application | +| [3f8aa9d8c](https://github.com/angular/angular-cli/commit/3f8aa9d8c7dc7eff06516c04ba08764bb044cb6b) | feat | update` ng new` to use the esbuild application builder based builder | +| [03a1eaf01](https://github.com/angular/angular-cli/commit/03a1eaf01c009d814cb476d2db53b2d0a4d58bcd) | fix | account for new block syntax in starter template | +| [eb0fc7434](https://github.com/angular/angular-cli/commit/eb0fc7434539d3f5a7ea3f3c4e540ac920b10c19) | fix | add missing express `REQUEST` and `RESPONSE` tokens | +| [ecdcff2db](https://github.com/angular/angular-cli/commit/ecdcff2db2b205443a585dd5dd118dbd50613883) | fix | add missing icons in ng-new template | +| [175944672](https://github.com/angular/angular-cli/commit/17594467218b788ebb27d8d16ffb0b555fcf71ee) | fix | do not add unnecessary dependency on `@angular/ssr` during migration | +| [23c4c5e42](https://github.com/angular/angular-cli/commit/23c4c5e4293ef770d555b8b2bd449ad32d1537d4) | fix | enable TypeScript `esModuleInterop` by default for ESM compliance | +| [d60a6e86a](https://github.com/angular/angular-cli/commit/d60a6e86a48f15b3ddf89943dad31ee267f67648) | fix | noop workspace config migration when already executed | +| [e516a4bdb](https://github.com/angular/angular-cli/commit/e516a4bdb7f6bb87f556e58557e57db6f7e65845) | fix | pass `ssr` option to application schematics | +| [419b5c191](https://github.com/angular/angular-cli/commit/419b5c1917c45dc115b107479d5066b9193497fa) | fix | remove `baseUrl` from `tsconfig.json` | +| [0368b23f2](https://github.com/angular/angular-cli/commit/0368b23f2e5d8ca9c6191a2db956dc6850daebfc) | fix | use @types/node v18 | +| [b15e82758](https://github.com/angular/angular-cli/commit/b15e827580d6d3159c49521eb9b5d2b6d8ca2502) | refactor | remove deprecated appId option | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------------------------------------- | +| [c48982dc1](https://github.com/angular/angular-cli/commit/c48982dc1d01d11be54ffb0b1469e3b0557f3920) | feat | add `buildTarget` option to dev-server and `extract-i18n` builders | +| [1fb0350eb](https://github.com/angular/angular-cli/commit/1fb0350eb7370ef6f72acc4e20c4d0bee8bf0b29) | feat | add initial support for bundle budgets to esbuild builders | +| [8168ae2a8](https://github.com/angular/angular-cli/commit/8168ae2a892dd012707bd294ffd26d0a070c0b5d) | feat | apply global CSS updates without a live-reload when using `vite` | +| [91019bde2](https://github.com/angular/angular-cli/commit/91019bde2af5fb9dff6426ba24098271d8ac4889) | feat | enable localize support for SSR with application builder | +| [3c0719bde](https://github.com/angular/angular-cli/commit/3c0719bde244c45d71881d35899e5ee6206c09ee) | feat | initial i18n extraction support for application builder | +| [8bce80b91](https://github.com/angular/angular-cli/commit/8bce80b91b953c391ef8e45fec7f887f8d8521aa) | feat | initial support for application Web Worker discovery with esbuild | +| [49f07a84d](https://github.com/angular/angular-cli/commit/49f07a84d6f6120388d9fc48a2514d3398986e49) | feat | standardize application builder output structure | +| [c3a87a60e](https://github.com/angular/angular-cli/commit/c3a87a60e0d3cdcae9f4361c2cf21c7ea29bd7de) | feat | support basic web worker bundling with esbuild builders | +| [9e425308a](https://github.com/angular/angular-cli/commit/9e425308a0c146b685e452a106cbdf3e02bddd00) | feat | support component style budgets in esbuild builders | +| [771e036d5](https://github.com/angular/angular-cli/commit/771e036d5ce3d436736d3c8b261050d633b3ef29) | feat | support deploy URL option for `browser-esbuild` builder | +| [c5f3ec71f](https://github.com/angular/angular-cli/commit/c5f3ec71f536e7ebb1c8cd0d7523b42e58f9611a) | feat | support i18n inlining with esbuild-based builder | +| [fd62a9315](https://github.com/angular/angular-cli/commit/fd62a9315defb89b4bea996d256887a6ec7b4327) | feat | support i18n with service worker and app-shell with esbuild builders | +| [5898f72a9](https://github.com/angular/angular-cli/commit/5898f72a97c29d38b9e8b8ca23255f9fbce501e5) | feat | support namedChunks option in application builder | +| [8f9a0d70c](https://github.com/angular/angular-cli/commit/8f9a0d70cdf692b19574410cebb4d029056263fc) | feat | support standalone apps route discovery during prerendering | +| [6b08efa6f](https://github.com/angular/angular-cli/commit/6b08efa6ffd988e08e3db471ffe3214a029de116) | fix | account for arrow function IIFE | +| [2f299fc7b](https://github.com/angular/angular-cli/commit/2f299fc7b5f00056054a06574e65ae311cd3ce0c) | fix | account for styles specified as string literals and styleUrl | +| [9994b2dde](https://github.com/angular/angular-cli/commit/9994b2dde801b2f74fb70152eb73225283da32a3) | fix | add a maximum rendering timeout for SSR and SSG during development | +| [da4e19145](https://github.com/angular/angular-cli/commit/da4e19145b341dccdd5174cc7bc821e5025514b1) | fix | address a path concatenation on Windows | +| [9d4d11cc4](https://github.com/angular/angular-cli/commit/9d4d11cc43f2ae149ee8bfcf28285a1f62594ef7) | fix | allow SSR compilation to work with TS allowJs option | +| [e3c5b91e8](https://github.com/angular/angular-cli/commit/e3c5b91e8a09c8a7dd940655087b69a8949cb2cc) | fix | automatically include known packages in vite prebundling | +| [ca38ee34c](https://github.com/angular/angular-cli/commit/ca38ee34c6267e32b8ee74db815f929896f1baba) | fix | avoid binary content in architect results with browser-esbuild | +| [657f78292](https://github.com/angular/angular-cli/commit/657f78292b4c78db5a43a172087a078820812323) | fix | avoid dev server update analysis when build fails with vite | +| [2c33f09db](https://github.com/angular/angular-cli/commit/2c33f09db0561f344a26dd4f4304a9098e0ee13f) | fix | avoid dev-server proxy rewrite normalization when invalid value | +| [b182be8aa](https://github.com/angular/angular-cli/commit/b182be8aa7ff5fd3cddc0bcac5f4e45e9ed9cf2e) | fix | avoid in-memory prerendering ESM loader errors | +| [0c982b993](https://github.com/angular/angular-cli/commit/0c982b993b69f4a4b52002cc65ad7ba3b0b9d591) | fix | avoid repeat error clear in vite development server | +| [e41e2015b](https://github.com/angular/angular-cli/commit/e41e2015bfc37672fb67014ae38f31b63f0bb256) | fix | avoid spawning workers when there are no routes to prerender | +| [2d2e79921](https://github.com/angular/angular-cli/commit/2d2e79921a72c4fafad673abe501ba10400403d2) | fix | clean up internal Angular state during rendering SSR | +| [83020fc32](https://github.com/angular/angular-cli/commit/83020fc3291715802c28c5f7dcf7a261bc7f32cd) | fix | clear diagnostic cache when external templates change with esbuild builders | +| [c12f98f94](https://github.com/angular/angular-cli/commit/c12f98f948b1c10594f9d00f4ebf87630fe3cc47) | fix | conditionally enable deprecated Less stylesheet JavaScript support | +| [e10f49efa](https://github.com/angular/angular-cli/commit/e10f49efa8ac96e72bbc441423a730fd172c9f1d) | fix | convert AOT compiler exceptions into diagnostics | +| [667f43af6](https://github.com/angular/angular-cli/commit/667f43af6d91025424147f6e9ac94800f463da1d) | fix | correctly resolve polyfills when `baseUrl` URL is not set to root | +| [d46fb128a](https://github.com/angular/angular-cli/commit/d46fb128a51f172da72ab403ec97213099f43de8) | fix | disable dependency optimization for SSR | +| [1b384308c](https://github.com/angular/angular-cli/commit/1b384308c65ff67b8eac7f3b6407e19ce3db46fa) | fix | disable parallel TS/NG compilation inside WebContainers | +| [070da72c4](https://github.com/angular/angular-cli/commit/070da72c481b881538d6f5ff39955a3da7eb5126) | fix | do not perform advanced optimizations on `@angular/common/locales/global` | +| [508c7606e](https://github.com/angular/angular-cli/commit/508c7606ea2fa8e84d5243992abb59db1b75af49) | fix | do not print `Angular is running in development mode.` in the server console when running prerender in dev mode | +| [e817656f6](https://github.com/angular/angular-cli/commit/e817656f601eaaf910271d5bb2c2230ddb8ed864) | fix | do not print `Angular is running in development mode.` in the server console when running prerender in dev mode | +| [f806e3498](https://github.com/angular/angular-cli/commit/f806e3498b5a4fced7a515258fad30821f3e866c) | fix | elide setClassDebugInfo calls | +| [188a00f3e](https://github.com/angular/angular-cli/commit/188a00f3e466c6c31c7671c63ffc91ccda4590c9) | fix | elide setClassMetadataAsync calls | +| [05ce9d697](https://github.com/angular/angular-cli/commit/05ce9d697a723dcac7a5d24a14f4d11f8686851a) | fix | ensure all SSR chunks are resolved correctly with dev server | +| [d392d653c](https://github.com/angular/angular-cli/commit/d392d653cba67db28eddd003dfec6dcb9b192a95) | fix | ensure correct web worker URL resolution in vite dev server | +| [1a6aa4378](https://github.com/angular/angular-cli/commit/1a6aa437887d2fc5d08c833efc0ca792f6157350) | fix | ensure css url() prefix warnings support Sass rebasing | +| [52f595655](https://github.com/angular/angular-cli/commit/52f595655c69bb6a1398b030cf937b0d92d49864) | fix | ensure i18n locale data is included in SSR application builds | +| [3ad028bb4](https://github.com/angular/angular-cli/commit/3ad028bb442a8978a4f45511cab9bb515764b930) | fix | ensure localize polyfill and locale specifier are injected when not inlining | +| [3e5a99c2c](https://github.com/angular/angular-cli/commit/3e5a99c2c438152a0b930864dcad660a6ea1590a) | fix | ensure recalculation of component diagnostics when template changes | +| [fa234a418](https://github.com/angular/angular-cli/commit/fa234a4186c9d408bfb52b3a649d307f93d0b9b3) | fix | ensure secondary Angular compilations are unblocked on start errors | +| [c0c7dad77](https://github.com/angular/angular-cli/commit/c0c7dad77dd59a387dbcc643a52ee1ed634727ab) | fix | ensure that externalMetadata is defined | +| [ac7caa426](https://github.com/angular/angular-cli/commit/ac7caa4264c7a68467903528deca4a6f579ee15c) | fix | ensure unique internal identifiers for inline stylesheet bundling | +| [1f73bcc49](https://github.com/angular/angular-cli/commit/1f73bcc49abd9f136a18dc6329e2f50a7565eb76) | fix | ensure Web Worker code file is replaced in esbuild builders | +| [23a722b79](https://github.com/angular/angular-cli/commit/23a722b791a64bae32dc925160f2c3d1942955fc) | fix | exclude node.js built-ins from vite dependency optimization | +| [fd2c4c324](https://github.com/angular/angular-cli/commit/fd2c4c324dcfedc81af41351b52ed4c8e41f48fc) | fix | expose ssr-dev-server builder in the public api | +| [9eb58cf7a](https://github.com/angular/angular-cli/commit/9eb58cf7a51c0b7950f80b474890fb2ebd685977) | fix | fail build on non bundling error when using the esbuild based builders | +| [a3e9efe80](https://github.com/angular/angular-cli/commit/a3e9efe80f6e77c8bf80f6a2d37f4488f780503b) | fix | fully track Web Worker file changes in watch mode | +| [b9505ed09](https://github.com/angular/angular-cli/commit/b9505ed097d60eadae665d4664199e3d4989c864) | fix | generate a file containing a list of prerendered routes | +| [192a2ae6b](https://github.com/angular/angular-cli/commit/192a2ae6bd8bdeab785f1ed8e60c5e4213801dd3) | fix | handle HTTP requests to assets during prerendering | +| [19191e32b](https://github.com/angular/angular-cli/commit/19191e32bab9a2927b4feb5074e14165597fbf6d) | fix | handle HTTP requests to assets during SSG in dev-server | +| [8981d8c35](https://github.com/angular/angular-cli/commit/8981d8c355ec9154fcdcdad3a66e1b789d1079b0) | fix | improve sharing of TypeScript compilation state between various esbuild instances during rebuilds | +| [5a3ae0159](https://github.com/angular/angular-cli/commit/5a3ae0159faa81558537012a0ceba07b5ad1b88b) | fix | in vite skip SSR middleware for path with extensions | +| [f87f22d3f](https://github.com/angular/angular-cli/commit/f87f22d3f1436678ca1e07cc10874a012ae55e60) | fix | keep dependencies pre-bundling validate between builds | +| [0da87bf1c](https://github.com/angular/angular-cli/commit/0da87bf1c94c6caf711204fcdd9a3973d766bd6e) | fix | limit concurrent output file writes with application builder | +| [391ff78cb](https://github.com/angular/angular-cli/commit/391ff78cb0f29212c476ca36940b77839b84075e) | fix | log number of prerendered routes in console | +| [c46f312ad](https://github.com/angular/angular-cli/commit/c46f312adb06ae4a8293a07aa441514030052e93) | fix | media files download files in vite | +| [87425a791](https://github.com/angular/angular-cli/commit/87425a791fbdb44b3504e7e6d4b000b1df92c494) | fix | normalize paths when invalidating stylesheet bundler | +| [d4f37da50](https://github.com/angular/angular-cli/commit/d4f37da50ce2890a2b86281e5a373beab349b630) | fix | only show changed output files in watch mode with esbuild | +| [0d54f2d20](https://github.com/angular/angular-cli/commit/0d54f2d20bfd6d55615c0ab3537b5af0aeb008ee) | fix | only watch used files with application builder | +| [1f299ff2d](https://github.com/angular/angular-cli/commit/1f299ff2de3c80bf9cb3dc4b6a5ff02e81c1a94f) | fix | prebundle dependencies for SSR when using Vite | +| [58bd3971f](https://github.com/angular/angular-cli/commit/58bd3971fd2a95a5da1a87deddfe2416f3d636d6) | fix | process nested tailwind usage in application builder | +| [60ca3c82d](https://github.com/angular/angular-cli/commit/60ca3c82d28d0168b2f608a44a701ad8ad658369) | fix | provide server baseUrl result property in Vite-based dev server | +| [0c20cc4dc](https://github.com/angular/angular-cli/commit/0c20cc4dc5fe64221533d0a4cbe9d907881c85ae) | fix | re-add TestBed compileComponents in schematics to support defer block testing | +| [9453a2380](https://github.com/angular/angular-cli/commit/9453a23800f40a33b16fd887e3aa0817448134b1) | fix | remove CJS usage warnings for inactionable packages | +| [5bf7022c4](https://github.com/angular/angular-cli/commit/5bf7022c4749f1298de61ef75e36769bbb8aba12) | fix | remove support for Node.js v16 | +| [c27ad719f](https://github.com/angular/angular-cli/commit/c27ad719f2cb1b13f76f8fce033087a9124e646d) | fix | remove unactionable error overlay suggestion from Vite-based dev server | +| [263271fae](https://github.com/angular/angular-cli/commit/263271fae3f664da9d396192152d22a9b6e3ef09) | fix | resolve and load sourcemaps during prerendering to provide better stacktraces | +| [651e3195f](https://github.com/angular/angular-cli/commit/651e3195ffe06394212c8d8d275289ac05ea5ef5) | fix | resolve and load sourcemaps when using vite dev server with prerendering and ssr | +| [b78508fc8](https://github.com/angular/angular-cli/commit/b78508fc80bb9b2a3aec9830ad3ae9903d25927b) | fix | several fixes to assets and files writes in browser-esbuild builder | +| [c4c299bce](https://github.com/angular/angular-cli/commit/c4c299bce900b27556eaf2e06838a52f16990bb6) | fix | silence xhr2 not ESM module warning | +| [f7f6e97d0](https://github.com/angular/angular-cli/commit/f7f6e97d0f3540badb68813c39ce0237e4dcc9e3) | fix | skip checking CommonJS module descendants | +| [c11a0f0d3](https://github.com/angular/angular-cli/commit/c11a0f0d36f6cbffdf0464135510bda454efb08b) | fix | support custom index option paths in Vite-based dev server | +| [6c3d7d1c1](https://github.com/angular/angular-cli/commit/6c3d7d1c10907d8d57b5f84f298b324d6f972226) | fix | update `ssr` option definition | +| [4e89c3cae](https://github.com/angular/angular-cli/commit/4e89c3cae43870a10ef58de5ebdc094f5a06023e) | fix | use a dash in bundle names | +| [83b4b2567](https://github.com/angular/angular-cli/commit/83b4b25678ba6b8082d580a2d75b0f02a9addc2a) | fix | use browserslist when processing global scripts in application builder | +| [ca4d1634f](https://github.com/angular/angular-cli/commit/ca4d1634f7fa2070f53f5978387ea68cc875c986) | fix | use component style load result caching information for file watching | +| [34947fc64](https://github.com/angular/angular-cli/commit/34947fc64953f845d33ffb1c52f236869a040c9d) | fix | use incremental component style bundling only in watch mode | +| [ec160fe4e](https://github.com/angular/angular-cli/commit/ec160fe4e89cb89b93278cfac63877093dd19392) | fix | warn if using partial mode with application builder | +| [559e89159](https://github.com/angular/angular-cli/commit/559e89159150a10728272081b7bbda80fe708093) | fix | Windows Node.js 20 prerendering failure ([#26186](https://github.com/angular/angular-cli/pull/26186)) | +| [2cbec36c7](https://github.com/angular/angular-cli/commit/2cbec36c7286cdbbbd547433061421d7fe7762cc) | perf | cache polyfills virtual module result | +| [e06e95f73](https://github.com/angular/angular-cli/commit/e06e95f73a35e2cc7cb00a44ce3633b4c99c8505) | perf | conditionally add Angular compiler plugin to polyfills bundling | +| [61f409cbe](https://github.com/angular/angular-cli/commit/61f409cbe4a7bf59711ef0cfa3b7365a8df3016d) | perf | disable ahead of time prerendering in vite dev-server | +| [01ab16c5d](https://github.com/angular/angular-cli/commit/01ab16c5d5678a135a5af5640ad2ba7c33a00452) | perf | fully avoid rebuild of component stylesheets when unchanged | +| [99d9037ee](https://github.com/angular/angular-cli/commit/99d9037eee2eabd7b5ec2d8f01146578ef6b5860) | perf | only perform a server build when either prerendering, app-shell or ssr is enabled | +| [c013a95e2](https://github.com/angular/angular-cli/commit/c013a95e2f38a5c2435b22c3338bf57b03c84ebf) | perf | only rebundle browser polyfills on explicit changes | +| [e68a662bc](https://github.com/angular/angular-cli/commit/e68a662bc0e636082e43b4f3c894585174366f4d) | perf | only rebundle global scripts/styles on explicit changes | +| [28d9ab88f](https://github.com/angular/angular-cli/commit/28d9ab88fe81898ec7591608816c77455c9a61bf) | perf | only rebundle server polyfills on explicit changes | +| [6d3942723](https://github.com/angular/angular-cli/commit/6d3942723d824382e52a8f06e03dcbc3d6d8eff6) | perf | optimize server or browser only dependencies once | +| [2e8e9d802](https://github.com/angular/angular-cli/commit/2e8e9d8020aa01107a3ee6b31942d9d53d6f73cd) | perf | patch `fetch` to load assets from memory | +| [49fe74e24](https://github.com/angular/angular-cli/commit/49fe74e241d75456c65a7cd439b9eb8842e9d6d7) | perf | reduce CLI loading times by removing critters from critical path | +| [07e2120da](https://github.com/angular/angular-cli/commit/07e2120dab741fda11debc0fe777a5ef888dcaad) | perf | remove JavaScript transformer from server polyfills bundling | +| [c28475d30](https://github.com/angular/angular-cli/commit/c28475d30b08138ddddb9903acaa067cf8ab2ef6) | perf | reuse esbuild generated output file hashes | +| [59c22aa4c](https://github.com/angular/angular-cli/commit/59c22aa4cadd7bc6da20acfd3632c834824044e2) | perf | start SSR dependencies optimization before the first request | +| [223a82f5f](https://github.com/angular/angular-cli/commit/223a82f5f02c8caaf34ce49ee3ddde22a75e65c1) | perf | use incremental bundling for component styles in esbuild builders | +| [4b67d2afd](https://github.com/angular/angular-cli/commit/4b67d2afd3a2d4be188a7313b3fe4ea5c07907b6) | perf | use single JS transformer instance during dev-server prebundling | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------- | +| [f600bbc97](https://github.com/angular/angular-cli/commit/f600bbc97d30a003b9d41fa5f67590d3955e6375) | refactor | remove deprecated `runExternalSchematicAsync` and `runSchematicAsync` | + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [81e4917ce](https://github.com/angular/angular-cli/commit/81e4917ceca89759770a76d63b932f380d83685c) | fix | replace Angular logos | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [dcf3fddff](https://github.com/angular/angular-cli/commit/dcf3fddff2fa4cf3433c5d726be9f514ba41e827) | feat | add performance profiler to `CommonEngine` | +| [6224b0599](https://github.com/angular/angular-cli/commit/6224b0599fd60f61c537aa602fb89079197a6e2d) | fix | correctly set config URL | +| [8d033841d](https://github.com/angular/angular-cli/commit/8d033841d1785944f60ccd425e413865c9caf581) | fix | enable `prerender` and `ssr` for all build configuration | +| [ee0991bed](https://github.com/angular/angular-cli/commit/ee0991beddc96160f9ba7e27b29def54868f3490) | fix | enable performance profiler option name | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [f43754570](https://github.com/angular/angular-cli/commit/f437545705d41c781498b8e7724293455cf3edf9) | feat | add automated preconnects for image domains | +| [4fe03266a](https://github.com/angular/angular-cli/commit/4fe03266a9232346ec49defa98d9eb3a8d88b1ff) | fix | account for arrow function IIFE | +| [828030da0](https://github.com/angular/angular-cli/commit/828030da0fa9e82fa784c4f55e3c089c7c601e98) | fix | account for styles specified as string literals and styleUrl | +| [16428fc97](https://github.com/angular/angular-cli/commit/16428fc97ae64627f790346e6b54b94a67c7202c) | fix | adjust static scan to find image domains in standlone components | +| [486becdbb](https://github.com/angular/angular-cli/commit/486becdbbaec7cacfa42bd66882efe720473b0f6) | fix | remove setClassDebugInfo calls | +| [89f21ac8c](https://github.com/angular/angular-cli/commit/89f21ac8c4309614a59cda5a8ebc3b3fbc663932) | fix | remove setClassMetadataAsync calls | +| [8899fb9e3](https://github.com/angular/angular-cli/commit/8899fb9e36556debe3b262f27c1b6e94c4963144) | fix | skip transforming empty inline styles in Webpack JIT compilations | + + + + + +# 16.2.10 (2023-11-08) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------- | +| [bab3672cd](https://github.com/angular/angular-cli/commit/bab3672cdaf4875cf83f94e34abdef29cffe2686) | fix | normalize exclude path | + + + + + +# 16.2.8 (2023-10-25) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [44275601b](https://github.com/angular/angular-cli/commit/44275601ba0e4c7b8c24f8184a33d09350a0fbef) | fix | remove the need to specify `--migrate-only` when `--name` is used during `ng update` | + + + + + +# 16.2.7 (2023-10-19) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------ | +| [f1a0c3361](https://github.com/angular/angular-cli/commit/f1a0c3361a6caa27bdf5cc07315d8bf2b6424b11) | fix | change Twitter logo to X | + + + + + +# 16.2.6 (2023-10-11) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- | +| [c6ea25626](https://github.com/angular/angular-cli/commit/c6ea2562683cc6e640136a02760db9363ded4352) | fix | fully downlevel async/await when using vite dev-server with caching enabled | + + + + + +# 15.2.10 (2023-10-05) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [05213c95b](https://github.com/angular/angular-cli/commit/05213c95b032dd64fdc73ed33af695e9f19b5d09) | fix | update dependency postcss to v8.4.31 | + + + + + +# 14.2.13 (2023-10-05) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [1ca44dcd9](https://github.com/angular/angular-cli/commit/1ca44dcd9d79916db70180da37b962c2672a76a8) | fix | update dependency postcss to v8.4.31 | + + + + + +# 16.2.5 (2023-10-04) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------- | +| [933358186](https://github.com/angular/angular-cli/commit/93335818689a67557942ab27ec8cc5b96f2a5abe) | fix | do not print `Angular is running in development mode.` in the server console when using dev-server | +| [493bd3906](https://github.com/angular/angular-cli/commit/493bd390679889359a05b2f23b74787647aee341) | fix | update dependency postcss to v8.4.31 | + + + + + +# 16.2.4 (2023-09-27) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [5dc7fb1a1](https://github.com/angular/angular-cli/commit/5dc7fb1a1849a427ceedb06404346de370c91083) | fix | update `@angular/cli` version specifier to use `^` | + + + + + +# 16.2.3 (2023-09-20) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [39643bee1](https://github.com/angular/angular-cli/commit/39643bee1522e0313be612b564f2b96ec45007ec) | fix | correctly re-point RXJS to ESM on Windows | +| [d8d116b31](https://github.com/angular/angular-cli/commit/d8d116b318377d51f258a1a23025be2d41136ee3) | fix | several windows fixes to application builder prerendering | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------- | +| [f1195d035](https://github.com/angular/angular-cli/commit/f1195d0351540bdcc7d3f3e7cf0761389eb3d569) | fix | fix recursion in webpack resolve | + + + + + +# 16.2.2 (2023-09-13) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [e3a40a49a](https://github.com/angular/angular-cli/commit/e3a40a49aa768c6b0ddce24ad47c3ba50028963c) | fix | support dev server proxy pathRewrite field in Vite-based server | + + + + + +# 16.2.1 (2023-08-30) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [221ab2483](https://github.com/angular/angular-cli/commit/221ab2483a5504b0ad864a18dc5a4dbeb8c0748e) | fix | display warning when using `resourcesOutputPath` with esbuild builder | +| [fe752ad87](https://github.com/angular/angular-cli/commit/fe752ad87b8588e2a1ee1611953b36d5c004e673) | fix | encode Sass package resolve directories in importer URLs | +| [82b0f94fd](https://github.com/angular/angular-cli/commit/82b0f94fdacc5f4665d00eeb1c93fcfc104b0cc8) | fix | handle HMR updates of global CSS when using Vite | +| [6a48a11b8](https://github.com/angular/angular-cli/commit/6a48a11b8c218796e4b778bd00d453fc0ac0c48e) | fix | update vite to be able to serve app-shell and SSG pages | +| [fdb16f7cd](https://github.com/angular/angular-cli/commit/fdb16f7cd4327980436ddb1ce190c67c86588d2d) | fix | use correct type for `extraEntryPoints` | + + + + + +# 16.2.0 (2023-08-09) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [e6b377436](https://github.com/angular/angular-cli/commit/e6b377436a471073657dc35e7c7a28db6688760a) | feat | add `ssr` option in application builder | +| [c05c83be7](https://github.com/angular/angular-cli/commit/c05c83be7c6c8bcdad4be8686a6e0701a55304cc) | feat | add initial application builder implementation | +| [095f5aba6](https://github.com/angular/angular-cli/commit/095f5aba60a4c1267a87b8b3ae38dbfbf70731f1) | feat | add initial support for server bundle generation using esbuild | +| [cb165a75d](https://github.com/angular/angular-cli/commit/cb165a75dc8c21ead537684a092ed50d3736e04a) | feat | add pre-rendering (SSG) and App-shell support generation to application builder | +| [2a3fc6846](https://github.com/angular/angular-cli/commit/2a3fc68460152a48758b9353bff48193641861c5) | feat | add preload hints based on transitive initial files | +| [099cec758](https://github.com/angular/angular-cli/commit/099cec758ad671c7fd0ca2058a271e4fe181a44d) | feat | add support for serving SSR with dev-server when using the application builder | +| [449e21b3a](https://github.com/angular/angular-cli/commit/449e21b3a6da990a5865bb5bdfb8145794f40cf9) | fix | correctly load dev server assets with vite 4.4.0+ | +| [f42f10135](https://github.com/angular/angular-cli/commit/f42f10135c1e2184a9080b726dc5e41669937b13) | fix | ensure preload hints for external stylesheets are marked as styles | +| [7defb3635](https://github.com/angular/angular-cli/commit/7defb3635c89737d151c9537bd7becd463098434) | fix | ensure that server dependencies are loaded also in ssr entrypoint | +| [05f31bd28](https://github.com/angular/angular-cli/commit/05f31bd28f002a232598e0468dc418f99e434ae0) | fix | prevent race condition in setting up sass worker pool | +| [5048f6e82](https://github.com/angular/angular-cli/commit/5048f6e82e299b0733f34cbdcb1e7b1ef9a63210) | fix | Set chunk names explicitly | +| [974748cdf](https://github.com/angular/angular-cli/commit/974748cdf894c5ad0451e3fdf1c186bdad80878b) | perf | filter postcss usage based on content in esbuild builder | +| [61a652d91](https://github.com/angular/angular-cli/commit/61a652d91274f4adce20182e630fe9963b4ceddd) | perf | inject Sass import/use directive importer information when resolving | +| [a0a2c7aef](https://github.com/angular/angular-cli/commit/a0a2c7aef675f8aae294d2119f721c4345d633b0) | perf | only load browserslist in babel preset if needed | +| [6bfd1800e](https://github.com/angular/angular-cli/commit/6bfd1800efa2bf41126696b66938bdf291ad5455) | perf | use in-memory Sass module resolution cache | + + + + + +# 16.1.8 (2023-08-04) + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------- | +| [7a420d338](https://github.com/angular/angular-cli/commit/7a420d3382b21d24c73b722e849f01b0aacfb860) | fix | build: update critters | + + + + + +# 16.1.7 (2023-08-02) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [1dab4ed87](https://github.com/angular/angular-cli/commit/1dab4ed8738b42d6b93298889caf1546b011706f) | fix | hot update filename suffix with `.mjs` | + + + + + +# 16.1.6 (2023-07-26) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [20816b57f](https://github.com/angular/angular-cli/commit/20816b57f16b0bcbd5b81f06f6f790e4485c1daa) | fix | error during critical CSS inlining for external stylesheets | + + + + + +# 16.1.5 (2023-07-20) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------- | +| [7e91d4709](https://github.com/angular/angular-cli/commit/7e91d4709966c592c271ff8d3456ce569156e2e5) | fix | add `zone.js` to `ng version` output | +| [475506822](https://github.com/angular/angular-cli/commit/475506822b148c8e2597c60653238a40140bacb0) | fix | throw an error when executed in a google3-context | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [07d3d8c6a](https://github.com/angular/angular-cli/commit/07d3d8c6ae01212de866fac769ff2da888d5adea) | fix | correctly wrap CommonJS exported enums when optimizing | + + + + + +# 16.1.4 (2023-07-06) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [7016cee57](https://github.com/angular/angular-cli/commit/7016cee5783b2762d550db8f2a4f29e7b56f317f) | fix | normalize paths in loader cache with esbuild | + + + + + +# 16.1.3 (2023-06-29) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [b56ab0798](https://github.com/angular/angular-cli/commit/b56ab07980c5d990606ddb1e298fc1c4ecf8a2a8) | fix | use absolute watch paths for postcss dependency messages | + + + + + +# 15.2.9 (2023-06-28) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [f36e38a91](https://github.com/angular/angular-cli/commit/f36e38a913b454ec340d6bf2311391c5df1cee24) | fix | update direct semver dependencies to 7.5.3 | + + + + + +# 16.1.2 (2023-06-28) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [3475e0281](https://github.com/angular/angular-cli/commit/3475e0281da3298f288a5f8836155c0b8c971372) | fix | update direct `semver` dependencies to 7.5.3 | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [8108b8c2d](https://github.com/angular/angular-cli/commit/8108b8c2da3ebfdb74f0f9d3554df01f484670bd) | fix | allow linker JIT support with prebundling with esbuild builder | +| [502365037](https://github.com/angular/angular-cli/commit/502365037bf7dbacd0e28d29a074a246971848ea) | fix | use all style language watch files in esbuild builder | + + + + + +# 14.2.12 (2023-06-28) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [bd396b656](https://github.com/angular/angular-cli/commit/bd396b65623fb0b8e826be13f88709e87b54336e) | fix | update direct semver dependencies to 7.5.3 | + + + + + +# 16.1.1 (2023-06-22) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [f017fee2e](https://github.com/angular/angular-cli/commit/f017fee2e93a4207b7bfd69c838991546b398753) | fix | actually disable Vite prebundling file discovery | +| [2b4beaca2](https://github.com/angular/angular-cli/commit/2b4beaca2c32c11508078e082b3338d1edb414a0) | fix | experimental esbuild pipeline, add `es2015` to main fields for RxJS v6 compatibility | +| [e3c85e00e](https://github.com/angular/angular-cli/commit/e3c85e00e6b3390f239aaeb3db6a38fe4b4d2523) | fix | track postcss provided file dependencies in esbuild builder | +| [1419fff88](https://github.com/angular/angular-cli/commit/1419fff887173e331690fb0a664a081154842554) | fix | unpin and downgrade `browserslist` | +| [950a4b60f](https://github.com/angular/angular-cli/commit/950a4b60f046117867755ccd005f0e04bcc403a7) | fix | watch all bundler provided inputs with esbuild builder | + + + + + +# 16.1.0 (2023-06-13) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [b14b95990](https://github.com/angular/angular-cli/commit/b14b959901d5a670da0df45e082b8fd4c3392d14) | feat | add bootstrap-agnostic utilities for writing ng-add schematics | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [3ede1a2ca](https://github.com/angular/angular-cli/commit/3ede1a2cac5005f4dfbd2a62ef528a34c3793b78) | feat | allow forcing esbuild builder with dev-server | +| [2d141fe3b](https://github.com/angular/angular-cli/commit/2d141fe3bc1efb9e254b15ce91ebc885a43c928a) | feat | show estimated transfer size with esbuild builder | +| [9aa9b5264](https://github.com/angular/angular-cli/commit/9aa9b5264eee1b1dda7abd334b560d4b446c4970) | feat | support autoprefixer/tailwind CSS with Less/Sass in esbuild builder | +| [3d1c09b23](https://github.com/angular/angular-cli/commit/3d1c09b235bf1db0d031c36fdc68ab99359b34b1) | feat | support dev-server package prebundling with esbuild builder | +| [d8930facc](https://github.com/angular/angular-cli/commit/d8930facc075e39d82b3c6cb252c9a8b5fa6a476) | feat | support incremental TypeScript semantic diagnostics in esbuild builder | +| [5cacd34a2](https://github.com/angular/angular-cli/commit/5cacd34a222eea16c18caa63dbe4448b81e106f3) | fix | watch all TypeScript referenced files in esbuild builder | +| [8336ad80d](https://github.com/angular/angular-cli/commit/8336ad80da41cde69343960f7515d9ffd5e5e2e1) | perf | enable in-memory load result caching for stylesheets in esbuild builder | + + + + + +# 16.0.6 (2023-06-13) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [eebb54cbf](https://github.com/angular/angular-cli/commit/eebb54cbf4683b6113eb56dba17fab038318c918) | fix | correctly handle sass imports | +| [081b62539](https://github.com/angular/angular-cli/commit/081b62539b2562bff130343558bf4baafed7c36d) | fix | support proxy configuration array-form in esbuild builder | + + + + + +# 16.0.5 (2023-06-07) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [9817b984b](https://github.com/angular/angular-cli/commit/9817b984b15e352caedac6e347cc662117b9e0f8) | fix | ignore .git folder in browser-esbuild watcher | +| [ce95d2545](https://github.com/angular/angular-cli/commit/ce95d254510ffa93a9bd4230f6447530d511ef5f) | fix | ignore folders starting with a dot in browser-esbuild watcher | + + + + + +# 16.0.4 (2023-06-01) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [5bff97d5b](https://github.com/angular/angular-cli/commit/5bff97d5b965373cd7e4dc0b731c24d80b512faa) | fix | correctly set overridden compiler option | +| [cd0247514](https://github.com/angular/angular-cli/commit/cd0247514db295661d319bec33ad7167229d99f9) | fix | preemptively remove AOT metadata in esbuild builder | + + + + + +# 16.0.3 (2023-05-25) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [1d83bb656](https://github.com/angular/angular-cli/commit/1d83bb6565550107ab00de52b706cad8f28514b3) | fix | percent encode asset URLs in development server for esbuild | + + + + + +# 16.0.2 (2023-05-17) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [7a3c895c8](https://github.com/angular/angular-cli/commit/7a3c895c8da534ceff26754ca7ffd49b30c24069) | fix | attempt relative global script read first in esbuild builder | +| [f30be2518](https://github.com/angular/angular-cli/commit/f30be2518b118106f5d6634c92279adcefab0f70) | fix | correctly generate serviceworker hashes for binary assets | +| [117e8d001](https://github.com/angular/angular-cli/commit/117e8d00192d3b764c9c362c2554fa80706946cf) | fix | normalize Vite dev-server Windows asset paths | +| [e5c1d43de](https://github.com/angular/angular-cli/commit/e5c1d43de932daedfaac002ff363ed12243f97bb) | perf | minor sourcemap ignorelist improvements for esbuild builder | + + + + + +# 16.0.1 (2023-05-10) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [ed82c83fe](https://github.com/angular/angular-cli/commit/ed82c83fef1a67b4168be455b119860217267564) | fix | avoid CommonJS warnings for relative imports with esbuild builders | +| [3083c4eda](https://github.com/angular/angular-cli/commit/3083c4eda87e735a4b1b9e16ff1f61abbccb1c98) | fix | avoid hash filenames for non-injected global styles/scripts | +| [b106bc9d0](https://github.com/angular/angular-cli/commit/b106bc9d07b1e2e38176c484d2fc04251e274aa5) | fix | clean incoming index URL before processing in esbuild builder | +| [2967705ed](https://github.com/angular/angular-cli/commit/2967705ed3f88c35e93866bca659222769664c62) | fix | convert dev-server glob proxy entries for esbuild builder | +| [a9d20015c](https://github.com/angular/angular-cli/commit/a9d20015c943e89b6f29a6e3e295bef6e2072a92) | fix | disable runtime errors from being displayed in overlay | +| [822b552f6](https://github.com/angular/angular-cli/commit/822b552f6f94ac1c39405f7359550e1ab5aa4c17) | fix | fix index option const value for browser-esbuild | +| [131cd23b6](https://github.com/angular/angular-cli/commit/131cd23b65c12ba671088aafcaff4d522f402ba8) | fix | prevent relative import failure with Less in esbuild builder | +| [fedcc5d92](https://github.com/angular/angular-cli/commit/fedcc5d923b7237622b1e7adef053a2ee68f872e) | fix | properly set base dev-server path with esbuild | +| [cb3161045](https://github.com/angular/angular-cli/commit/cb3161045ef39e335460672d016cf0a973de428a) | fix | show error note for CSS url() tilde usage in esbuild builder | +| [54e5000ca](https://github.com/angular/angular-cli/commit/54e5000ca88655bf9d01b87e317dc5810a7ac676) | fix | workaround for esbuild static block AOT generated code | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [5a35970af](https://github.com/angular/angular-cli/commit/5a35970afdf39461592bb0130eb9b959272949fb) | fix | do not generate an UpdateBuffer for created and overridden files | + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------ | +| [70d224ca7](https://github.com/angular/angular-cli/commit/70d224ca7edbfe31fb6360e55cbe06c65dc5e91a) | fix | compress PWA icons | + + + + + +# 16.0.0 (2023-05-03) + +## Breaking Changes + +### @angular/cli + +- The deprecated `defaultCollection` workspace option has been removed. Use `schematicCollections` instead. + + Before + + ```json + "defaultCollection": "@angular/material" + ``` + + After + + ```json + "schematicCollections": ["@angular/material"] + ``` + +- The deprecated `defaultProject` workspace option has been removed. The project to use will be determined from the current working directory. +- Node.js v14 support has been removed + + Node.js v14 is planned to be End-of-Life on 2023-04-30. Angular will stop supporting Node.js v14 in Angular v16. + Angular v16 will continue to officially support Node.js versions v16 and v18. + +### @schematics/angular + +- `ng g resolver` and `ng g guard` now generate a functional resolver or guard by default. It is still possible to generate a (deprecated) class-based resolver or guard by using `ng g resolver --no-functional` or `ng g guard --no-functional`. +- The CLI no longer allows to generate `CanLoad` guards. Use `CanMatch` instead. + +### + +- - TypeScript 4.8 is no longer supported. + +### @angular-devkit/build-angular + +- Deprecated `outputPath` and `outputPaths` from the server and browser builder have been removed from the builder output. Use `outputs` instead. + + Note: this change does not effect application developers. + +### @angular-devkit/core + +- Several changes to the `SchemaRegistry`. + - `compile` method now returns a `Promise`. + - Deprecated `flatten` has been removed without replacement. +- - `ContentHasMutatedException`, `InvalidUpdateRecordException`, `UnimplementedException` and `MergeConflictException` API from `@angular-devkit/core` have been removed in favor of the API from `@angular-devkit/schematics`. + - `UnsupportedPlatformException` - A custom error exception should be created instead. + +### @angular-devkit/schematics + +- The depracated `UpdateBuffer` has been removed and `UpdateBuffer2` + is renamed to `UpdateBuffer`. With this change the related and + deprecated symbols `ContentCannotBeRemovedException` and `Chunk` + have also been removed. + +### @ngtools/webpack + +- NGCC integration has been removed and as a result Angular View Engine libraries will no longer work. + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [c2d2da41b](https://github.com/angular/angular-cli/commit/c2d2da41b15143e11f597192eef755c5e3fb4c5d) | feat | add support to add service worker to standalone application | +| [22fdd7da9](https://github.com/angular/angular-cli/commit/22fdd7da97c832048410ca89622712d097490c5d) | feat | generate functional resolvers and guards by default | +| [a832c2028](https://github.com/angular/angular-cli/commit/a832c202828a1caa425e1a0c5ff8d2ebb77c4667) | feat | Implement a standalone flag for new applications | +| [5ceedcb11](https://github.com/angular/angular-cli/commit/5ceedcb11e3ca5bdad4248c7c76ca2562fab43f2) | feat | remove deprecated CanLoad option for guards | +| [c9e84d024](https://github.com/angular/angular-cli/commit/c9e84d0243b4e9191f6cfcd72ebf8288de2b6f2d) | feat | remove generation of `BrowserModule.withServerTransition` | +| [50b9e59a5](https://github.com/angular/angular-cli/commit/50b9e59a50b737e34ee12ee48ab83d17c2b8744a) | feat | update app-shell schematic to support standalone applications | +| [dc5cc893d](https://github.com/angular/angular-cli/commit/dc5cc893d6c3d4e5e6f6c4b19bee632b66a94fc0) | feat | Update universal schematic to support standalone applications | +| [f98c9de80](https://github.com/angular/angular-cli/commit/f98c9de80952593e0294538d96bdac7136629f77) | fix | add experimental message when using standalone application schematic. | +| [a5cb46124](https://github.com/angular/angular-cli/commit/a5cb46124234ec2c47f6288914ad3ed9564f3a72) | fix | add standalone option to library library | +| [b2ed7bd10](https://github.com/angular/angular-cli/commit/b2ed7bd100bfe77dca81c590b827870fd496075f) | fix | provide migration that disables build optimizer on dev server builds | +| [ba4414b2c](https://github.com/angular/angular-cli/commit/ba4414b2cfb7a040393f314d87ab823bcad75f26) | fix | reformat app.config.ts | +| [202e9a50f](https://github.com/angular/angular-cli/commit/202e9a50f62b7927c0900469b21d323b3010762d) | fix | remove compileComponents from component test schematic | +| [0d58f73c5](https://github.com/angular/angular-cli/commit/0d58f73c50ce496dd3a0166533069f450f83a461) | fix | rename `app.server.module.ts` to `app.module.server.ts` | +| [de6d30102](https://github.com/angular/angular-cli/commit/de6d30102978eebda7edbdda43ca50f18c4c8aaf) | fix | replace `provideServerSupport` with `provideServerRendering` | +| [bff634fe0](https://github.com/angular/angular-cli/commit/bff634fe0938ecb4a316064ba3f1b9c2c1f208fe) | fix | update private Components utilities to work with standalone project structure | +| [85fe820b0](https://github.com/angular/angular-cli/commit/85fe820b081b73b229084882e98e65b5c57f9d0f) | fix | use same property order in standalone AppComponent | + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ | +| [68024234e](https://github.com/angular/angular-cli/commit/68024234edcb942d5a177d6bd7567e77d7e40245) | feat | remove deprecated `defaultCollection` from workspace configuration | +| [d58428d3d](https://github.com/angular/angular-cli/commit/d58428d3dbdb7275e2e4f6d271fcc5fdda5c489e) | feat | remove deprecated `defaultProject` from workspace configuration | +| [7cb5689e0](https://github.com/angular/angular-cli/commit/7cb5689e02c30c0ef53adef92d0e9969e1a1536b) | feat | show optional migrations during update process | +| [c29c8e18d](https://github.com/angular/angular-cli/commit/c29c8e18d84096e2f72af12643c31bde51010548) | refactor | remove Node.js v14 support | + +### + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ----- | ---------------------------------------------------------- | +| [5a171ddff](https://github.com/angular/angular-cli/commit/5a171ddff66ff366089616736baf7545fe44f570) | build | update to TypeScript 5 and drop support for TypeScript 4.8 | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [48871381a](https://github.com/angular/angular-cli/commit/48871381a169888f1d29275ab25915b0d815d1c1) | fix | allow registered builder teardowns to execute | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- | +| [ff5ebf9b1](https://github.com/angular/angular-cli/commit/ff5ebf9b1244c5a01961cd3dba6bb345392aa57c) | feat | add CSP support for inline styles | +| [ee8013f66](https://github.com/angular/angular-cli/commit/ee8013f66f7587ba85ed76fb0c662168fd850c47) | feat | display build output table with esbuild | +| [0eac98f61](https://github.com/angular/angular-cli/commit/0eac98f6176bde662d7d7e9532b5a988b8e7ece2) | feat | implement progress option for esbuild builder | +| [f04859d16](https://github.com/angular/angular-cli/commit/f04859d16117a41b6e8ad698a449aca73456b9d7) | feat | initial autoprefixer support for CSS in esbuild builder | +| [8c550302c](https://github.com/angular/angular-cli/commit/8c550302cc046e649f1245007e0e26550a61f931) | feat | initial development server for esbuild-based builder | +| [52969db6b](https://github.com/angular/angular-cli/commit/52969db6bdaf42ec7d7f28274eba518ed1a794b7) | feat | initial tailwindcss support for CSS in esbuild builder | +| [ce46ecae0](https://github.com/angular/angular-cli/commit/ce46ecae011595c86fea265e121ea313bb3cb030) | feat | support module resolution with less stylesheets in esbuild builder | +| [584b51907](https://github.com/angular/angular-cli/commit/584b51907c3b3f60db5478994fff3f800b70c3f2) | feat | support scripts option with esbuild builder | +| [e4883b0ee](https://github.com/angular/angular-cli/commit/e4883b0ee1d1ee7cd57e6cb374944021a100fd3b) | feat | support SSL options with esbuild development server | +| [290802060](https://github.com/angular/angular-cli/commit/2908020601e627b7c76c6fe8d53e19e8858cd325) | feat | support standalone app-shell generation | +| [766c14698](https://github.com/angular/angular-cli/commit/766c14698473fe333168c06e3b88c7303e868acf) | fix | add sourcemap `x_google_ignoreList` support for esbuild builder | +| [cdfa7ca88](https://github.com/angular/angular-cli/commit/cdfa7ca88c2e79564192d4b7fdafb53d97f2607d) | fix | allow multiple polyfills with esbuild-based builder | +| [e690b7cbd](https://github.com/angular/angular-cli/commit/e690b7cbde470b69b3c23fa9af1ecfca4c8e3a7e) | fix | always enable `looseEnums` build optimizer rule | +| [135ab4c36](https://github.com/angular/angular-cli/commit/135ab4c363d5d247342c4bc123a17eb66de17752) | fix | avoid double sourcemap comments with esbuild dev-server | +| [dcf60d2be](https://github.com/angular/angular-cli/commit/dcf60d2be26fdbc1efaec1c506188cb166ffbdf0) | fix | correctly filter lazy global styles in esbuild builder | +| [342a4ea30](https://github.com/angular/angular-cli/commit/342a4ea30e1ab9cbdbe5d6de339c21bdcff1a2c1) | fix | correctly show initial files in stat table with esbuild builder | +| [107851ae4](https://github.com/angular/angular-cli/commit/107851ae45d8399782cbc73d3fa09b3f779e1e02) | fix | display warning when `preserveWhitespaces` is set in the tsconfig provided to the server builder | +| [ff8a89cbf](https://github.com/angular/angular-cli/commit/ff8a89cbfd308a0312d16956d55c30e2425e2d33) | fix | ensure all build resources are served in esbuild dev server | +| [f76a8358e](https://github.com/angular/angular-cli/commit/f76a8358ea07a0d00fb0eb1c62dfaccf056531be) | fix | ensure directories are properly ignored in esbuild builder | +| [005ba4276](https://github.com/angular/angular-cli/commit/005ba427661f0e5907020aea10c432a324b528a8) | fix | ensure empty component styles compile with esbuild | +| [f74151baa](https://github.com/angular/angular-cli/commit/f74151baab740df15a5cc80255d97d0320147b2a) | fix | exclude `@angular/platform-server/init` from unsafe optimizations | +| [f72155bc7](https://github.com/angular/angular-cli/commit/f72155bc7025f4e0b23eb58a92e422bd341720f6) | fix | fully remove third-party sourcemaps when disabled in esbuild builder | +| [26dced95c](https://github.com/angular/angular-cli/commit/26dced95c5612f6386b3179fce50904f178ee569) | fix | JIT support for standalone applications | +| [4822b3ba5](https://github.com/angular/angular-cli/commit/4822b3ba55ec824913e895e76cf83e2b36ec99f9) | fix | keep esbuild server active until builder fully stops | +| [adbf2c8a1](https://github.com/angular/angular-cli/commit/adbf2c8a1ed67f505ea27921c00f957509e9a958) | fix | normalize long-form asset option output to relative path | +| [67670b612](https://github.com/angular/angular-cli/commit/67670b612e2397e26a974cd337cdce1a9c6a0f21) | fix | pass listening port in result for esbuild dev server | +| [1a8833b21](https://github.com/angular/angular-cli/commit/1a8833b211cbf2535d3deed1029591050bc995b8) | fix | provide option to run build-optimizer on server bundles | +| [b8c9667f9](https://github.com/angular/angular-cli/commit/b8c9667f9292d3829bfcac10a98acd859301c3c7) | fix | remove unintended files in esbuild output stats table | +| [04274afc1](https://github.com/angular/angular-cli/commit/04274afc15084ead2916e11055aa8f1d2f61951d) | fix | set public class fields as properties ([#24849](https://github.com/angular/angular-cli/pull/24849)) | +| [a778fe6c2](https://github.com/angular/angular-cli/commit/a778fe6c2e7b9ca0c0995e1350460e97085b39a1) | fix | show lazy files in stat table correctly with esbuild | +| [955b493b1](https://github.com/angular/angular-cli/commit/955b493b13e0a8956706c486d31d9e4338bf41c5) | fix | support CSP on critical CSS link tags. | +| [c272172c8](https://github.com/angular/angular-cli/commit/c272172c84bef35f63038f1fc5fa184b1e2d99bf) | fix | update esbuild builder complete log | +| [0b450578a](https://github.com/angular/angular-cli/commit/0b450578a74e2b46488ae2e97c7f76389baa5271) | fix | update list of known tailwind configuration files | +| [759ae92aa](https://github.com/angular/angular-cli/commit/759ae92aaa595fe3f6000f3aae0e6bb8d025db3a) | fix | update peer dependencies to support version 16 | +| [eca366a84](https://github.com/angular/angular-cli/commit/eca366a843be1fcc8d949bc335cac4cdcbdea41c) | fix | use preserveSymlinks option for tsconfigs in esbuild builder | +| [28c27567c](https://github.com/angular/angular-cli/commit/28c27567cf90712e6c8f4d483bcc0e0fc683ee9b) | perf | asynchronously delete output path in esbuild builder | +| [458400b7b](https://github.com/angular/angular-cli/commit/458400b7b1a435e2febe2c4e1a9fd1ca4eda58d0) | perf | avoid unnessary iterations | +| [a710a262a](https://github.com/angular/angular-cli/commit/a710a262aed8a6c4a6af48e0ad7f479f0a23212e) | perf | cache Sass in memory with esbuild watch mode | +| [e1398d333](https://github.com/angular/angular-cli/commit/e1398d333e86b6caad8b5cfef7048fefd77a9e22) | perf | do not inline sourcemap when using vite dev-server | +| [b2ece91b7](https://github.com/angular/angular-cli/commit/b2ece91b7488a01b6ddfcba1e68f97416c8b05f7) | perf | enhance Sass package resolution in esbuild builder | +| [aae34fc02](https://github.com/angular/angular-cli/commit/aae34fc02dc774d59ecac6483288f47074ee8c2d) | perf | fully lazy load sass in esbuild builder | +| [9ea3e8e34](https://github.com/angular/angular-cli/commit/9ea3e8e349dd1765d5935517999a1879a7a0227d) | perf | only import esbuild watcher when in watch mode | +| [f88ac6fdf](https://github.com/angular/angular-cli/commit/f88ac6fdfee6abf406720c9bc72aa9ddadb112f9) | perf | skip Angular linker in JIT mode with esbuild | +| [a99018cd7](https://github.com/angular/angular-cli/commit/a99018cd7bb66ee53026e06deae6a14455023910) | refactor | remove deprecated `outputPaths` and `outputPath` Builder output | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------- | +| [f6624b974](https://github.com/angular/angular-cli/commit/f6624b974faf13fa718d304e1a473260c16f0c1d) | feat | update SchemaRegistry `compile` to return `Promise` | +| [0ad81cdbc](https://github.com/angular/angular-cli/commit/0ad81cdbc72e80ca75d9d5cc2bc0c6163267a0bb) | refactor | remove deprecated exceptions | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ | +| [d2ef386f4](https://github.com/angular/angular-cli/commit/d2ef386f46131af904ca800cc77388c03239cd9d) | refactor | remove `UpdateBuffer` and rename `UpdateBuffer2` to `UpdateBuffer` | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ----------------------- | +| [c8ac660d8](https://github.com/angular/angular-cli/commit/c8ac660d8b13922be7ebcc92dfd5b18392602c40) | refactor | remove NGCC integration | + + + + + +# 15.2.8 (2023-05-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [069dcdf0c](https://github.com/angular/angular-cli/commit/069dcdf0c4e614fea83af61d4496bdd8a96920ca) | docs | improve wording in doc command version description | + + + + + +# 15.2.7 (2023-04-26) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [f4a6dac87](https://github.com/angular/angular-cli/commit/f4a6dac8782808e564678b4484f3ce87e59f6c8f) | fix | process keeps running when analytics are enabled | +| [f9b2fb1c4](https://github.com/angular/angular-cli/commit/f9b2fb1c4981ff138992a502d3aba4f6a3886df4) | perf | register CLI commands lazily | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [d9aefd6da](https://github.com/angular/angular-cli/commit/d9aefd6da5bd6ea244da3a8d5ea3dcbbadd31f99) | fix | replace vscode launch type from `pwa-chrome` to `chrome` | + + + + + +# 15.2.6 (2023-04-12) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [f0b257ef4](https://github.com/angular/angular-cli/commit/f0b257ef4ae62f92d70bfd2a4e9912d4ceff9c78) | fix | ignore hidden directories when running browserlist migration | + + + + + +# 15.2.5 (2023-04-05) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------ | +| [db173d7ed](https://github.com/angular/angular-cli/commit/db173d7edf685df67b782d81d1bacb84b8debf9a) | fix | collect tech information | + +## Special Thanks + +Alan Agius + + + + + +# 15.2.4 (2023-03-16) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------- | +| [f74bfea24](https://github.com/angular/angular-cli/commit/f74bfea241b189f261ec81a8561aea7a56774ae8) | fix | update `webpack` dependency to `5.76.1` | + +## Special Thanks + +Alan Agius + + + + + +# 14.2.11 (2023-03-16) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------- | +| [ddd33bf38](https://github.com/angular/angular-cli/commit/ddd33bf38d7d76e816ebc0459559917da514477d) | fix | update webpack dependency to `5.76.1` | + +## Special Thanks + +Alan Agius and Joey Perrott + + + + + +# 13.3.11 (2023-03-16) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------- | +| [50fa9300f](https://github.com/angular/angular-cli/commit/50fa9300f264f68ad35606ae46da820c3798f665) | fix | update `webpack` dependency to `5.76.1` | + +## Special Thanks + +Alan Agius and Joey Perrott + + + + + +# 15.2.3 (2023-03-15) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [a93680585](https://github.com/angular/angular-cli/commit/a9368058517509b277236d6e7db4abc6248817fa) | fix | correct wrap ES2022 classes with static properties | + +## Special Thanks + +Alan Agius and Paul Gschwendtner + + + + + +# 15.2.2 (2023-03-08) + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------- | +| [dfd03aa7c](https://github.com/angular/angular-cli/commit/dfd03aa7c262f4425fa680e205a46792bd7b8451) | fix | correctly transform numbers from prompts | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [eb22f634f](https://github.com/angular/angular-cli/commit/eb22f634f2ec7a5b0bc2f5300682ed8e718b1424) | fix | build optimizer support for non spec-compliant ES2022 class static properties | + +## Special Thanks + +Alan Agius + + + + + +# 15.2.1 (2023-03-01) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- | +| [9a5609a44](https://github.com/angular/angular-cli/commit/9a5609a440fc49b3f7ddf88efb73618b7eede1ea) | fix | improve parsing of error messages | + +## Special Thanks + +Alan Agius and Paul Gschwendtner + + + + + +# 15.2.0 (2023-02-22) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [0f58a17c4](https://github.com/angular/angular-cli/commit/0f58a17c4ce92495d96721bc3f2b632a890bbab4) | feat | log number of files update during `ng update` | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------- | +| [ecf43090d](https://github.com/angular/angular-cli/commit/ecf43090d110f996f45a259c279f1b83dcab3fd8) | feat | auto detect package manager ([#24305](https://github.com/angular/angular-cli/pull/24305)) | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [01b3bcf89](https://github.com/angular/angular-cli/commit/01b3bcf898108f9b879da4a791fa2a21c6d9f7c5) | feat | add Less stylesheet support to experimental esbuild-based builder | +| [09af70743](https://github.com/angular/angular-cli/commit/09af70743800aefdefe06e0ca32bcdde18f9eb77) | feat | implement node module license extraction for esbuild builder | +| [bbc1a4f0d](https://github.com/angular/angular-cli/commit/bbc1a4f0dc93437fe97a53a35f68d978cc50bb9e) | feat | support CommonJS dependency checking in esbuild | +| [8cf0d17fb](https://github.com/angular/angular-cli/commit/8cf0d17fb1b39ea7bbd1c751995a56de3df45114) | feat | support JIT compilation with esbuild | +| [3f6769ef9](https://github.com/angular/angular-cli/commit/3f6769ef953b1f880508a9152e669064cbb4dcc9) | fix | allow empty scripts to be optimized | +| [421417a36](https://github.com/angular/angular-cli/commit/421417a36b13a44d39e0818171482871ea8b895f) | fix | avoid CommonJS warning for zone.js in esbuild | + +## Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Jason Bedard, Joey Perrott, Marvin and Paul Gschwendtner + + + + + +# 15.1.6 (2023-02-15) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [3d1f48fa2](https://github.com/angular/angular-cli/commit/3d1f48fa2991ded75da3a1b3a431480710a8ce15) | fix | add set `SessionEngaged` in GA | +| [df07ab113](https://github.com/angular/angular-cli/commit/df07ab11351d6f2d82922ae251ccd17b23d9d0a9) | fix | convert `before` option in `.npmrc` to Date | +| [c787cc780](https://github.com/angular/angular-cli/commit/c787cc7803598eb67260cbd2112d411384d518cc) | fix | replace `os.version` with `os.release`. | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [34a4a1bbf](https://github.com/angular/angular-cli/commit/34a4a1bbf608eb54b0a33b3aa3a6be3e2a576770) | fix | correctly copy `safety-worker.js` contents | +| [88a33155d](https://github.com/angular/angular-cli/commit/88a33155d4bc00077d32bef42588427fb2ed49f4) | fix | update the ECMA output warning message to be more actionable | +| [384ad29c9](https://github.com/angular/angular-cli/commit/384ad29c9a66d78e545ed7e48bf962e4df9d0549) | fix | use babel default export helper in build optimizer | +| [59aa1cdbd](https://github.com/angular/angular-cli/commit/59aa1cdbdf3e2712f988790f68bacc174d070b0c) | perf | reduce rebuilt times when using the `scripts` option | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 15.1.5 (2023-02-08) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [b8bbe9688](https://github.com/angular/angular-cli/commit/b8bbe9688e0e684245636e7d58d50c51719039c8) | fix | error if Angular compiler is used in a schematic | +| [fabbb8a93](https://github.com/angular/angular-cli/commit/fabbb8a936f3b3b1cee8ea5cbdb7bb7832cb02a7) | fix | only set `DebugView` when `NG_DEBUG` is passed | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [499173b5d](https://github.com/angular/angular-cli/commit/499173b5d197f14377203b92b49ff3cbbf55b260) | fix | remove bootstrapping wrapping in universal schematic | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [e87134fe9](https://github.com/angular/angular-cli/commit/e87134fe94831df76698fe0e90fe556da0011511) | fix | build optimizer support for spec-compliant downlevel class properties | +| [d80adde2f](https://github.com/angular/angular-cli/commit/d80adde2fec53e6513983a89dd194a35c426b8aa) | fix | do not fail compilation when spec pattern does not match | +| [11be502e7](https://github.com/angular/angular-cli/commit/11be502e7cc2544371d55c8b3d32b7bcbbf8066e) | fix | fix support of Safari TP versions | +| [14e317d85](https://github.com/angular/angular-cli/commit/14e317d85429c83e6285c5cec4a1c4483d8a1c8f) | fix | load polyfills and runtime as scripts instead of modules | + +## Special Thanks + +Alan Agius, Charles Lyding, Kristiyan Kostadinov and Ricardo + + + + + +# 15.1.4 (2023-02-01) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [6c8fdfc69](https://github.com/angular/angular-cli/commit/6c8fdfc6985c5b5017a0b6ab6fa38daf4cb9a775) | fix | load JavaScript bundles as modules in karma | +| [317452e3b](https://github.com/angular/angular-cli/commit/317452e3b7e25080132b7f7a069696d1c5054f69) | fix | print server builder errors and warnings | + +## Special Thanks + +Alan Agius + + + + + +# 15.1.3 (2023-01-25) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [de15ec576](https://github.com/angular/angular-cli/commit/de15ec5763afe231439c3f1ace35cbacefad2ca7) | fix | handle extended schematics when retrieving aliases | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [2c04f4a8f](https://github.com/angular/angular-cli/commit/2c04f4a8f493781fda65f31e81ad86cdd3e510c0) | fix | update browserslist config to include last 2 Chrome version | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [f31bf300b](https://github.com/angular/angular-cli/commit/f31bf300b9f226d9574060b0e4401c4da88c0ee3) | fix | avoid undefined module path for Sass imports in esbuild | +| [c152a4a13](https://github.com/angular/angular-cli/commit/c152a4a13f482948c6aedbbc99d1423f2cf43aea) | fix | update browserslist config to include last 2 Chrome versions | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [9de99202e](https://github.com/angular/angular-cli/commit/9de99202e9427973c7983940fcdea9e4580a79bd) | fix | handle number like strings in workspace writer | + +## Special Thanks + +Alan Agius, Charles Lyding and Doug Parker + + + + + +# 15.1.2 (2023-01-18) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- | +| [387472a95](https://github.com/angular/angular-cli/commit/387472a956b71eaca89e210e64f4d75969abc9d3) | fix | register schematic aliases when providing collection name in `ng generate` | +| [5d9fd788a](https://github.com/angular/angular-cli/commit/5d9fd788a997066dea1b2d69dced865a7c60f5c1) | fix | remove `--to` option from being required when using `--from` in `ng update` | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------- | +| [0f5fb7e59](https://github.com/angular/angular-cli/commit/0f5fb7e5944e3a521758c67f403d71928f93f7ac) | fix | replace existing `BrowserModule.withServerTransition` calls when running universal schematic | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [bf4639a6e](https://github.com/angular/angular-cli/commit/bf4639a6e97670972c3d5b137230e2f08467010e) | fix | prevent hanging initial build during exception with esbuild | + +## Special Thanks + +Alan Agius, Charles Lyding and Doug Parker + + + + + +# 15.1.1 (2023-01-12) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- | +| [b94bf60ca](https://github.com/angular/angular-cli/commit/b94bf60ca828a22d548d65b819ea745eafb96deb) | fix | update `esbuild` to `0.16.17` | + +## Special Thanks + +Alan Agius + + + + + +# 15.1.0 (2023-01-11) + +## Deprecations + +### @angular-devkit/schematics + +- The Observable based `SchematicTestRunner.runSchematicAsync` and `SchematicTestRunner.runExternalSchematicAsync` method have been deprecated in favor of the Promise based `SchematicTestRunner.runSchematic` and `SchematicTestRunner.runExternalSchematic`. + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------- | +| [5b18ce154](https://github.com/angular/angular-cli/commit/5b18ce1545d047d49851a64e81a1f8ef59624ef7) | feat | add `guardType` as an alias of `implements` in guard schematic | +| [dd2b65943](https://github.com/angular/angular-cli/commit/dd2b65943d706833f449f76cf8c7278d0a5399ad) | feat | add configuration files generation schematic | +| [8d000d156](https://github.com/angular/angular-cli/commit/8d000d1563684f9a9b6869e549e265f0997187c4) | feat | add environments generation schematic | +| [6c39a162b](https://github.com/angular/angular-cli/commit/6c39a162bec67083bf6c11b54e84612f1d68c384) | feat | Add schematics for generating functional router guards and resolvers | +| [62121f89a](https://github.com/angular/angular-cli/commit/62121f89abce54e0a1c2b816cdd32b57f2b5a5d1) | feat | add sideEffects:false to library package.json | +| [9299dea64](https://github.com/angular/angular-cli/commit/9299dea6492527bcaea24c9c7f3116ee2779405b) | feat | generate functional interceptors | +| [49b313f27](https://github.com/angular/angular-cli/commit/49b313f27adef6300063c9d6817d1454a8657fe2) | fix | add missing import for functional interceptor spec | +| [2f92fe7e5](https://github.com/angular/angular-cli/commit/2f92fe7e589705b282102271897454ea852c4814) | fix | add missing semicolon in functional guard/resolver/interceptor | +| [9b6d190f4](https://github.com/angular/angular-cli/commit/9b6d190f4a082c166d253b0f00162e0286238e45) | fix | remove EnvironmentInjector import in functional guard spec | +| [b11d3f644](https://github.com/angular/angular-cli/commit/b11d3f6442d38f609471ab19c08a1c9a871e0ae3) | fix | use proper variable in functional guard spec | +| [451975f76](https://github.com/angular/angular-cli/commit/451975f7650041a83994e1308f85fe7e33a31e32) | fix | use proper variable in resolver functional spec | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [c29df6954](https://github.com/angular/angular-cli/commit/c29df695467c41feccd3846a55c91c6784af87b2) | feat | add `assets` option to server builder | +| [839d0cb57](https://github.com/angular/angular-cli/commit/839d0cb57ad42896578c235354ffb918ea8bb146) | feat | implement stats-json option for esbuild builder | +| [216991b9d](https://github.com/angular/angular-cli/commit/216991b9d9ca1d8f09992880a5fa92e7c98813fa) | feat | support inline component Sass styles with esbuild builder | +| [7c87ce47c](https://github.com/angular/angular-cli/commit/7c87ce47c66a6426b6b7fbb2edd38d8da729221f) | fix | ensure Sass load paths are resolved from workspace root | +| [7a063238b](https://github.com/angular/angular-cli/commit/7a063238b83eea8b5b3237fed12db5528d1f6912) | fix | explicitly send options to JS transformer workers | +| [22cba7937](https://github.com/angular/angular-cli/commit/22cba79370ed60a27f932acda363ffd87f5d9983) | fix | provide an option to `exclude` specs in Karma builder | +| [20376649c](https://github.com/angular/angular-cli/commit/20376649c5e3003b0aa99b9328e2b61699ccba78) | fix | transform async generator class methods for Zone.js support | +| [0520608f6](https://github.com/angular/angular-cli/commit/0520608f68f1768a13a46fbdb9ecb65310492460) | fix | use relative css resource paths in esbuild JSON stats | +| [0c01532cb](https://github.com/angular/angular-cli/commit/0c01532cb5a3072b96cd65845a38b88ed4543de6) | perf | use worker pool for JavaScript transforms in esbuild builder | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [207358afb](https://github.com/angular/angular-cli/commit/207358afb89e6515cb8d73f5a3a63d9101e80d97) | feat | add `runSchematic` and `runExternalSchematic` methods | + +## Special Thanks + +Alan Agius, Andrew Scott, Charles Lyding, Cédric Exbrayat, Doug Parker, Felix Hamann, Jason Bedard, Joey Perrott and Kristiyan Kostadinov + + + + + +# 15.0.5 (2023-01-06) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [c2030dec7](https://github.com/angular/angular-cli/commit/c2030dec7d9fecf42cca2de37cc3f7adaaa45e7f) | fix | format esbuild error messages to include more information | + +## Special Thanks + +Alan Agius, Kristiyan Kostadinov, Paul Gschwendtner and aanchal + + + + + +# 15.0.4 (2022-12-14) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [ccc8e0350](https://github.com/angular/angular-cli/commit/ccc8e0350810d123269f55de29acd7964e663f7e) | fix | display actionable error when a style does not exist in Karma builder | +| [507f756c3](https://github.com/angular/angular-cli/commit/507f756c34171db842365398150460e1e29f531a) | fix | downlevel class private methods when targeting Safari <=v15 | +| [a0da91dba](https://github.com/angular/angular-cli/commit/a0da91dba3d9b4c4a86102668f52ab933406e5da) | fix | include sources in generated | +| [9fd356234](https://github.com/angular/angular-cli/commit/9fd356234210734ec5f44ae18f055308b7acc963) | fix | only set ngDevMode when script optimizations are enabled | +| [8e85f4728](https://github.com/angular/angular-cli/commit/8e85f47284472f9df49f2ca6c59057ad28240e9c) | fix | update `css-loader` to `6.7.3` | +| [b2d4415ca](https://github.com/angular/angular-cli/commit/b2d4415caa486bebe55e6147a153f120cf08b070) | fix | update locale setting snippet to use `globalThis`. | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 15.0.3 (2022-12-07) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [3d9971edb](https://github.com/angular/angular-cli/commit/3d9971edb05e9b8de24bafc1b4381cbf4bad8dbf) | fix | default preserve symlinks to Node.js value for esbuild | +| [24f4b51d2](https://github.com/angular/angular-cli/commit/24f4b51d22a0debc8ff853cf9040a15273654f7a) | fix | downlevel class fields with Safari <= v15 for esbuild | +| [45afc42db](https://github.com/angular/angular-cli/commit/45afc42db86e58357d1618d9984dcf03bffea957) | fix | downlevel class properties when targeting Safari <=v15 | +| [e6461badf](https://github.com/angular/angular-cli/commit/e6461badf7959ff8b8d9a3824a4a081f44e0b237) | fix | prevent optimization adding unsupported ECMASCript features | + +## Special Thanks + +Charles Lyding, Dominic Elm and Paul Gschwendtner + + + + + +# 15.0.2 (2022-11-30) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [2891d5bc9](https://github.com/angular/angular-cli/commit/2891d5bc9eecf7fa8e3b80906d9c56e6a49f3d15) | fix | correctly set Sass quietDeps and verbose options | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [d9cc4b028](https://github.com/angular/angular-cli/commit/d9cc4b0289eaf382782a994a15497e9526c5a4a2) | fix | elide unused type references | + +## Special Thanks + +Alan Agius and Juuso Valkeejärvi + + + + + +# 15.0.1 (2022-11-23) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [eda96def4](https://github.com/angular/angular-cli/commit/eda96def48e11533cd0a3353c96b7eac9a881e1e) | fix | use global version of the CLI when running `ng new` | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [48426852b](https://github.com/angular/angular-cli/commit/48426852b0c1d5541a3e7369dc2b343e33856968) | fix | show warning when a TS Config is not found during migrations | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [2af32fd3a](https://github.com/angular/angular-cli/commit/2af32fd3a981b1c29e1cf77b442982e1e07aae38) | fix | hide loader paths in webpack warnings | +| [19f5cc746](https://github.com/angular/angular-cli/commit/19f5cc746ec724f15d1b89126c7c1b8a343818fe) | fix | improve package deep import Sass index resolution in esbuild plugin | +| [2220a907d](https://github.com/angular/angular-cli/commit/2220a907daf9ccd9e22dfc8e5ddc259b9d495997) | fix | use url function lexer to rebase Sass URLs | + +## Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Joey Perrott and Piotr Wysocki + + + + + +# 15.0.0 (2022-11-16) + +## Breaking Changes + +### @angular/cli + +- The Angular CLI no longer supports `16.10.x`, `16.11.x` and `16.12.x`. Current minimum versions of Node.js are `14.20.0`, `16.13.0` and `18.10.0`. +- Node.js versions older than 14.20 are no longer supported. +- The 'path' option in schematics schema no longer has a special meaning. Use 'workingDirectory' smart default provider should be used instead. + +### @schematics/angular + +- Removed unused `appDir` option from Universal and App-Shell schematic. This option can safely be removed if present since it no longer has effect. + +### + +- `analyticsSharing` option in the global angular configuration has been + removed without replacement. This option was used to configure the Angular CLI to access to your own users' CLI usage data. + + If this option is used, it can be removed using `ng config --global cli.analyticsSharing undefined`. + +- analytics APIs have been removed without replacement from `@angular-devkit/core` and `@angular-devkit/architect`. + +### @angular-devkit/build-angular + +- TypeScript versions older than 4.8.2 are no longer supported. +- The server builder `bundleDependencies` option has been removed. This option was used pre Ivy. Currently, using this option is unlikely to produce working server bundles. + + The `externalDependencies` option can be used instead to exclude specific node_module packages from the final bundle. + +- - Deprecated support for tilde import has been removed. Please update the imports by removing the `~`. + + Before + + ```scss + @import '~font-awesome/scss/font-awesome'; + ``` + + After + + ```scss + @import 'font-awesome/scss/font-awesome'; + ``` + + - By default the CLI will use Sass modern API, While not recommended, users can still opt to use legacy API by setting `NG_BUILD_LEGACY_SASS=1`. + +- Internally the Angular CLI now always set the TypeScript `target` to `ES2022` and `useDefineForClassFields` to `false` unless the target is set to `ES2022` or later in the TypeScript configuration. To control ECMA version and features use the Browerslist configuration. +- `require.context` are no longer parsed. Webpack specific features are not supported nor guaranteed to work in the future. +- Producing ES5 output is no longer possible. This was needed for Internet Explorer which is no longer supported. All browsers that Angular supports work with ES2015+ +- server builder `bundleDependencies` option now only accept a boolean value. +- Deprecated support for Stylus has been removed. The Stylus package has never reached a stable version and its usage in the Angular CLI is minimal. It's recommended to migrate to another CSS preprocessor that the Angular CLI supports. + +### @angular-devkit/core + +- Workspace projects with missing `root` is now an error. + +### @ngtools/webpack + +- TypeScript versions older than 4.8.2 are no longer supported. + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ | +| [766d4a089](https://github.com/angular/angular-cli/commit/766d4a0895e7895211e93bc73ff131c6e47613a7) | feat | add migration to remove require calls from karma builder main file | +| [d8bff4f1e](https://github.com/angular/angular-cli/commit/d8bff4f1e68a76da1983f9d0774f415e73dfd8c3) | feat | Added --project-root option to the library schematics | +| [597bfea1b](https://github.com/angular/angular-cli/commit/597bfea1b29cc7b25d1f466eb313cbeeb6dffc98) | feat | drop `polyfills.ts` file from new templates | +| [1c21e470c](https://github.com/angular/angular-cli/commit/1c21e470c76d69d08e5096b46b952dbce330f7ef) | feat | enable error on unknown properties and elements in tests | +| [f2a0682dc](https://github.com/angular/angular-cli/commit/f2a0682dc82afa23a3d3481df59e4aaca5e90c78) | feat | generate new projects using TypeScript 4.8.2 | +| [b06421d15](https://github.com/angular/angular-cli/commit/b06421d15e4b5e6daffcb73ee1c2c8703b72cb47) | feat | mark `projectRoot` as non hidden option in application schematic | +| [b6897dbb0](https://github.com/angular/angular-cli/commit/b6897dbb0a1ef287644e117251c1c76cc8afcae0) | feat | remove `karma.conf.js` from newly generated projects | +| [301b5669a](https://github.com/angular/angular-cli/commit/301b5669a724261d53444d5172334966903078c0) | feat | remove `ngOnInit` from component template | +| [9beb878e2](https://github.com/angular/angular-cli/commit/9beb878e2eecd32e499c8af557f22f46548248fc) | feat | remove Browserslist configuration files from projects | +| [283b564d1](https://github.com/angular/angular-cli/commit/283b564d1de985f0af8c2fcb6192801a90baacda) | feat | remove environment files in new applications | +| [56a1e8f9f](https://github.com/angular/angular-cli/commit/56a1e8f9f52658488afb9d36007e96c96d08a03b) | feat | remove test.ts file from new projects | +| [4e69e8050](https://github.com/angular/angular-cli/commit/4e69e80501dd2a9394b7df4518e0d6b0f2ebb7d9) | fix | add `@angular/localize` as type when localize package is installed | +| [57d93fb7d](https://github.com/angular/angular-cli/commit/57d93fb7d979e68c2a4e6f6046ff633f69098afe) | fix | mark project as required option | +| [84e3f7727](https://github.com/angular/angular-cli/commit/84e3f7727dc1de31484704c7c06d51ff5392a34a) | fix | remove empty lines | +| [316a50d75](https://github.com/angular/angular-cli/commit/316a50d75e45962ea3efe4108aa48d9479245dd5) | fix | remove TypeScript target from universal schematic | +| [69b221498](https://github.com/angular/angular-cli/commit/69b2214987c8fad6efd091782cf28b20be62d244) | refactor | remove deprecated appDir option | + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | +| [4827d1b23](https://github.com/angular/angular-cli/commit/4827d1b23e564e4e4a8684c5e8ff035d8fa855a2) | feat | add support for Node.js version 18 | +| [4b623461a](https://github.com/angular/angular-cli/commit/4b623461a4a938ba320b5e019f9c715d634a46c4) | feat | drop support for Node.js versions older than 14.20 | +| [3dea1fa71](https://github.com/angular/angular-cli/commit/3dea1fa7173e846aff5b0d15b919d9786bbf7198) | fix | add unique user id as user parameter in GA | +| [af07aa340](https://github.com/angular/angular-cli/commit/af07aa340a1c3c9f3d42446981be59a73effa498) | fix | add workspace information as part of analytics collection | +| [83524f625](https://github.com/angular/angular-cli/commit/83524f62533f9a6bda0c1dbc76c6b16e730a7397) | fix | allow `ng add` to find prerelease versions when CLI is prerelease | +| [22955f245](https://github.com/angular/angular-cli/commit/22955f24592df8044dbdeeb8e635beb1cc770c75) | fix | do not collect analytics when running in non TTY mode | +| [35e5f4278](https://github.com/angular/angular-cli/commit/35e5f4278145b7ef55a75f1692c8e92d6bcd59db) | fix | exclude `@angular/localize@<10.0.0` from ng add pa… ([#24152](https://github.com/angular/angular-cli/pull/24152)) | +| [1a584364e](https://github.com/angular/angular-cli/commit/1a584364e70cafd84770ef45f3da9ad58a46083f) | fix | exclude `@angular/material@7.x` from ng add package discovery | +| [ff0382718](https://github.com/angular/angular-cli/commit/ff0382718af60923fe71f8b224d36a50449484e6) | fix | respect registry in RC when running update through yarn | +| [774d349b7](https://github.com/angular/angular-cli/commit/774d349b73a436a99f2ea932b7509dab7c1d5e45) | refactor | remove deprecated path handler | + +### + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- | +| [639a3071c](https://github.com/angular/angular-cli/commit/639a3071c3630c1ccdf7e3c015e81e9423ab2678) | refactor | migrate analytics collector to use GA4 | +| [c969152de](https://github.com/angular/angular-cli/commit/c969152de630a9afdef44ba2342e728b9353c8e7) | refactor | remove analytics API from core and architect | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------- | +| [4ead45cab](https://github.com/angular/angular-cli/commit/4ead45caba08cb0b67dc7df2f6a9b304c75fff7d) | feat | add `ng-server-context` when using app-shell builder | +| [1c527a9da](https://github.com/angular/angular-cli/commit/1c527a9da5b55a8421ebca787fd322e879f6d29d) | feat | add esbuild-based builder initial support for fileReplacements | +| [67324b3e5](https://github.com/angular/angular-cli/commit/67324b3e5861510b1df9641bb4b10bb67e3a2325) | feat | add initial incremental code rebuilding to esbuild builder | +| [3d94ca21b](https://github.com/angular/angular-cli/commit/3d94ca21bbb7496a2ff588166fd93c5f2339b823) | feat | add initial watch support to esbuild-based builder | +| [c592ec584](https://github.com/angular/angular-cli/commit/c592ec584f1c0b126a2045e5ea1b01cb1569ce4d) | feat | amend `polyfills` option in all builders to support an array of module specifiers | +| [a95d130ef](https://github.com/angular/angular-cli/commit/a95d130ef4249457ed2433d52eb43c94a1169782) | feat | auto include `@angular/localize/init` when found in `types` | +| [979bce45e](https://github.com/angular/angular-cli/commit/979bce45e63eda9ac5402869ef3dc4c63aaca3f1) | feat | auto include `@angular/platform-server/init` during server builds | +| [fd4175357](https://github.com/angular/angular-cli/commit/fd41753579affa78328bfc4b6108db15ff5053f9) | feat | drop support for TypeScript 4.6 and 4.7 | +| [15d3fc6dc](https://github.com/angular/angular-cli/commit/15d3fc6dc3f74462818b3745f6fb4995212a4d22) | feat | export `@angular/platform-server` symbols in server bundle | +| [05a98c029](https://github.com/angular/angular-cli/commit/05a98c02924f656be3257d5f459ae88c1ae29fba) | feat | karma builder `main` option is now optional | +| [2b6029245](https://github.com/angular/angular-cli/commit/2b602924538bf987e92f806c25c2a3d008a3f0a9) | feat | providing a karma config is now optional | +| [9c13fce16](https://github.com/angular/angular-cli/commit/9c13fce162eff8d01d1fa6a7f0e0029da2887c86) | feat | remove `bundleDependencies` from server builder | +| [308e3a017](https://github.com/angular/angular-cli/commit/308e3a017f876bfc727e68803bfbce11e9d3396e) | feat | switch to use Sass modern API | +| [1e5d4a750](https://github.com/angular/angular-cli/commit/1e5d4a75084dfd2aeebb6a0c0b3039417e14bc84) | feat | use Browserslist to determine ECMA output | +| [3ff391738](https://github.com/angular/angular-cli/commit/3ff39173808f2beed97ee5deb91be541205f9a03) | fix | account for package.json exports fields with CSS import statements | +| [001445982](https://github.com/angular/angular-cli/commit/0014459820dc1c127e93993414c154947a7f8da6) | fix | account for package.json exports with Sass in esbuild builder | +| [6280741ce](https://github.com/angular/angular-cli/commit/6280741ce4a89882595c834f48a45cca6f9534e0) | fix | add `@angular/platform-server` as an optional peer dependency | +| [f9a2c3a12](https://github.com/angular/angular-cli/commit/f9a2c3a1216cf9510e122df44a64ddd11d47226b) | fix | allow both script and module sourceTypes to be localized | +| [4cb27b803](https://github.com/angular/angular-cli/commit/4cb27b8031d0f36e687c5116538ebe473acaa149) | fix | avoid attempted resolve of external CSS URLs with esbuild builder | +| [192e0e6d7](https://github.com/angular/angular-cli/commit/192e0e6d77d4f0f20af3f88b653c5196a2c1e052) | fix | correct escaping of target warning text in esbuild builder | +| [4fcb0a82b](https://github.com/angular/angular-cli/commit/4fcb0a82b5fa8a092d8c374cdea448edd80270d4) | fix | correctly resolve Sass partial files in node packages | +| [fb5a66ae6](https://github.com/angular/angular-cli/commit/fb5a66ae66b595602d2a8aea8e938efe5df6d13c) | fix | fix crash when Sass error occurs | +| [b6df9c136](https://github.com/angular/angular-cli/commit/b6df9c1367ae5795a3895628ec9822d432b315bb) | fix | handle conditional exports in `scripts` and `styles` option | +| [0ee7625d6](https://github.com/angular/angular-cli/commit/0ee7625d6b4bd84be6fca0df82f3e74e4b94728c) | fix | ignore cache path when watching with esbuild builder | +| [e34bfe5eb](https://github.com/angular/angular-cli/commit/e34bfe5eb1a559cbf53449ce213503e32fa27ae4) | fix | ignore specs in node_modules when finding specs | +| [f143171fd](https://github.com/angular/angular-cli/commit/f143171fd030fa1cc8df84ed5f0b96f5ad0f9e10) | fix | only add `@angular/platform-server/init` when package is installed. | +| [3a1970b76](https://github.com/angular/angular-cli/commit/3a1970b76e4da7424e2661664a1e9e669bd279b4) | fix | only import karma when running karma builder | +| [8b84c18ed](https://github.com/angular/angular-cli/commit/8b84c18edd01e91c7ebf4327dde8ce60f7f700ca) | fix | provide workaround for V8 object spread performance defect | +| [7dd122ad5](https://github.com/angular/angular-cli/commit/7dd122ad5f34a488f3784326b579b8a93511af7e) | fix | rebase Sass url() values when using esbuild-based builder | +| [2105964af](https://github.com/angular/angular-cli/commit/2105964afc0285cc40c16d32c47d1eb60be5e279) | fix | resolve transitive dependencies in Sass when using Yarn PNP | +| [54e1c01d8](https://github.com/angular/angular-cli/commit/54e1c01d8b608ff240f7559ca176cd50e991952c) | fix | show file replacement in TS missing file error in esbuild builder | +| [6c3f281d9](https://github.com/angular/angular-cli/commit/6c3f281d927c9ae2d4ec76ff9f920752e2cb73d1) | fix | show warning when using TypeScript target older then ES2022 in esbuild builder | +| [8f8e02c32](https://github.com/angular/angular-cli/commit/8f8e02c3221c9477ec931bb6983daf6a2c8dc8be) | fix | support Yarn PNP resolution in modern SASS API | +| [fc82e3bec](https://github.com/angular/angular-cli/commit/fc82e3bec3f188d449e952d9955b845b2efdcd6b) | fix | update browerslist package | +| [0d62157a3](https://github.com/angular/angular-cli/commit/0d62157a30a246c1e00273c2300b9251574e75ae) | fix | update sourcemaps when rebasing Sass url() functions in esbuild builder | +| [1518133db](https://github.com/angular/angular-cli/commit/1518133db3b1c710500786f9f1fcfa05a016862e) | fix | use relative sourcemap source paths for Sass in esbuild builder | +| [fb4ead2ce](https://github.com/angular/angular-cli/commit/fb4ead2ce0de824eef46ce8e27a8f6cc1d08c744) | fix | wait during file watching to improve multi-save rebuilds for esbuild builder | +| [b059fc735](https://github.com/angular/angular-cli/commit/b059fc73597c12330a96fca5f6ab9b1ca226136c) | fix | warn when components styles sourcemaps are not generated when styles optimization is enabled | +| [9d0872fb5](https://github.com/angular/angular-cli/commit/9d0872fb5e369f714633387d9ae39c4242ba1ea1) | perf | add initial global styles incremental rebuilds with esbuild builder | +| [0fe6b3b75](https://github.com/angular/angular-cli/commit/0fe6b3b75b87f6f8050b196615e1c1543b707841) | perf | add vendor chunking to server builder | +| [8c915d414](https://github.com/angular/angular-cli/commit/8c915d41496c99fb42ae3992d9c91de542260bf2) | perf | avoid extra babel file reads in esbuild builder rebuilds | +| [919fe2148](https://github.com/angular/angular-cli/commit/919fe2148885c44655ce36085768b1eab2c8c246) | perf | avoid extra TypeScript emits with esbuild rebuilds | +| [92145c4a7](https://github.com/angular/angular-cli/commit/92145c4a7d2c835b703319676bafd8ea3b4a19f0) | perf | avoid template diagnostics for declaration files in esbuild builder | +| [52db3c000](https://github.com/angular/angular-cli/commit/52db3c00076dfe118cd39d7724229210c30665e0) | perf | minimize Angular diagnostics incremental analysis in esbuild-based builder | +| [feb06753d](https://github.com/angular/angular-cli/commit/feb06753d59f782c6ad8fd59a60537863094f498) | perf | use esbuild-based builder to directly downlevel for await...of | +| [9d83fb91b](https://github.com/angular/angular-cli/commit/9d83fb91b654eed79a5c9c9691d0f1c094f37771) | perf | use Sass worker pool for Sass support in esbuild builder | +| [45a94228f](https://github.com/angular/angular-cli/commit/45a94228fb23acbd0d1a9329448f07b759c8654b) | perf | use Uint8Arrays for incremental caching with esbuild-based builder | +| [f393b0928](https://github.com/angular/angular-cli/commit/f393b09282582da47db683344e037fd1434b32a8) | refactor | disable `requireContext` parsing | +| [12931ba8c](https://github.com/angular/angular-cli/commit/12931ba8c3772b1dd65846cbd6146804b08eab31) | refactor | remove deprecated ES5 support | +| [7f1017e60](https://github.com/angular/angular-cli/commit/7f1017e60f82389568065478d666ae4be6ebfea2) | refactor | remove old `bundleDependencies` enum logic | +| [2ba44a433](https://github.com/angular/angular-cli/commit/2ba44a433c827413a53d12de0ef203f8988ddc2a) | refactor | remove support for Stylus | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [ea4c0aa2e](https://github.com/angular/angular-cli/commit/ea4c0aa2e84d48be37b75e37c99ad381122297c3) | fix | throw error when project has missing root property | +| [de467f46d](https://github.com/angular/angular-cli/commit/de467f46de63059f9c701dfe8695513c742f22b5) | fix | update logger `forEach` `promiseCtor` type | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ | +| [9b07b469b](https://github.com/angular/angular-cli/commit/9b07b469b622e083a9915ed3c24e1d53d8abf38f) | refactor | remove `UpdateBuffer` and rename `UpdateBuffer2` to `UpdateBuffer` | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | +| [43bd0abc1](https://github.com/angular/angular-cli/commit/43bd0abc147cf3177e707624bf6163b3dc9e06f8) | feat | drop support for TypeScript 4.6 and 4.7 | +| [1c1f985b9](https://github.com/angular/angular-cli/commit/1c1f985b9c9913f28915f101ee1717c0da540362) | fix | support inline style sourcemaps when using css-loader for component styles | + +## Special Thanks + +Alan Agius, Brent Schmidt, Charles Lyding, Cédric Exbrayat, Dariusz Ostolski, Doug Parker, Günhan Gülsoy, Jason Bedard, Lukas Spirig, Ruslan Lekhman, angular-robot[bot] and minijus + + + + + +# 14.2.10 (2022-11-17) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------- | +| [9ce386caf](https://github.com/angular/angular-cli/commit/9ce386caf6037f21f422a785fec977634406d208) | fix | exclude `@angular/localize@<10.0.0` from ng add pa… ([#24152](https://github.com/angular/angular-cli/pull/24152)) | +| [6446091a3](https://github.com/angular/angular-cli/commit/6446091a310f327ceeb68ae85f3673f6e3e83286) | fix | exclude `@angular/material@7.x` from ng add package discovery | +| [7541e04f3](https://github.com/angular/angular-cli/commit/7541e04f36ff32118e93588be38dcbb5cc2c92a9) | fix | respect registry in RC when running update through yarn | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------- | +| [21cea0b42](https://github.com/angular/angular-cli/commit/21cea0b42f08bf56990bdade82e2daa7c33011ed) | fix | update `loader-utils` to `3.2.1` | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 13.3.10 (2022-11-17) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------- | +| [f298ebbd5](https://github.com/angular/angular-cli/commit/f298ebbd5f86077985d994662314379df92b6771) | fix | update `loader-utils` to `3.2.1` | + +## Special Thanks + +Alan Agius + + + + + +# 14.2.9 (2022-11-09) + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [e3e787767](https://github.com/angular/angular-cli/commit/e3e78776782da9d933f7b0e4c6bf391a62585bee) | fix | default to failure if no builder result is provided | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [12b2dc5a2](https://github.com/angular/angular-cli/commit/12b2dc5a2374f992df151af32cc80e2c2d7c4dee) | fix | isolate zone.js usage when rendering server bundles | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 14.2.8 (2022-11-02) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [4b0ee8ad1](https://github.com/angular/angular-cli/commit/4b0ee8ad15efcb513ab5d9e38bf9b1e08857e798) | fix | guard schematics should include all guards (CanMatch) | + +## Special Thanks + +Andrew Scott + + + + + +# 14.2.7 (2022-10-26) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [91b5bcbb3](https://github.com/angular/angular-cli/commit/91b5bcbb31715a3c2e183e264ebd5ec1188d5437) | fix | disable version check during auto completion | +| [02a3d7b71](https://github.com/angular/angular-cli/commit/02a3d7b715f4069650389ba26a3601747e67d9c2) | fix | skip node.js compatibility checks when running completion | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------- | +| [bebed9df8](https://github.com/angular/angular-cli/commit/bebed9df834d01f72753aa0e60dc104f1781bd67) | fix | issue dev-server support warning when using esbuild builder | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 14.2.6 (2022-10-12) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------ | +| [1c9cf594f](https://github.com/angular/angular-cli/commit/1c9cf594f7a855ea4b462fad53acd3bf3a2e7622) | fix | handle missing `which` binary in path | +| [28b2cd18e](https://github.com/angular/angular-cli/commit/28b2cd18e3c490cf2db64d4a6744bbd26c0aeabb) | fix | skip downloading temp CLI when running `ng update` without package names | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [ad6928184](https://github.com/angular/angular-cli/commit/ad692818413a97afe54aee6a39f0447ee9239343) | fix | project extension warning message should identify concerned project | + +## Special Thanks + +AgentEnder and Alan Agius + + + + + +# 14.2.5 (2022-10-05) + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [17eb20c77](https://github.com/angular/angular-cli/commit/17eb20c77098841d45f0444f5f047c4d44fc614f) | fix | throw more relevant error when Rule returns invalid null value | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 14.2.4 (2022-09-28) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [05b18f4e4](https://github.com/angular/angular-cli/commit/05b18f4e4b39d73c8a3532507c4b7bba8722bf80) | fix | add builders and schematic names as page titles in collected analytics | + +## Special Thanks + +Alan Agius, Jason Bedard and Paul Gschwendtner + + + + + +# 14.2.3 (2022-09-15) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [e7e0cb78f](https://github.com/angular/angular-cli/commit/e7e0cb78f4c6d684fdf25e23a11599b82807cd25) | fix | correctly display error messages that contain "at" text. | +| [4756d7e06](https://github.com/angular/angular-cli/commit/4756d7e0675aa9a8bed11b830b66288141fa6e16) | fix | watch symbolic links | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [1e3ecbdb1](https://github.com/angular/angular-cli/commit/1e3ecbdb138861eff550e05d9662a10d106c0990) | perf | avoid bootstrap conversion AST traversal where possible | + +## Special Thanks + +Alan Agius, Charles Lyding, Jason Bedard and Joey Perrott + + + + + +# 14.2.2 (2022-09-08) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [5405a9b3b](https://github.com/angular/angular-cli/commit/5405a9b3b56675dc671e1ef27410e632f3f6f536) | fix | favor non deprecated packages during update | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [6bfd6a7fb](https://github.com/angular/angular-cli/commit/6bfd6a7fbcaf433bd2c380087803044df4c6d8ee) | fix | update minimum Angular version to 14.2 | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [2b00bca61](https://github.com/angular/angular-cli/commit/2b00bca615a2c79b0a0311c83cb9f1450b6f1745) | fix | allow esbuild-based builder to use SVG Angular templates | +| [45c95e1bf](https://github.com/angular/angular-cli/commit/45c95e1bf1327532ceeb1277fa6f4ce7c3a45581) | fix | change service worker errors to compilation errors | +| [ecc014d66](https://github.com/angular/angular-cli/commit/ecc014d669efe9609177354c465f24a1c94279cd) | fix | handle service-worker serving with localize in dev-server | +| [39ea128c1](https://github.com/angular/angular-cli/commit/39ea128c1294046525a8c098ed6a776407990365) | fix | handling of `@media` queries inside css layers | +| [17b7e1bdf](https://github.com/angular/angular-cli/commit/17b7e1bdfce5823718d1fa915d25858f4b0d7110) | fix | issue warning when using deprecated tilde imports | +| [3afd784f1](https://github.com/angular/angular-cli/commit/3afd784f1f00ee07f68ba112bea7786ccb2d4f35) | fix | watch index file when running build in watch mode | + +## Special Thanks + +Alan Agius, Charles Lyding, Jason Bedard and Joey Perrott + + + + + +# 14.2.1 (2022-08-26) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [e4ca46866](https://github.com/angular/angular-cli/commit/e4ca4686627bd31604cf68bc1d2473337e26864c) | fix | update ng-packagr version to `^14.2.0` | + +## Special Thanks + +Alan Agius + + + + + +# 14.2.0 (2022-08-25) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [596037010](https://github.com/angular/angular-cli/commit/596037010a8113809657cebc9385d040922e6d86) | fix | add missing space after period in warning text | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [44c25511e](https://github.com/angular/angular-cli/commit/44c25511ea2adbd4fbe82a6122fc00af612be8e8) | feat | add ability to serve service worker when using dev-server | +| [3fb569b5c](https://github.com/angular/angular-cli/commit/3fb569b5c82f22afca4dc59313356f198755827e) | feat | switch to Sass modern API in esbuild builder | +| [5bd03353a](https://github.com/angular/angular-cli/commit/5bd03353ac6bb19c983efb7ff015e7aec3ff61d1) | fix | correct esbuild builder global stylesheet sourcemap URL | +| [c4402b1bd](https://github.com/angular/angular-cli/commit/c4402b1bd32cdb0cdd7aeab14239b57ee700d361) | fix | correctly handle parenthesis in url | +| [50c783307](https://github.com/angular/angular-cli/commit/50c783307eb1253f4f2a87502bd7a19f6a409aeb) | fix | use valid CSS comment for sourcemaps with Sass in esbuild builder | +| [4c251853f](https://github.com/angular/angular-cli/commit/4c251853fbc66c6c9aae171dc75612db31afe2fb) | perf | avoid extra string creation with no sourcemaps for esbuild sass | +| [d97640534](https://github.com/angular/angular-cli/commit/d9764053478620a5f4a3349c377c74415435bcbb) | perf | with esbuild builder only load Sass compiler when needed | + +## Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Jason Bedard, Joey Perrott, Kristiyan Kostadinov and angular-robot[bot] + + + + + +# 14.1.3 (2022-08-17) + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [365035cb3](https://github.com/angular/angular-cli/commit/365035cb37c57e07cb96e45a38f266b16b4e2fbf) | fix | update workspace extension warning to use correct phrasing | + +## Special Thanks + +AgentEnder, Alan Agius, Charles Lyding and Jason Bedard + + + + + +# 14.1.2 (2022-08-10) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [3e19c842c](https://github.com/angular/angular-cli/commit/3e19c842cc2a7f2dc62904f5f88025a4687d378a) | fix | avoid collect stats from chunks with no files | +| [d0a0c597c](https://github.com/angular/angular-cli/commit/d0a0c597cd09b1ce4d7134d3e330982b522f28a9) | fix | correctly handle data URIs with escaped quotes in stylesheets | +| [67b3a086f](https://github.com/angular/angular-cli/commit/67b3a086fe90d1b7e5443e8a9f29b12367dd07e7) | fix | process stylesheet resources from url tokens with esbuild browser builder | +| [e6c45c316](https://github.com/angular/angular-cli/commit/e6c45c316ebcd1b5a16b410a3743088e9e9f789c) | perf | reduce babel transformation in esbuild builder | +| [38b71bcc0](https://github.com/angular/angular-cli/commit/38b71bcc0ddca1a34a5a4480ecd0b170bd1e9620) | perf | use esbuild in esbuild builder to downlevel native async/await | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [dd47a5e8c](https://github.com/angular/angular-cli/commit/dd47a5e8c543cbd3bb37afe5040a72531b028347) | fix | elide type only named imports when using `emitDecoratorMetadata` | + +## Special Thanks + +Alan Agius, Charles Lyding and Jason Bedard + + + + + +# 14.1.1 (2022-08-03) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [4ee825bac](https://github.com/angular/angular-cli/commit/4ee825baca21c21db844bdf718b6ec29dc6c3d42) | fix | catch clause variable is not an Error instance | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------- | +| [83dcfb32f](https://github.com/angular/angular-cli/commit/83dcfb32f8ef3334f83bb36a2c3097fe9f8a4e4b) | fix | prevent numbers from class names | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- | +| [ef6da4aad](https://github.com/angular/angular-cli/commit/ef6da4aad76ff534d4edb9e73c2d56c53b649b15) | fix | allow the esbuild-based builder to fully resolve global stylesheet packages | +| [eed54b359](https://github.com/angular/angular-cli/commit/eed54b359d2b514156242529ee8a25b51c50dae0) | fix | catch clause variable is not an Error instance | +| [c98471094](https://github.com/angular/angular-cli/commit/c9847109438d33d38a31ded20a1cab2721fc1fbd) | fix | correctly respond to preflight requests | +| [94b444e4c](https://github.com/angular/angular-cli/commit/94b444e4caff4c3092e0291d9109e2abed966656) | fix | correctly set `ngDevMode` in esbuilder | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------- | +| [44c18082a](https://github.com/angular/angular-cli/commit/44c18082a5963b7f9d0f1577a0975b2f35abe6a2) | fix | `classify` string util should concat string without using a `.` | + +### @angular/create + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [cb0d3fb33](https://github.com/angular/angular-cli/commit/cb0d3fb33f196393761924731c3c3786a3a3493b) | fix | use appropriate package manager to install dependencies | + +## Special Thanks + +Alan Agius, Charles Lyding, Jason Bedard and Paul Gschwendtner + + + + + +# 14.1.0 (2022-07-20) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [3884b8652](https://github.com/angular/angular-cli/commit/3884b865262c1ffa5652ac0f4d67bbf59087f453) | fix | add esbuild browser builder to workspace schema | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [707911d42](https://github.com/angular/angular-cli/commit/707911d423873623d4201d2fbce4a294ab73a135) | feat | support controlling `addDependency` utility rule install behavior | +| [a8fe4fcc3](https://github.com/angular/angular-cli/commit/a8fe4fcc315fd408b5b530a44a02c1655b5450a8) | fix | Allow skipping existing dependencies in E2E schematic | +| [b8bf3b480](https://github.com/angular/angular-cli/commit/b8bf3b480bef752641370e542ebb5aee649a8ac6) | fix | only issue a warning for addDependency existing specifier | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [a7709b718](https://github.com/angular/angular-cli/commit/a7709b718c953d83f3bde00fa3bf896501359946) | feat | add `externalDependencies` to the esbuild browser builder | +| [248860ad6](https://github.com/angular/angular-cli/commit/248860ad674b54f750bb5c197588bb6d031be208) | feat | add Sass file support to experimental esbuild-based builder | +| [b06ae5514](https://github.com/angular/angular-cli/commit/b06ae55140c01f8b5107527fd0af1da3b04a721f) | feat | add service worker support to experimental esbuild builder | +| [b5f6d862b](https://github.com/angular/angular-cli/commit/b5f6d862b95afd0ec42d9b3968e963f59b1b1658) | feat | Identify third-party sources in sourcemaps | +| [b3a14d056](https://github.com/angular/angular-cli/commit/b3a14d05629ba6e3b23c09b1bfdbc4b35d534813) | fix | allow third-party sourcemaps to be ignored in esbuild builder | +| [53dd929e5](https://github.com/angular/angular-cli/commit/53dd929e59f98a7088d150e861d18e97e6de4114) | fix | ensure esbuild builder sourcemap sources are relative to workspace | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [526cdb263](https://github.com/angular/angular-cli/commit/526cdb263a8c74ad228f584f70dc029aa69351d7) | feat | allow `chain` rule to accept iterables of rules | + +### @angular/create + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [cfe93fbc8](https://github.com/angular/angular-cli/commit/cfe93fbc89fad2f58826f0118ce7ff421cd0e4f2) | feat | add support for `yarn create` and `npm init` | + +## Special Thanks + +Alan Agius, Charles Lyding, Derek Cormier, Doug Parker, Jason Bedard, Joey Perrott, Paul Gschwendtner, Victor Porof and renovate[bot] + + + + + +# 14.0.7 (2022-07-20) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------- | +| [f653bf4fb](https://github.com/angular/angular-cli/commit/f653bf4fbb69b9e0fa0e6440a88a30f17566d9a3) | fix | incorrect logo for Angular Material | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [5810c2cc2](https://github.com/angular/angular-cli/commit/5810c2cc2dd21e5922a5eaa330e854e4327a0500) | fix | fallback to use projectRoot when sourceRoot is missing during coverage | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------- | +| [2ba4678b6](https://github.com/angular/angular-cli/commit/2ba4678b6ba2164e80cb661758565c133e08afaa) | fix | add i18n as valid project extension | +| [c2201c835](https://github.com/angular/angular-cli/commit/c2201c835801ef9c1cc6cacec2748c8ca341519d) | fix | log name of invalid extension too | + +## Special Thanks + +Alan Agius, Fortunato Ventre, Katerina Skroumpelou and Kristiyan Kostadinov + + + + + +# 13.3.9 (2022-07-20) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------- | +| [0d62716ae](https://github.com/angular/angular-cli/commit/0d62716ae3753bb463de6b176ae07520ebb24fc9) | fix | update terser to address CVE-2022-25858 | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 14.0.6 (2022-07-13) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------- | +| [178550529](https://github.com/angular/angular-cli/commit/1785505290940dad2ef9a62d4725e0d1b4b486d4) | fix | handle cases when completion is enabled and running in an older CLI workspace | +| [10f24498e](https://github.com/angular/angular-cli/commit/10f24498ec2938487ae80d6ecea584e20b01dcbe) | fix | remove deprecation warning of `no` prefixed schema options | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- | +| [dfa6d73c5](https://github.com/angular/angular-cli/commit/dfa6d73c5c45d3c3276fb1fecfb6535362d180c5) | fix | remove browserslist configuration | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------------------- | +| [4d848c4e6](https://github.com/angular/angular-cli/commit/4d848c4e6f6944f32b9ecb2cf2db5c544b3894fe) | fix | generate different content hashes for scripts which are changed during the optimization phase | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [2500f34a4](https://github.com/angular/angular-cli/commit/2500f34a401c2ffb03b1dfa41299d91ddebe787e) | fix | provide actionable warning when a workspace project has missing `root` property | + +## Special Thanks + +Alan Agius and martinfrancois + + + + + +# 14.0.5 (2022-07-06) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------ | +| [98a6aad60](https://github.com/angular/angular-cli/commit/98a6aad60276960bd6bcecda73172480e4bdec48) | fix | during an update only use package manager force option with npm 7+ | +| [094aa16aa](https://github.com/angular/angular-cli/commit/094aa16aaf5b148f2ca94cae45e18dbdeaacad9d) | fix | improve error message for project-specific ng commands when run outside of a project | +| [e5e07fff1](https://github.com/angular/angular-cli/commit/e5e07fff1919c46c15d6ce61355e0c63007b7d55) | fix | show deprecated workspace config options in IDE | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [f9f970cab](https://github.com/angular/angular-cli/commit/f9f970cab515a8a1b1fbb56830b03250dd5cccce) | fix | prevent importing `RouterModule` parallel to `RoutingModule` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [aa8ed532f](https://github.com/angular/angular-cli/commit/aa8ed532f816f2fa23b1fe443a216c5d75507432) | fix | disable glob mounting for patterns that start with a forward slash | +| [c76edb8a7](https://github.com/angular/angular-cli/commit/c76edb8a79d1a12376c2a163287251c06e1f0222) | fix | don't override base-href in HTML when it's not set in builder | +| [f64903528](https://github.com/angular/angular-cli/commit/f649035286d640660c3bc808b7297fb60d0888bc) | fix | improve detection of CommonJS dependencies | +| [74dbd5fc2](https://github.com/angular/angular-cli/commit/74dbd5fc273aece097b2b3ee0b28607d24479d8c) | fix | support hidden component stylesheet sourcemaps with esbuild builder | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [7aed97561](https://github.com/angular/angular-cli/commit/7aed97561c2320f92f8af584cc9852d4c8d818b9) | fix | do not run ngcc when `node_modules` does not exist | + +## Special Thanks + +Alan Agius, Charles Lyding, JoostK and Paul Gschwendtner + + + + + +# 14.0.4 (2022-06-29) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [fc72c625b](https://github.com/angular/angular-cli/commit/fc72c625bb7db7b9c8d865086bcff05e2db426ee) | fix | correctly handle `--collection` option in `ng new` | +| [f5badf221](https://github.com/angular/angular-cli/commit/f5badf221d2a2f5357f93bf0e32146669f8bbede) | fix | improve global schema validation | +| [ed302ea4c](https://github.com/angular/angular-cli/commit/ed302ea4c80b4f6fe8a73c5a0d25055a7dca1db2) | fix | remove color from help epilogue | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [c58c66c0d](https://github.com/angular/angular-cli/commit/c58c66c0d5c76630453151b65b1a1c3707c82e9f) | fix | use `sourceRoot` instead of `src` in universal schematic | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [88acec1fd](https://github.com/angular/angular-cli/commit/88acec1fd302d7d8a053e37ed0334ec6a30c952c) | fix | complete builders on the next event loop iteration | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [694b73dfa](https://github.com/angular/angular-cli/commit/694b73dfa12e5aefff8fc5fdecf220833ac40b42) | fix | exit dev-server when CTRL+C is pressed | +| [6d4782199](https://github.com/angular/angular-cli/commit/6d4782199c4a4e92a9c0b189d6a7857ca631dd3f) | fix | exit localized builds when CTRL+C is pressed | +| [282baffed](https://github.com/angular/angular-cli/commit/282baffed507926e806db673b6804b9299c383af) | fix | hide stacktraces from webpack errors | +| [c4b0abf5b](https://github.com/angular/angular-cli/commit/c4b0abf5b8c1e392ead84c8810e8d6e615fd0024) | fix | set base-href in service worker manifest when using i18n and app-shell | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [33f1cc192](https://github.com/angular/angular-cli/commit/33f1cc192d963b4a4348bb41b8fb0969ffd5c342) | fix | restore process title after NGCC is executed | +| [6796998bf](https://github.com/angular/angular-cli/commit/6796998bf4dd829f9ac085a52ce7e9d2cda73fd1) | fix | show a compilation error on invalid TypeScript version | + +## Special Thanks + +Alan Agius, Charles Lyding and Tim Bowersox + + + + + +# 14.0.3 (2022-06-23) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------- | +| [b3db91baf](https://github.com/angular/angular-cli/commit/b3db91baf50c92589549a66ffef437f7890d3de7) | fix | disable version check when running `ng completion` commands | +| [cdab9fa74](https://github.com/angular/angular-cli/commit/cdab9fa7431db7e2a75e04e776555b8e5e15fc94) | fix | provide an actionable error when using `--configuration` with `ng run` | +| [5521648e3](https://github.com/angular/angular-cli/commit/5521648e33af634285f6352b43a324a1ee023e27) | fix | temporarily handle boolean options in schema prefixed with `no` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [5e960ce24](https://github.com/angular/angular-cli/commit/5e960ce246e7090f57ce22723911a743aa8fcb0c) | fix | fix incorrect glob cwd in karma when using `--include` option | +| [1b5e92075](https://github.com/angular/angular-cli/commit/1b5e92075e64563459942d4de785f1a8bef46ec7) | fix | handle `codeCoverageExclude` correctly in Windows | +| [ff6d81a45](https://github.com/angular/angular-cli/commit/ff6d81a4539657446c8f5770cefe688d2d578450) | fix | ignore supported browsers during i18n extraction | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [170c16f2e](https://github.com/angular/angular-cli/commit/170c16f2ea769e76a48f1ac215ee88ba47ff511d) | fix | workspace writer skip creating empty projects property | + +## Special Thanks + +Alan Agius, Charles Lyding and Paul Gschwendtner + + + + + +# 14.0.2 (2022-06-15) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [23095e9c3](https://github.com/angular/angular-cli/commit/23095e9c3fc514c7e9a892833d8a18270da5bd95) | fix | show more actionable error when command is ran in wrong scope | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [5a486cb64](https://github.com/angular/angular-cli/commit/5a486cb64253ba2829160a6f1fa3bf0e381d45ea) | fix | remove vscode testing configurations for `minimal` workspaces | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------- | +| [9d88c96d8](https://github.com/angular/angular-cli/commit/9d88c96d898c5c46575a910a7230d239f4fe7a77) | fix | replace fallback locale for `en-US` | + +## Special Thanks + +Alan Agius and Julien Marcou + + + + + +# 13.3.8 (2022-06-15) + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- | +| [c7f994f88](https://github.com/angular/angular-cli/commit/c7f994f88a396be96c01da1017a15083d5f544fb) | fix | add peer dependency on Angular CLI | + +## Special Thanks + +Alan Agius + + + + + +# 14.0.1 (2022-06-08) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------- | +| [e4fb96657](https://github.com/angular/angular-cli/commit/e4fb96657f044d97562008b5b3c6f3a55ac8ba3a) | fix | add text to help output to indicate that additional commands are available when ran in different context | +| [7952e5790](https://github.com/angular/angular-cli/commit/7952e579066f7191f4b82a10816c6a41a4ea5644) | fix | avoid creating unnecessary global configuration | +| [66a1d6b9d](https://github.com/angular/angular-cli/commit/66a1d6b9d2e1fba3d5ee88a6c5d81206f530ce3a) | fix | correct scope cache command | +| [e2d964289](https://github.com/angular/angular-cli/commit/e2d964289fe2a418e5f4e421249e2f8da64185cc) | fix | correctly print package manager name when an install is needed | +| [75fd3330d](https://github.com/angular/angular-cli/commit/75fd3330d4c27263522ea931eb1545ce0a34ab6a) | fix | during an update only use package manager force option with npm 7+ | +| [e223890c1](https://github.com/angular/angular-cli/commit/e223890c1235b4564ec15eb99d71256791a21c3c) | fix | ensure full process exit with older local CLI versions | +| [0cca3638a](https://github.com/angular/angular-cli/commit/0cca3638adb46cd5d0c18b823c83d4b604d7c798) | fix | handle project being passed as a flag | +| [b1451cb5e](https://github.com/angular/angular-cli/commit/b1451cb5e90f43df365202a6fdfcfbc9e0853ca4) | fix | improve resilience of logging during process exit | +| [17fec1357](https://github.com/angular/angular-cli/commit/17fec13577ac333fc66c3752c75be58146c9ebac) | fix | provide actionable error when project cannot be determined | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [73dcf39c6](https://github.com/angular/angular-cli/commit/73dcf39c6e7678a3915a113fd72829549ccc3b8e) | fix | remove strict setting under application project | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [c788d5b56](https://github.com/angular/angular-cli/commit/c788d5b56a1a191e7ca53c3b63245e3979a1cf44) | fix | log modified and removed files when using the `verbose` option | +| [6e8fe0ed5](https://github.com/angular/angular-cli/commit/6e8fe0ed54d88132da0238fdb3a6e97330c85ff7) | fix | replace dev-server socket path from `/ws` to `/ng-cli-ws` | +| [651adadf4](https://github.com/angular/angular-cli/commit/651adadf4df8b66c60771f27737cb2a67957b46a) | fix | update Angular peer dependencies to 14.0 stable | + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- | +| [cfd264d06](https://github.com/angular/angular-cli/commit/cfd264d061109c7989933e51a14b6bf83b289b07) | fix | add peer dependency on Angular CLI | + +## Special Thanks + +Alan Agius, Charles Lyding and Doug Parker + + + + + +# 14.0.0 (2022-06-02) + +## Breaking Changes + +### @angular/cli + +- Several changes to the `ng analytics` command syntax. + - `ng analytics project ` has been replaced with `ng analytics ` + - `ng analytics ` has been replaced with `ng analytics --global` + +- Support for Node.js v12 has been removed as it will become EOL on 2022-04-30. Please use Node.js v14.15 or later. +- Support for TypeScript 4.4 and 4.5 has been removed. Please update to TypeScript 4.6. +- `--all` option from `ng update` has been removed without replacement. To update packages which don’t provide `ng update` capabilities in your workspace `package.json` use `npm update`, `yarn upgrade-interactive` or `yarn upgrade` instead. +- Deprecated option `--prod` has been removed from all builders. `--configuration production`/`-c production` should be used instead if the default configuration of the builder is not configured to `production`. +- `--configuration` cannot be used with `ng run`. Provide the configuration as part of the target. Ex: `ng run project:builder:configuration`. +- Deprecated `ng x18n` and `ng i18n-extract` commands have been removed in favor of `ng extract-i18n`. +- Several changes in the Angular CLI commands and arguments handling. + - `ng help` has been removed in favour of the `—-help` option. + - `ng —-version` has been removed in favour of `ng version` and `ng v`. + - Deprecated camel cased arguments are no longer supported. Ex. using `—-sourceMap` instead of `—-source-map` will result in an error. + - `ng update`, `—-migrate-only` option no longer accepts a string of migration name, instead use `—-migrate-only -—name `. + - `—-help json` help has been removed. + +### @angular-devkit/architect-cli + +- camel case arguments are no longer allowed. + +### @angular-devkit/schematics-cli + +- camel case arguments are no longer allowed. + +### @angular-devkit/build-angular + +- `browser` and `karma` builders `script` and `styles` options input files extensions are now validated. + + Valid extensions for `scripts` are: + - `.js` + - `.cjs` + - `.mjs` + - `.jsx` + - `.cjsx` + - `.mjsx` + + Valid extensions for `styles` are: + - `.css` + - `.less` + - `.sass` + - `.scss` + - `.styl` + +- We now issue a build time error since importing a CSS file as an ECMA module is non standard Webpack specific feature, which is not supported by the Angular CLI. + + This feature was never truly supported by the Angular CLI, but has as such for visibility. + +- Reflect metadata polyfill is no longer automatically provided in JIT mode + Reflect metadata support is not required by Angular in JIT applications compiled by the CLI. + Applications built in AOT mode did not and will continue to not provide the polyfill. + For the majority of applications, the reflect metadata polyfill removal should have no effect. + However, if an application uses JIT mode and also uses the previously polyfilled reflect metadata JavaScript APIs, the polyfill will need to be manually added to the application after updating. + To replicate the previous behavior, the `core-js` package should be manually installed and the `import 'core-js/proposals/reflect-metadata';` statement should be added to the application's `polyfills.ts` file. +- `NG_BUILD_CACHE` environment variable has been removed. `cli.cache` in the workspace configuration should be used instead. +- The deprecated `showCircularDependencies` browser and server builder option has been removed. The recommended method to detect circular dependencies in project code is to use either a lint rule or other external tools. + +### @angular-devkit/core + +- `parseJson` and `ParseJsonOptions` APIs have been removed in favor of 3rd party JSON parsers such as `jsonc-parser`. +- The below APIs have been removed without replacement. Users should leverage other Node.js or other APIs. + - `fs` namespace + - `clean` + - `mapObject` + +### @angular-devkit/schematics + +- Schematics `NodePackageInstallTask` will not execute package scripts by default + The `NodePackageInstallTask` will now use the package manager's `--ignore-scripts` option by default. + The `--ignore-scripts` option will prevent package scripts from executing automatically during an install. + If a schematic installs packages that need their `install`/`postinstall` scripts to be executed, the + `NodePackageInstallTask` now contains an `allowScripts` boolean option which can be enabled to provide the + previous behavior for that individual task. As with previous behavior, the `allowScripts` option will + prevent the individual task's usage of the `--ignore-scripts` option but will not override the package + manager's existing configuration. +- Deprecated `analytics` property has been removed from `TypedSchematicContext` interface + +### @ngtools/webpack + +- `ivy` namespace has been removed from the public API. + - `ivy.AngularWebpackPlugin` -> `AngularWebpackPlugin` + - `ivy.AngularPluginOptions` -> `AngularPluginOptions` + +## Deprecations + +### @angular/cli + +- The `defaultCollection` workspace option has been deprecated in favor of `schematicCollections`. + + Before + + ```json + "defaultCollection": "@angular/material" + ``` + + After + + ```json + "schematicCollections": ["@angular/material"] + ``` + +- The `defaultProject` workspace option has been deprecated. The project to use will be determined from the current working directory. + +### @angular-devkit/core + +- - `ContentHasMutatedException`, `InvalidUpdateRecordException`, `UnimplementedException` and `MergeConflictException` symbol from `@angular-devkit/core` have been deprecated in favor of the symbol from `@angular-devkit/schematics`. + - `UnsupportedPlatformException` - A custom error exception should be created instead. + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------- | +| [afafa5788](https://github.com/angular/angular-cli/commit/afafa5788f11b8727c39bb0a390300a706aba5bc) | feat | add `--global` option to `ng analytics` command | +| [bb550436a](https://github.com/angular/angular-cli/commit/bb550436a476d74705742a8c36f38971b346b903) | feat | add `ng analytics info` command | +| [e5bf35ea3](https://github.com/angular/angular-cli/commit/e5bf35ea3061a3e532aa85df44551107e62e24c5) | feat | add `ng cache` command | +| [7ab22ed40](https://github.com/angular/angular-cli/commit/7ab22ed40d521e3cec29ab2d66d0289c3cdb4106) | feat | add disable/enable aliases for off/on `ng analytics` command | +| [4212fb8de](https://github.com/angular/angular-cli/commit/4212fb8de2f4f3e80831a0803acc5fc6e54db1e1) | feat | add prompt to set up CLI autocompletion | +| [0316dea67](https://github.com/angular/angular-cli/commit/0316dea676be522b04d654054880cc5794e3c8b3) | feat | add prompts on missing builder targets | +| [607a723f7](https://github.com/angular/angular-cli/commit/607a723f7d623ec8a15054722b2afd13042f66a1) | feat | add support for auto completion | +| [366cabc66](https://github.com/angular/angular-cli/commit/366cabc66c3dd836e2fdfea8dad6c4c7c2096b1d) | feat | add support for multiple schematics collections | +| [036327e9c](https://github.com/angular/angular-cli/commit/036327e9ca838f9ef3f117fbd18949d9d357e68d) | feat | deprecated `defaultProject` option | +| [fb0622893](https://github.com/angular/angular-cli/commit/fb06228932299870774a7b254f022573f5d8175f) | feat | don't prompt to set up autocompletion for `ng update` and `ng completion` commands | +| [4ebfe0341](https://github.com/angular/angular-cli/commit/4ebfe03415ebe4e8f1625286d1be8bd1b54d3862) | feat | drop support for Node.js 12 | +| [022d8c7bb](https://github.com/angular/angular-cli/commit/022d8c7bb142e8b83f9805a39bc1ae312da465eb) | feat | make `ng completion` set up CLI autocompletion by modifying `.bashrc` files | +| [2e15df941](https://github.com/angular/angular-cli/commit/2e15df9417dcc47b12785a8c4c9074bf05d0450c) | feat | remember after prompting users to set up autocompletion and don't prompt again | +| [7fa3e6587](https://github.com/angular/angular-cli/commit/7fa3e6587955d0638929758d3c257392c242c796) | feat | support TypeScript 4.6.2 | +| [9e69331fa](https://github.com/angular/angular-cli/commit/9e69331fa61265c77d6281232bb64a2c63509290) | feat | use PNPM as package manager when `pnpm-lock.yaml` exists | +| [6f6b453fb](https://github.com/angular/angular-cli/commit/6f6b453fbf90adad16eba7ea8929a11235c1061b) | fix | `ng doc` doesn't open browser in Windows | +| [8e66c9188](https://github.com/angular/angular-cli/commit/8e66c9188be827380e5acda93c7e21fae718b9ce) | fix | `ng g` show description from `collection.json` if not present in `schema.json` | +| [9edeb8614](https://github.com/angular/angular-cli/commit/9edeb86146131878c5e8b21b6adaa24a26f12453) | fix | add long description to `ng update` | +| [160cb0718](https://github.com/angular/angular-cli/commit/160cb071870602d9e7fece2ce381facb71e7d762) | fix | correctly handle `--search` option in `ng doc` | +| [d46cf6744](https://github.com/angular/angular-cli/commit/d46cf6744eadb70008df1ef25e24fb1db58bb997) | fix | display option descriptions during auto completion | +| [09f8659ce](https://github.com/angular/angular-cli/commit/09f8659cedcba70903140d0c3eb5d0e10ebb506c) | fix | display package manager during `ng update` | +| [a49cdfbfe](https://github.com/angular/angular-cli/commit/a49cdfbfefbdd756882be96fb61dc8a0d374b6e0) | fix | don't prompt for analytics when running `ng analytics` | +| [4b22593c4](https://github.com/angular/angular-cli/commit/4b22593c4a269ea4bd63cef39009aad69f159fa1) | fix | ensure all available package migrations are executed | +| [054ae02c2](https://github.com/angular/angular-cli/commit/054ae02c2fb8eed52af76cf39a432a3770d301e4) | fix | favor project in cwd when running architect commands | +| [ff4eba3d4](https://github.com/angular/angular-cli/commit/ff4eba3d4a9417d2baef70aaa953bdef4bb426a6) | fix | handle duplicate arguments | +| [5a8bdeb43](https://github.com/angular/angular-cli/commit/5a8bdeb434c7561334bfc8865ed279110a44bd93) | fix | hide private schematics from `ng g` help output | +| [644f86d55](https://github.com/angular/angular-cli/commit/644f86d55b75a289e641ba280e8456be82383b06) | fix | improve error message for Windows autocompletion use cases | +| [3012036e8](https://github.com/angular/angular-cli/commit/3012036e81fc6e5fc6c0f1df7ec626f91285673e) | fix | populate path with working directory in nested schematics | +| [8a396de6a](https://github.com/angular/angular-cli/commit/8a396de6a8a58347d2201a43d7f5101f94f20e89) | fix | print entire config when no positional args are provided to `ng config` | +| [bdf2b9bfa](https://github.com/angular/angular-cli/commit/bdf2b9bfa9893a940ba254073d024172e0dc1abc) | fix | print schematic errors correctly | +| [efc3c3225](https://github.com/angular/angular-cli/commit/efc3c32257a65caf36999dc34cadc41eedcbf323) | fix | remove analytics prompt postinstall script | +| [bf15b202b](https://github.com/angular/angular-cli/commit/bf15b202bb1cd073fe01cf387dce2c033b5bb14c) | fix | remove cache path from global valid paths | +| [142da460b](https://github.com/angular/angular-cli/commit/142da460b22e07a5a37b6140b50663446c3a2dbf) | fix | remove incorrect warning during `ng update` | +| [96a0d92da](https://github.com/angular/angular-cli/commit/96a0d92da2903edfb3835ce86b3700629d6e43ad) | fix | remove JSON serialized description from help output | +| [78460e995](https://github.com/angular/angular-cli/commit/78460e995a192336db3c4be9d0592b4e7a2ff2c8) | fix | remove type casting and add optional chaining for current in optionTransforms | +| [e5bdadac4](https://github.com/angular/angular-cli/commit/e5bdadac44ac023363bc0a2473892fc17430b81f) | fix | skip prompt or warn when setting up autocompletion without a global CLI install | +| [ca401255f](https://github.com/angular/angular-cli/commit/ca401255f49568cfe5f9ec6a35ea5b91c91afa70) | fix | sort commands in help output | +| [b97772dfc](https://github.com/angular/angular-cli/commit/b97772dfc03401fe1faa79e77742905341bd5d46) | fix | support silent package installs with Yarn 2+ | +| [87cd5cd43](https://github.com/angular/angular-cli/commit/87cd5cd4311e71a15ea1ecb82dde7480036cb815) | fix | workaround npm 7+ peer dependency resolve errors during updates | +| [d94a67353](https://github.com/angular/angular-cli/commit/d94a67353dcdaa30cf5487744a7ef151a6268f2d) | refactor | remove deprecated `--all` option from `ng update` | +| [2fc7c73d7](https://github.com/angular/angular-cli/commit/2fc7c73d7e40dbb0a593df61eeba17c8a8f618a9) | refactor | remove deprecated `--prod` flag | +| [b69ca3a7d](https://github.com/angular/angular-cli/commit/b69ca3a7d22b54fc06fbc1cfb559b2fd915f5609) | refactor | remove deprecated command aliases for `extract-i18n`. | +| [2e0493130](https://github.com/angular/angular-cli/commit/2e0493130acfe7244f7ee3ef28c961b1b04d7722) | refactor | replace command line arguments parser | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [7b78b7840](https://github.com/angular/angular-cli/commit/7b78b7840e95b0f4dca2fcb9218b67dd7500ff2c) | feat | add --standalone to ng generate | +| [e49220fba](https://github.com/angular/angular-cli/commit/e49220fba0d158be0971989e26eb199ec02fa113) | feat | add migratiom to remove `defaultProject` in workspace config | +| [3fa38b08b](https://github.com/angular/angular-cli/commit/3fa38b08ba8ef57a6079873223a7d6088d5ea64e) | feat | introduce `addDependency` rule to utilities | +| [b07ccfbb1](https://github.com/angular/angular-cli/commit/b07ccfbb1b2045d285c23dd4b654e1380892fcb2) | feat | introduce a utility subpath export for Angular rules and utilities | +| [7e7de6858](https://github.com/angular/angular-cli/commit/7e7de6858dd71bd461ceb0f89e29e2c57099bbcc) | feat | update Angular dependencies to use `^` as version prefix | +| [69ecddaa7](https://github.com/angular/angular-cli/commit/69ecddaa7d8b01aa7a9e61c403a4b9a8669e34c4) | feat | update new and existing projects compilation target to `ES2020` | +| [7e8e42063](https://github.com/angular/angular-cli/commit/7e8e42063f354c402d758f10c8ba9bee7e0c8aff) | fix | add migration to remove `package.json` in libraries secondary entrypoints | +| [b928d973e](https://github.com/angular/angular-cli/commit/b928d973e97f33220afe16549b41c4031feb5c5e) | fix | alphabetically order imports during component generation | +| [09a71bab6](https://github.com/angular/angular-cli/commit/09a71bab6044e517319f061dbd4555ce57fe6485) | fix | Consolidated setup with a single `beforeEach()` | +| [1921b07ee](https://github.com/angular/angular-cli/commit/1921b07eeb710875825dc6f7a4452bd5462e6ba7) | fix | don't add path mapping to old entrypoint definition file | +| [c927c038b](https://github.com/angular/angular-cli/commit/c927c038ba356732327a026fe9a4c36ed23c9dec) | fix | remove `@types/node` from new projects | +| [27cb29438](https://github.com/angular/angular-cli/commit/27cb29438aa01b185b2dca3617100d87f45f14e8) | fix | remove extra space in standalone imports | + +### @angular-devkit/architect-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | -------------------------------- | +| [c7556b62b](https://github.com/angular/angular-cli/commit/c7556b62b7b0eab5717ed6eeab3fa7f0f1f2a873) | refactor | replace parser with yargs-parser | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | -------------------------------- | +| [5330d52ae](https://github.com/angular/angular-cli/commit/5330d52aee32daca27fa1a2fa15712f4a408602a) | refactor | replace parser with yargs-parser | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------ | +| [00186fb93](https://github.com/angular/angular-cli/commit/00186fb93f66d8da51886de37cfa4599f3e89af9) | feat | add initial experimental esbuild-based application browser builder | +| [d23a168b8](https://github.com/angular/angular-cli/commit/d23a168b8d558ae9d73c8c9eed4ff199fc4d74b9) | feat | validate file extensions for `scripts` and `styles` options | +| [2adf252dc](https://github.com/angular/angular-cli/commit/2adf252dc8a7eb0ce504de771facca56730e5272) | fix | add es2015 exports package condition to browser-esbuild | +| [72e820e7b](https://github.com/angular/angular-cli/commit/72e820e7b2bc6904b030f1092bbb610334a4036f) | fix | better handle Windows paths in esbuild experimental builder | +| [587082fb0](https://github.com/angular/angular-cli/commit/587082fb0fa7bdb6cddb36327f791889d76e3e7b) | fix | close compiler on Karma exit | +| [c52d10d1f](https://github.com/angular/angular-cli/commit/c52d10d1fc4b70483a2043edfa73dc0f323f6bf1) | fix | close dev-server on error | +| [48630ccfd](https://github.com/angular/angular-cli/commit/48630ccfd7a672fc5174ef484b3bd5c549d32fef) | fix | detect `tailwind.config.cjs` as valid tailwindcss configuration | +| [4d5f6c659](https://github.com/angular/angular-cli/commit/4d5f6c65918c1a8a4bde0a0af01089242d1cdc4a) | fix | downlevel libraries based on the browserslist configurations | +| [1a160dac0](https://github.com/angular/angular-cli/commit/1a160dac00f34aab089053281c640dba3efd597f) | fix | ensure karma sourcemap support on Windows | +| [07e776ea3](https://github.com/angular/angular-cli/commit/07e776ea379a50a98a50cf590156c2dc1b272e78) | fix | fail build when importing CSS files as an ECMA modules | +| [ac1383f9e](https://github.com/angular/angular-cli/commit/ac1383f9e5d491181812c090bd4323f46110f3d8) | fix | properly handle locally-built APF v14 libraries | +| [966d25b55](https://github.com/angular/angular-cli/commit/966d25b55eeb6cb84eaca183b30e7d3b0d0a2188) | fix | remove unneeded JIT reflect metadata polyfill | +| [b8564a638](https://github.com/angular/angular-cli/commit/b8564a638df3b6971ef2ac8fb838e6a7c910ac3b) | refactor | remove deprecated `NG_BUILD_CACHE` environment variable | +| [0a1cd584d](https://github.com/angular/angular-cli/commit/0a1cd584d8ed00889b177f4284baec7e5427caf2) | refactor | remove deprecated `showCircularDependencies` browser and server builder option | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------- | +| [c5b3e9299](https://github.com/angular/angular-cli/commit/c5b3e9299130132aecfa19219405e1964d0c5443) | refactor | deprecate unused exception classes | +| [67144b9e5](https://github.com/angular/angular-cli/commit/67144b9e54b5a9bfbc963e386b01275be5eaccf5) | refactor | remove deprecated `parseJson` and `ParseJsonOptions` APIs | +| [a0c02af7e](https://github.com/angular/angular-cli/commit/a0c02af7e340bb16f4e6f523c2d835c9b18926b3) | refactor | remove deprecated fs, object and array APIs | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------- | +| [c9c781c7d](https://github.com/angular/angular-cli/commit/c9c781c7d5f3c6de780912fd7c624a457e6da14c) | feat | add parameter to `listSchematicNames` to allow returning hidden schematics. | +| [0e6425fd8](https://github.com/angular/angular-cli/commit/0e6425fd88ea32679516251efdca6ff07cc4b56a) | feat | disable package script execution by default in `NodePackageInstallTask` | +| [25498ad5b](https://github.com/angular/angular-cli/commit/25498ad5b2ba6fa5a88c9802ddeb0ed85c5d9b60) | feat | re-export core string helpers from schematics package | +| [464cf330a](https://github.com/angular/angular-cli/commit/464cf330a14397470e1e57450a77f421a45a927e) | feat | support null for options parameter from OptionTransform type | +| [33f9f3de8](https://github.com/angular/angular-cli/commit/33f9f3de869bba2ecd855a01cc9a0a36651bd281) | feat | support reading JSON content directly from a Tree | +| [01297f450](https://github.com/angular/angular-cli/commit/01297f450387dea02eafd6f5701c417ab5c5d844) | feat | support reading text content directly from a Tree | +| [48f9b79bc](https://github.com/angular/angular-cli/commit/48f9b79bc4d43d0180bab5af5726621a68204a15) | fix | support ignore scripts package installs with Yarn 2+ | +| [3471cd6d8](https://github.com/angular/angular-cli/commit/3471cd6d8696ae9c28dba901d3e0f6868d69efc8) | fix | support quiet package installs with Yarn 2+ | +| [44c1e6d0d](https://github.com/angular/angular-cli/commit/44c1e6d0d2db5f2dc212d63a34ade045cb7854d5) | refactor | remove deprecated `analytics` property | + +### @angular/pwa + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [243cb4062](https://github.com/angular/angular-cli/commit/243cb40622fef4107b0162bc7b6a374471cebc14) | fix | remove `@schematics/angular` utility deep import usage | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------ | +| [0c344259d](https://github.com/angular/angular-cli/commit/0c344259dcdc10a35840151bfe3ae1b27f9b53ff) | fix | update peer dependency to reflect TS 4.6 support | +| [044101554](https://github.com/angular/angular-cli/commit/044101554dfbca07d74f2a4391f94875df7928d2) | perf | use Webpack's built-in xxhash64 support | +| [9277eed1d](https://github.com/angular/angular-cli/commit/9277eed1d9603d5e258eb7ae27de527eba919482) | refactor | remove deprecated ivy namespace | + +## Special Thanks + +Adrien Crivelli, Alan Agius, Charles Lyding, Cédric Exbrayat, Daniil Dubrava, Doug Parker, Elton Coelho, George Kalpakas, Jason Bedard, Joey Perrott, Kristiyan Kostadinov, Paul Gschwendtner, Pawel Kozlowski, Tobias Speicher and alkavats1 + + + + + +# 13.3.7 (2022-05-25) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [a54018d8f](https://github.com/angular/angular-cli/commit/a54018d8f5f976034bf0a33f826245b7a6b74bbe) | fix | add debugging and timing information in JavaScript and CSS optimization plugins | + +## Special Thanks + +Alan Agius and Joey Perrott + + + + + +# 13.3.6 (2022-05-18) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------- | +| [e20964c43](https://github.com/angular/angular-cli/commit/e20964c43c52125b6d2bfa9bbea444fb2eea1e15) | fix | resolve relative schematic from `angular.json` instead of current working directory | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------ | +| [16fec8d58](https://github.com/angular/angular-cli/commit/16fec8d58b6ec421df5e7809c45838baf232b4a9) | fix | update `babel-loader` to 8.2.5 | + +## Special Thanks + +Alan Agius, Charles Lyding, Jason Bedard and Paul Gschwendtner + + + + + +# 13.3.5 (2022-05-04) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------- | +| [6da0910d3](https://github.com/angular/angular-cli/commit/6da0910d345eb84084e32a462432a508d518f402) | fix | update `@ampproject/remapping` to `2.2.0` | + +## Special Thanks + +Alan Agius, Charles Lyding and Paul Gschwendtner + + + + + +# 13.3.4 (2022-04-27) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------- | +| [f4da75656](https://github.com/angular/angular-cli/commit/f4da756560358273098df2a5cae7848201206c77) | fix | change wrapping of schematic code | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [5d0141bfb](https://github.com/angular/angular-cli/commit/5d0141bfb4ae80b1a7543eab64e9c381c932eaef) | fix | correctly resolve custom service worker configuration file | + +## Special Thanks + +Charles Lyding and Wagner Maciel + + + + + +# 13.3.3 (2022-04-13) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [d38b247cf](https://github.com/angular/angular-cli/commit/d38b247cf19edf5ecf7792343fa2bc8c05a3a8b8) | fix | display debug logs when using the `--verbose` option | + +### @angular-devkit/build-webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------- | +| [5682baee4](https://github.com/angular/angular-cli/commit/5682baee4b562b314dad781403dcc0c46e0a8abb) | fix | emit devserver setup errors | + +## Special Thanks + +Alan Agius + + + + + +# 13.3.2 (2022-04-06) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [49dc63d09](https://github.com/angular/angular-cli/commit/49dc63d09a7a7f2b7759b47e79fac934b867e9b4) | fix | ensure lint command auto-add exits after completion | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [bbe74b87e](https://github.com/angular/angular-cli/commit/bbe74b87e52579c06b911db6173f33c67b8010a6) | fix | provide actionable error message when routing declaration cannot be found | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------- | +| [c97c8e7c9](https://github.com/angular/angular-cli/commit/c97c8e7c9bbcad66ba80967681cac46042c3aca7) | fix | update `minimatch` dependency to `3.0.5` | + +## Special Thanks + +Alan Agius, Charles Lyding and Morga Cezary + + + + + +# 13.3.1 (2022-03-30) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------- | +| [cf3cb2ecf](https://github.com/angular/angular-cli/commit/cf3cb2ecf9ca47a984c4272f0094f2a1c68c7dfe) | fix | fix extra comma added when use --change-detection=onPush and --style=none to generate a component | + +### @angular-devkit/architect-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [9f8d4dea0](https://github.com/angular/angular-cli/commit/9f8d4dea0449e236de7b928c5cc97e597a6f5844) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [ba3486de9](https://github.com/angular/angular-cli/commit/ba3486de94e733addf0ac17706b806dd813c9046) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/benchmark + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [1f7fa6970](https://github.com/angular/angular-cli/commit/1f7fa6970e8cddb2ba0c42df0e048a57292b7fe8) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [293526c31](https://github.com/angular/angular-cli/commit/293526c31db9f0becc0ffc2d60999c80afa8a308) | fix | add `node_modules` prefix to excludes RegExp | +| [58ed97410](https://github.com/angular/angular-cli/commit/58ed97410b760909d523b05c3b4a06364e3c9a0f) | fix | allow Workers in Stackblitz | +| [4cd2331d3](https://github.com/angular/angular-cli/commit/4cd2331d34e2a9ab2ed78edf0284dbfefef511a5) | fix | don't override asset info when updating assets | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [c7c75820f](https://github.com/angular/angular-cli/commit/c7c75820f1d4ef827336626b78c8c3e5c0bd1f00) | fix | add Angular CLI major version as analytics dimension | + +## Special Thanks + +Alan Agius and gauravsoni119 + + + + + +# 12.2.17 (2022-03-31) + +### @angular-devkit/architect-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [ccb0f95f3](https://github.com/angular/angular-cli/commit/ccb0f95f33ff0d23a0ff9b237d0d78fc4c864787) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [abcdf4df2](https://github.com/angular/angular-cli/commit/abcdf4df20c29907ee28a38842942464addcf259) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/benchmark + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [2656a330e](https://github.com/angular/angular-cli/commit/2656a330eb365f37c3b6f8894436b4449d157e63) | fix | update `minimist` to `1.2.6` | + +## Special Thanks + +Alan Agius + + + + + +# 11.2.19 (2022-03-30) + +### @angular-devkit/architect-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [75caa1143](https://github.com/angular/angular-cli/commit/75caa1143f4007c9550ab0dabb62ae4df91e3827) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [80d479e9f](https://github.com/angular/angular-cli/commit/80d479e9fdfcf6863ebbe0986ea6cd29309f398d) | fix | update `minimist` to `1.2.6` | + +### @angular-devkit/benchmark + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------- | +| [f61cd1a79](https://github.com/angular/angular-cli/commit/f61cd1a79b6960711d4aa5b16d04308bbdc67beb) | fix | update `minimist` to `1.2.6` | + +## Special Thanks + +Alan Agius and Doug Parker + + + + + +# 13.3.0 (2022-03-16) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------- | +| [c995ed5e8](https://github.com/angular/angular-cli/commit/c995ed5e8a8e1b20cf376f4c48c5141fd5f4548a) | feat | support TypeScript 4.6 | + +## Special Thanks + +Alan Agius and Doug Parker + + + + + +# 13.2.6 (2022-03-09) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------ | +| [90a5531b1](https://github.com/angular/angular-cli/commit/90a5531b1fbe4043ab47f921ad6b858d34e7c7d0) | fix | ignore css only chunks during naming | + +## Special Thanks + +Alan Agius, Charles Lyding and Daniele Maltese + + + + + +# 13.2.5 (2022-02-23) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------- | +| [acf1e5e4a](https://github.com/angular/angular-cli/commit/acf1e5e4a5b359be125272f7e4055208116a13d8) | fix | don't rename blocks which have a name | +| [7a493979c](https://github.com/angular/angular-cli/commit/7a493979ccb71e974d668fca67d75e1b194f8608) | fix | update `terser` to `5.11.0` | + +## Special Thanks + +Alan Agius and Paul Gschwendtner + + + + + +# 13.2.4 (2022-02-17) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------- | +| [48c655ac9](https://github.com/angular/angular-cli/commit/48c655ac98e1d69622dd832c6a915c48e703cd8f) | fix | update `esbuild` to `0.14.22` | +| [c0736ea0b](https://github.com/angular/angular-cli/commit/c0736ea0b173861bb5ceb9315d27038eb28535e1) | fix | update license-webpack-plugin to 4.0.2 | + +## Special Thanks + +Alan Agius, Anner Visser and Charles Lyding + + + + + +# 13.2.3 (2022-02-09) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------- | +| [8c8377fee](https://github.com/angular/angular-cli/commit/8c8377fee4999266f4e58bf3c3091100d4393df7) | fix | block Karma from starting until build is complete | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [1317e470e](https://github.com/angular/angular-cli/commit/1317e470ec74d1dd9dced2d0ec0022abfe921995) | fix | support locating PNPM lock file during NGCC processing | + +## Special Thanks + +Alan Agius, Derek Cormier and Joey Perrott + + + + + +# 13.2.2 (2022-02-02) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [cc5505cfc](https://github.com/angular/angular-cli/commit/cc5505cfcf12732fad4f85e6e76c8e4f0584c13a) | fix | add `whatwg-url` to downlevel exclusion list | +| [ff54b49e7](https://github.com/angular/angular-cli/commit/ff54b49e7097cda2eb835bc8c9674f71fcc91c3c) | fix | ensure to use content hash as filenames hashing mechanism | +| [b0e2bb289](https://github.com/angular/angular-cli/commit/b0e2bb289050efc77478a0f50778abbec9c5a318) | perf | update `license-webpack-plugin` to `4.0.1` | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [c8826a973](https://github.com/angular/angular-cli/commit/c8826a9738f860e374bd65a058c6be1b02545133) | fix | correctly resolve schema references defaults | + +## Special Thanks + +Alan Agius, Derek Cormier and Joey Perrott + + + + + +# 13.2.1 (2022-01-31) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------- | +| [acd752773](https://github.com/angular/angular-cli/commit/acd752773d85e4debbc2b415c7ea369bc3d7018a) | fix | invalid browsers version ranges | + +## Special Thanks + +Alan Agius + + + + + +# 13.2.0 (2022-01-26) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [41a828e20](https://github.com/angular/angular-cli/commit/41a828e2068b881f744846c3f0edbff8c62cb9ce) | fix | updated Angular new project version to v13.2.0-next.0 | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------- | +| [f2c6b2b7e](https://github.com/angular/angular-cli/commit/f2c6b2b7ec88a1b7e45884b38faa0978af1b4b74) | fix | correctly handle ESM builders | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------- | +| [cbe028e37](https://github.com/angular/angular-cli/commit/cbe028e37c8af6f2e17cbbeddc968c9410151bbb) | feat | expose i18nDuplicateTranslation option of browser and server builders | +| [509322b62](https://github.com/angular/angular-cli/commit/509322b6214b3425bd209087ac99ee9b14edeaba) | fix | Don't use TAILWIND_MODE=watch | + +### @angular-devkit/build-webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [820ff2a3e](https://github.com/angular/angular-cli/commit/820ff2a3e84c5a55e23359e3a45714db83362a2a) | fix | correctly handle ESM webpack configurations | + +## Special Thanks + +Alan Agius, Cédric Exbrayat, Derek Cormier, Doug Parker, Joey Perrott, Jordan Pittman, grant-wilson and minijus + + + + + +# 13.1.4 (2022-01-19) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [2f2069dba](https://github.com/angular/angular-cli/commit/2f2069dbaa70c3d4725923f1c3ccbf56b1f57576) | fix | disable parsing `new URL` syntax | +| [bddd0fb9f](https://github.com/angular/angular-cli/commit/bddd0fb9f34a8706dd1646952eed08970b9cddbe) | fix | support ESNext as target for JavaScript optimizations | + +## Special Thanks + +Alan Agius, Derek Cormier and Doug Parker + + + + + +# 13.1.3 (2022-01-12) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------- | +| [4c9d72c65](https://github.com/angular/angular-cli/commit/4c9d72c659d912bd9ef4590a2e88340932a96868) | fix | remove extra space in `Unable to find compatible package` during `ng add` | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [9b07191b1](https://github.com/angular/angular-cli/commit/9b07191b1ccdcd2a6bb17686471acddd5862dcf5) | fix | set `skipTest` flag for resolvers when using ng new --skip-tests | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [5b39e0eca](https://github.com/angular/angular-cli/commit/5b39e0eca6e8a3825f66ad6cd1818e551bf98f08) | fix | automatically purge stale build cache entries | +| [6046e06b9](https://github.com/angular/angular-cli/commit/6046e06b926af29f89c605504f5356ec553c6390) | fix | correctly resolve `core-js/proposals/reflect-metadata` | +| [de68daa55](https://github.com/angular/angular-cli/commit/de68daa5581dd1f257382da16704d442b540ec41) | fix | enable `:where` CSS pseudo-class | +| [6a617ff4a](https://github.com/angular/angular-cli/commit/6a617ff4a2fe75968965dc5dcf0f3ba7bae92935) | fix | ensure `$localize` calls are replaced in watch mode | +| [92b4e067b](https://github.com/angular/angular-cli/commit/92b4e067b24bdcd1bb7e40612b5355ce61e040ce) | fix | load translations fresh start | +| [d674dcd1a](https://github.com/angular/angular-cli/commit/d674dcd1af409910dd4f41ac676349aee363ebdb) | fix | localized bundle generation fails in watch mode | +| [6876ad36e](https://github.com/angular/angular-cli/commit/6876ad36efaadac5c4d371cff96c9a4cfa0e3d2b) | fix | use `contenthash` instead of `chunkhash` for chunks | +| [11fd02105](https://github.com/angular/angular-cli/commit/11fd02105908e155c4a9c7f87e9641127cc2f378) | fix | websocket client only injected if required | +| [6ca0e41a9](https://github.com/angular/angular-cli/commit/6ca0e41a9b54aef0a8ea626be73e06d19370f3a7) | perf | update `esbuild` to `0.14.11` | + +## Special Thanks + +Alan Agius, Bill Barry, Derek Cormier, Elio Goettelmann, Joey Perrott, Kasper Christensen, Lukas Spirig and Zoltan Lehoczky + + + + + +# 12.2.15 (2022-01-12) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [526115fdb](https://github.com/angular/angular-cli/commit/526115fdb7d35ff01f5dbdb6027d9f5e925e4056) | fix | updated webpack-dev-server to latest security patch | + +## Special Thanks + +Doug Parker and iRealNirmal + + + +# 11.2.18 (2022-01-12) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------- | +| [534678450](https://github.com/angular/angular-cli/commit/534678450196a45610e88a85ee01317aa43dc788) | fix | updated webpack-dev-server to latest security patch | + +## Special Thanks + +Doug Parker and iRealNirmal + + + +# 13.2.0-next.1 (2021-12-15) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [41a828e20](https://github.com/angular/angular-cli/commit/41a828e2068b881f744846c3f0edbff8c62cb9ce) | fix | updated Angular new project version to v13.2.0-next.0 | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [0323a35b4](https://github.com/angular/angular-cli/commit/0323a35b47a4a2fd3870b09d46e3655714e50abd) | fix | add `tailwindcss` support for version 3 | +| [471930007](https://github.com/angular/angular-cli/commit/471930007cb9cd26264eab483fdfd1f5b4db6641) | fix | display FS cache information when `verbose` option is used | +| [f1d2873ca](https://github.com/angular/angular-cli/commit/f1d2873ca7ee337748366d04878514c2c27a72a2) | fix | only extract CSS styles when are specified in `styles` option | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [b03b9eefe](https://github.com/angular/angular-cli/commit/b03b9eefeac77b93931803de208118e3a6c5a928) | perf | reduce redundant module rebuilds when cache is restored | + +## Special Thanks + +Alan Agius, Cédric Exbrayat, Derek Cormier and Doug Parker + + + + + +# 13.1.2 (2021-12-15) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [1ddbd75ae](https://github.com/angular/angular-cli/commit/1ddbd75ae200c14b5f33556bd6d5ae6b7722d14e) | fix | add `tailwindcss` support for version 3 | +| [adf925c07](https://github.com/angular/angular-cli/commit/adf925c0755b6e78a57932becdb7b7a764afb9e6) | fix | display FS cache information when `verbose` option is used | +| [09c3826c9](https://github.com/angular/angular-cli/commit/09c3826c9d9128a6b520d0fe8da3cb466d18cddc) | fix | only extract CSS styles when are specified in `styles` option | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [f31d7f79d](https://github.com/angular/angular-cli/commit/f31d7f79dfa8f997fecdcfec1ebc6cfbe657f3fb) | perf | reduce redundant module rebuilds when cache is restored | + +## Special Thanks + +Alan Agius, Derek Cormier and Doug Parker + + + + + +# 11.2.17 (2021-12-16) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [1efff8f82](https://github.com/angular/angular-cli/commit/1efff8f82df38b7485f8a8dcdd5bfea5a457c6a1) | fix | exclude deprecated packages with removal migration from update | + +## Special Thanks + +Alan Agius and Doug Parker + + + + + +# 11.2.16 (2021-12-15) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------- | +| [f456b0962](https://github.com/angular/angular-cli/commit/f456b0962b9f339759bc86c092256f68d68d9ecf) | fix | error when updating Angular packages across multi-major migrations | +| [886d2511e](https://github.com/angular/angular-cli/commit/886d2511e292b687acce1ac4c6924f992494d14f) | fix | logic which determines which temp version of the CLI is to be download during `ng update` | +| [776d1210a](https://github.com/angular/angular-cli/commit/776d1210a9e62bf2531d977138f49f93820a8b87) | fix | update `ng update` output for Angular packages | + +## Special Thanks + +Alan Agius and Doug Parker + + + + + +# 10.2.4 (2021-12-15) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------- | +| [745d77728](https://github.com/angular/angular-cli/commit/745d777288a5ae0e79b4ecdf7b8483f242ba8e66) | fix | error when updating Angular packages across multi-major migrations | +| [460ea21b5](https://github.com/angular/angular-cli/commit/460ea21b5d4b8759a3f7457b885110022dd21dfc) | fix | logic which determines which temp version of the CLI is to be download during `ng update` | +| [03da12899](https://github.com/angular/angular-cli/commit/03da1289996790ae574a49bb46123c74417a97c2) | fix | update `ng update` output for Angular packages | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [d6582d489](https://github.com/angular/angular-cli/commit/d6582d48944f7bf169f3902e4c19186a6751f473) | fix | change `karma-jasmine-html-reporter` dependency to use tilde | + +## Special Thanks + +Alan Agius, Charles Lyding, Doug Parker and Joey Perrott + + + + + +# 13.1.1 (2021-12-10) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [a315b968a](https://github.com/angular/angular-cli/commit/a315b968a36e6aae990e52d9a18673fef9b5fda6) | fix | updated Angular new project version to v13.1.0 | + +## Special Thanks + +Alan Agius, Cédric Exbrayat and Derek Cormier + + + + + +# 13.1.0 (2021-12-09) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------ | +| [56f802b7d](https://github.com/angular/angular-cli/commit/56f802b7dd26bfc774b6b00982a1dbbe0bafddd0) | feat | ask to install angular-eslint when running ng lint in new projects | +| [ecd9fb5c7](https://github.com/angular/angular-cli/commit/ecd9fb5c774b6301348c4514da04d58ae8903d06) | feat | provide more detailed error for not found builder | +| [0b6071af3](https://github.com/angular/angular-cli/commit/0b6071af3a51e7d3f38a661bd4e0a3c3e81aff2f) | fix | `ng doc` does open browser on Windows | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | +| [d5d9f042f](https://github.com/angular/angular-cli/commit/d5d9f042f2ea42573b7ff4fab90cab85d0c5ec0b) | feat | add VS Code configurations when generating a new workspace | +| [f95cc8281](https://github.com/angular/angular-cli/commit/f95cc8281a64bd9ac19e0fa5d92cb0a6ee8c32ec) | feat | generate new projects using TypeScript 4.5 | +| [21809e14c](https://github.com/angular/angular-cli/commit/21809e14cd5c666c82fdaebc9e601341dfb76d0a) | feat | loosen project name validation | + +### @angular-devkit/schematics-cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------ | +| [339bab06c](https://github.com/angular/angular-cli/commit/339bab06cc25863571acb09cb3e877fed14ca2f9) | feat | generate new projects using TypeScript 4.5 | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------- | +| [bc8563760](https://github.com/angular/angular-cli/commit/bc856376039287cf5fb6135ca5da65a9000f5664) | feat | add estimated transfer size to build output report | +| [bc17cf0cd](https://github.com/angular/angular-cli/commit/bc17cf0cdd02bf50758e510756a26e6e6ca32d14) | feat | colorize file raw sizes based on failing budgets | +| [3c681b68d](https://github.com/angular/angular-cli/commit/3c681b68d7a32f1cfaf3feee6b2e02cc6e0f0568) | feat | set `dir` attribute when using localization | +| [6d0f99a2d](https://github.com/angular/angular-cli/commit/6d0f99a2deef957c15836c172b9f68f716f836a4) | feat | support JSON comments in dev-server proxy configuration file | +| [9300545e6](https://github.com/angular/angular-cli/commit/9300545e6148b4548cc02bb6a311a2f0e2bb79c5) | feat | watch i18n translation files with dev server | +| [9bacba342](https://github.com/angular/angular-cli/commit/9bacba3420cda7897091522415a8d55cf1b75106) | fix | differentiate components and global styles using file query instead of filename | +| [7408511da](https://github.com/angular/angular-cli/commit/7408511da555f37560ca7e3b536e15dfc8f6a1e5) | fix | display cleaner errors | +| [d55fc62ef](https://github.com/angular/angular-cli/commit/d55fc62ef2f8bc7a6f1190f56f8e8b64c9195263) | fix | fallback to use language ID to set the `dir` attribute | +| [4c288b8bd](https://github.com/angular/angular-cli/commit/4c288b8bd28e7215887aa52025c4fa41fcf7bc01) | fix | lazy modules bundle budgets | +| [562dc6a89](https://github.com/angular/angular-cli/commit/562dc6a8924826509d9012b2c0fe61c089077399) | fix | prefer ES2015 entrypoints when application targets ES2019 or lower | +| [ac66e400c](https://github.com/angular/angular-cli/commit/ac66e400cddc81bde46949d1abe4560185dfbedb) | fix | Sass compilation in StackBlitz webcontainers | +| [e1bac5bbb](https://github.com/angular/angular-cli/commit/e1bac5bbb36f391b89445ba61abe561c75746f30) | fix | update Angular peer dependencies to v13.1 prerelease | +| [789ddfaeb](https://github.com/angular/angular-cli/commit/789ddfaeb0fcbc9aab1581384b88c3618e606c4b) | perf | disable webpack backwards compatible APIs | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [5402f99f8](https://github.com/angular/angular-cli/commit/5402f99f8ad20e0a57456a416a992415fc6332bd) | fix | add `cjs` and `mjs` to passthrough files | +| [10d4ede2d](https://github.com/angular/angular-cli/commit/10d4ede2de42dfc302dcb4c5790274290170568d) | fix | handle promise rejection during Angular program analyzes | + +## Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Ferdinand Malcher, Joey Perrott and Ruslan Lekhman + + + + + +# 12.2.14 (2021-12-07) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------- | +| [30295b33e](https://github.com/angular/angular-cli/commit/30295b33ed74667f31e9d3a4a0017910a85fd734) | fix | error when updating Angular packages across multi-major migrations | +| [e07bd059e](https://github.com/angular/angular-cli/commit/e07bd059e3d6bc6b40191c036c467595ed119da7) | fix | logic which determines which temp version of the CLI is to be download during `ng update` | +| [ce1ec0420](https://github.com/angular/angular-cli/commit/ce1ec0420770a8e28c1c1301df9e5eb4548d4c53) | fix | update `ng update` output for Angular packages | +| [dd9f8df52](https://github.com/angular/angular-cli/commit/dd9f8df5204d639272f183795ebd48d7994df427) | fix | update `pacote` to `12.0.2` | + +## Special Thanks + +Alan Agius and Doug Parker + + + + + +# 13.0.4 (2021-12-01) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------- | +| [ded7b5c06](https://github.com/angular/angular-cli/commit/ded7b5c069a145d1b3e264538d7c4302919ad030) | fix | exit with a non-zero error code when migration fails during `ng update` | +| [250a58b48](https://github.com/angular/angular-cli/commit/250a58b4820a738aba7609627fa7fce0a24f10db) | fix | logic which determines which temp version of the CLI is to be download during `ng update` | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------- | +| [372e2e633](https://github.com/angular/angular-cli/commit/372e2e633f4bd9bf29c35d02890e1c6a70da3169) | fix | address eslint linting failures in `test.ts` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------- | +| [b835389c8](https://github.com/angular/angular-cli/commit/b835389c8a60749151039ed0baf0be025ce0932b) | fix | correctly extract messages when using cached build ([#22266](https://github.com/angular/angular-cli/pull/22266)) | +| [647a5f0b1](https://github.com/angular/angular-cli/commit/647a5f0b18e49b2ece3f43c0a06bfb75d7caef49) | fix | don't watch nested `node_modules` when polling is enabled | +| [4d01d4f72](https://github.com/angular/angular-cli/commit/4d01d4f72344c42f650f5495b21e6bd94069969a) | fix | transform remapped sourcemap into a plain object | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [4d918ef99](https://github.com/angular/angular-cli/commit/4d918ef9912d53a09d73fb19fa41b121dceed37c) | fix | JIT mode CommonJS accessing inexistent `default` property | + +## Special Thanks + +Alan Agius, Billy Lando, David-Emmanuel DIVERNOIS and Derek Cormier + + + + + +# 13.0.3 (2021-11-17) + +## Special Thanks + +Alan Agius, Joey Perrott and Krzysztof Platis + + + + + +# 13.0.2 (2021-11-10) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [34047b1ad](https://github.com/angular/angular-cli/commit/34047b1adccd7eb852c1900c872e9ca71c8d4cd9) | fix | avoid redirecting @angular/core in Angular migrations | +| [ff4538e98](https://github.com/angular/angular-cli/commit/ff4538e981cfff49b6e8433ffcb5ac2d2ea5d07e) | fix | favor ng-update `packageGroupName` in ng update output | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [1bc00b6fe](https://github.com/angular/angular-cli/commit/1bc00b6feb9033fd611dec965c82f03e4135a9f4) | fix | migrate ng-packagr configurations in package.json | +| [9ea74a13d](https://github.com/angular/angular-cli/commit/9ea74a13d07208373490c7cdb3ff7c452c698322) | fix | show warning when migrating ng-packagr JS configurations | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------------- | +| [35164bf92](https://github.com/angular/angular-cli/commit/35164bf92b986a67215580622aaddc4148a7c822) | fix | don't restore `input` of type `file` during HMR | +| [facb5d8ff](https://github.com/angular/angular-cli/commit/facb5d8ffd4f6a81d3132515b8bae64278cf8316) | fix | don't show `[NG HMR] Unknown input type` when restoring file type input | +| [ef8815d04](https://github.com/angular/angular-cli/commit/ef8815d0434836f2d8119e91a7bc09742ff77d37) | fix | improve sourcemap fidelity during code-coverage | +| [966a1334a](https://github.com/angular/angular-cli/commit/966a1334a6502f5d4a18710ae22e739e62770101) | fix | suppress "@charset" must be the first rule in the file warning | +| [1cdc24da0](https://github.com/angular/angular-cli/commit/1cdc24da0105fad75221e3c145de12dafc601059) | fix | update Angular peer dependencies to 13.0 stable | + +## Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott and Paul Gschwendtner + + + + + +# 13.0.1 (2021-11-03) + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------- | +| [40f599241](https://github.com/angular/angular-cli/commit/40f599241e278478c694580c9dec4f5cc34db011) | fix | updated Angular new project version to v13.0.0 | + +## Special Thanks + +Charles Lyding and Joey Perrott + + + + + +# 12.2.13 (2021-11-03) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [a2bd940e4](https://github.com/angular/angular-cli/commit/a2bd940e4ab44db57b0fc69d5346d2862a19c879) | fix | add verbose logging for differential loading and i18n | + +## Special Thanks + +Charles Lyding and Doug Parker + + + + + +# 13.0.0 (2021-11-03) + +## Breaking Changes + +### @angular/cli + +- We drop support for Node.js versions prior to `12.20`. + +### @schematics/angular + +- `classlist.js` and `web-animations-js` are removed from application polyfills and uninstalled from the package. These were only needed for compatibility with Internet Explorer, which is no longer needed now that Angular only supports evergreen browsers. See: https://angular.dev/reference/versions#browser-support. + +Add the following to the polyfills file for an app to re-add these packages: + +```typescript +import 'classlist.js'; +import 'web-animations-js'; +``` + +And then run: + +```sh +npm install classlist.js web-animations-js --save +``` + +- We removed several deprecated `@schematics/angular` deprecated options. +- `lintFix` have been removed from all schematics. `ng lint --fix` should be used instead. +- `legacyBrowsers` have been removed from the `application` schematics since IE 11 is no longer supported. +- `configuration` has been removed from the `web-worker` as it was unused. +- `target` has been removed from the `service-worker` as it was unused. + +### @angular-devkit/build-angular + +- Support for `karma-coverage-instanbul-reporter` has been dropped in favor of the official karma coverage plugin `karma-coverage`. + +- Support for `node-sass` has been removed. `sass` will be used by default to compile SASS and SCSS files. + +- `NG_PERSISTENT_BUILD_CACHE` environment variable option no longer have effect. Configure `cli.cache` in the workspace configuration instead. + +```json +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "cache": { + "enabled": true, + "path": ".custom-cache-path", + "environment": "all" + } + } + ... +} +``` + +- Calling `BuilderContext.scheduleBuilder()` with a builder from `@angular-devkit/build-angular` now requires passing the `target` property in the 3rd argument, like in the following example: + + ```typescript + context.scheduleBuilder('@angular-devkit/build-angular:ng-packagr', options, { + target: context.target, + }); + ``` + +- The automatic inclusion of Angular-required ES2015 polyfills to support ES5 browsers has been removed. Previously when targeting ES5 within the application's TypeScript configuration or listing an ES5 requiring browser in the browserslist file, Angular-required polyfills were included in the built application. However, with Angular no longer supporting IE11, there are now no browsers officially supported by Angular that would require these polyfills. As a result, the automatic inclusion of these ES2015 polyfills has been removed. Any polyfills manually added to an application's code are not affected by this change. + +- With this change a number of deprecated dev-server builder options which proxied to the browser builder have been removed. These options should be configured in the browser builder instead. + +The removed options are: + +- `aot` +- `sourceMap` +- `deployUrl` +- `baseHref` +- `vendorChunk` +- `commonChunk` +- `optimization` +- `progress` + +- With this change we removed several deprecated builder options +- `extractCss` has been removed from the browser builder. CSS is now always extracted. +- `servePathDefaultWarning` and `hmrWarning` have been removed from the dev-server builder. These options had no effect. + +- Deprecated `@angular-devkit/build-angular:tslint` builder has been removed. Use https://github.com/angular-eslint/angular-eslint instead. + +- Differential loading support has been removed. With Angular no longer supporting IE11, there are now no browsers officially supported by Angular that require ES5 code. As a result, differential loading's functionality for creating and conditionally loading ES5 and ES2015+ variants of an application is no longer required. + +- TypeScript versions prior to 4.4 are no longer supported. + +- The dev-server now uses WebSockets to communicate changes to the browser during HMR and live-reloaded. If during your development you are using a proxy you will need to enable proxying of WebSockets. + +- We remove inlining of Google fonts in WOFF format since IE 11 is no longer supported. Other supported browsers use WOFF2. + +### @angular-devkit/build-webpack + +- Support for `webpack-dev-server` version 3 has been removed. For more information about the migration please see: https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md + +Note: this change only affects users depending on `@angular-devkit/build-webpack` directly. + +### @angular-devkit/core + +- With this change we drop support for the deprecated behaviour to transform `id` in schemas. Use `$id` instead. + +Note: this only effects schematics and builders authors. + +- The deprecated JSON parser has been removed from public API. [jsonc-parser](https://www.npmjs.com/package/jsonc-parser) should be used instead. + +### @angular-devkit/schematics + +- `isAction` has been removed without replacement as it was unused. + +- With this change we remove the following deprecated APIs +- `TslintFixTask` +- `TslintFixTaskOptions` + +**Note:** this only effects schematics developers. + +### @ngtools/webpack + +- Deprecated `inlineStyleMimeType` option has been removed from `AngularWebpackPluginOptions`. Use `inlineStyleFileExtension` instead. + +- Applications directly using the `webpack-cli` and not the Angular CLI to build must set the environment variable `DISABLE_V8_COMPILE_CACHE=1`. The `@ngtools/webpack` package now uses dynamic imports to provide support for the ESM `@angular/compiler-cli` package. The `v8-compile-cache` package used by the `webpack-cli` does not currently support dynamic import expressions and will cause builds to fail if the environment variable is not specified. Applications using the Angular CLI are not affected by this limitation. + +## Deprecations + +### + +- `@angular-devkit/build-optimizer` + +It's functionality has been included in `@angular-devkit/build-angular` so this package is no longer needed by the CLI and we will stop publishing the package soon. It has been an experimental (never hit `1.0.0`) and internal (only used by Angular itself) package and should be not be used directly by others. + +### @angular-devkit/build-angular + +- `NG_BUILD_CACHE` environment variable option will be removed in the next major version. Configure `cli.cache` in the workspace configuration instead. + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [9fe55752d](https://github.com/angular/angular-cli/commit/9fe55752db8bb50cad5a1ddfe670dce06528e23e) | feat | officially support Node.js v16 | +| [5ad145722](https://github.com/angular/angular-cli/commit/5ad145722f66af526a36983b259c6d625c93f307) | fix | error when updating Angular packages across multi-major migrations | +| [e4bc35e33](https://github.com/angular/angular-cli/commit/e4bc35e332e378f8d238f4069dc56f422fe205d6) | fix | exclude packages from ng add that contain invalid peer dependencies | +| [e1b954d70](https://github.com/angular/angular-cli/commit/e1b954d707f90622d8a75fc45840cefeb224c286) | fix | keep relative migration paths during update analysis | +| [c3acf3cc2](https://github.com/angular/angular-cli/commit/c3acf3cc26b9e37a3b8f4c369f42731f46b522ee) | fix | remove unused cli project options. | +| [77fe6c4e6](https://github.com/angular/angular-cli/commit/77fe6c4e67147ff42fa6350edaf4ef7dc184a3a6) | fix | update `engines` to require `node` `12.20.0` | +| [8795536a3](https://github.com/angular/angular-cli/commit/8795536a31efbed6373787188cb21c5d1e0accbd) | fix | update `ng update` output for Angular packages | +| [d8c9f6eaf](https://github.com/angular/angular-cli/commit/d8c9f6eaf4513639741d20c6af97a751b33b968e) | fix | update the update command to fully support Node.js v16 | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------- | +| [7ff8c5350](https://github.com/angular/angular-cli/commit/7ff8c5350ea2e49574dd659adae02215957d2685) | feat | add `/.angular/cache` to `.gitignore` | +| [3ba13f467](https://github.com/angular/angular-cli/commit/3ba13f467c12f4ad0c314cc92a2d94fb63f640ec) | feat | add `noImplicitOverride` and `noPropertyAccessFromIndexSignature` to workspace tsconfig | +| [268a03b63](https://github.com/angular/angular-cli/commit/268a03b63094d9c680401bc0977edafb22826ce3) | feat | add migration to update the workspace config | +| [7bdcd7da1](https://github.com/angular/angular-cli/commit/7bdcd7da1ff3a31f4958d90d856beb297e99b187) | feat | create new projects with rxjs 7 | +| [eac18aed7](https://github.com/angular/angular-cli/commit/eac18aed78da55efb840a3ef6f5e90718946504c) | feat | drop polyfills required only for Internet Explorer now that support has been dropped for it | +| [4f91816b2](https://github.com/angular/angular-cli/commit/4f91816b2951c0e2b0109ad1938eb0ae632c0c76) | feat | migrate libraries to be published from ViewEngine to Ivy Partial compilation | +| [5986befcd](https://github.com/angular/angular-cli/commit/5986befcdc953c0e8c90c756ac1c89b8c4b66614) | feat | remove deprecated options | +| [9fbd16655](https://github.com/angular/angular-cli/commit/9fbd16655e86ec6fc598a47436e3e80a48beb649) | feat | remove IE 11 specific polyfills | +| [a7b2e6f51](https://github.com/angular/angular-cli/commit/a7b2e6f512d2a1124f0d2c68caacfe6552a10cd5) | feat | update ngsw-config resources extensions | +| [732ef7985](https://github.com/angular/angular-cli/commit/732ef798523f74994ed3d482a65b191058674d19) | fix | add browserslist configuration in library projects | +| [585adacd0](https://github.com/angular/angular-cli/commit/585adacd0624ddf32c5c69a755d8e542f3463861) | fix | don't add `destroyAfterEach` in newly generated spec files | +| [e58226ee9](https://github.com/angular/angular-cli/commit/e58226ee948ea88f27a81d50d71945b5c9c39ee3) | fix | don't export `renderModuleFactory` from server file | +| [0ec0ad8a4](https://github.com/angular/angular-cli/commit/0ec0ad8a4dba4a778b368c5cd76ef13fb370b310) | fix | remove `target` and `lib` options for library tsconfig | +| [f227e145d](https://github.com/angular/angular-cli/commit/f227e145dfbec2954cb96c92ab3c4cb97cbe0f32) | fix | updated Angular new project version to v13.0 prerelease | + +### + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [5e435ff37](https://github.com/angular/angular-cli/commit/5e435ff37703f9ffea7fa92fbd5cd42d9a3db07e) | docs | mark `@angular-devkit/build-optimizer` as deprecated. | + +### @angular-devkit/architect + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------ | +| [09e039500](https://github.com/angular/angular-cli/commit/09e039500f34b0d6a16e62128409ac5821e8b9c2) | feat | include workspace extensions in project metadata | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------- | +| [f53bf9dc2](https://github.com/angular/angular-cli/commit/f53bf9dc21ee9aa8a682b8a82ee8a9870fa859e1) | feat | add `type=module` to all scripts tags | +| [e95ecb8ab](https://github.com/angular/angular-cli/commit/e95ecb8ab0382eb803741619c446d6cc7b215ba0) | feat | deprecate deployUrl | +| [7dcfffaff](https://github.com/angular/angular-cli/commit/7dcfffafff6f3d29bbe679a90cdf77b1292fec0b) | feat | drop support for `karma-coverage-instanbul-reporter` | +| [ac3fc2752](https://github.com/angular/angular-cli/commit/ac3fc2752f28761e1cd42157b59dcf2364ae5567) | feat | drop support for `node-sass` | +| [5904afd1d](https://github.com/angular/angular-cli/commit/5904afd1de3ffa0bb6cd1757795ba9abfce9e523) | feat | enable disk cache by default and provide configurable options | +| [22cd9edfa](https://github.com/angular/angular-cli/commit/22cd9edfafd357bb9d62a93dd56f033b3f34bbe8) | feat | favor es2020 main fields | +| [7576136b2](https://github.com/angular/angular-cli/commit/7576136b2fc8a9173b0a92e2ab14c9bc2559081e) | feat | remove automatic inclusion of ES5 browser polyfills | +| [000b0e51c](https://github.com/angular/angular-cli/commit/000b0e51c166ecd26b6f24d6a133ea5076df9849) | feat | remove deprecated dev-server options | +| [20e48a33c](https://github.com/angular/angular-cli/commit/20e48a33c14a1b0b959ba0a45018df53a3e129c8) | feat | remove deprecated options | +| [e78f6ab5d](https://github.com/angular/angular-cli/commit/e78f6ab5d8f00338d826c8407ce5c8fca40cf097) | feat | remove deprecated tslint builder | +| [701214d17](https://github.com/angular/angular-cli/commit/701214d174586fe7373b6155024c9b6e97b26377) | feat | remove differential loading support | +| [fb1ad7c5b](https://github.com/angular/angular-cli/commit/fb1ad7c5b3fa3df85f1d3dff3850e1ad0003ef9d) | feat | support ESM proxy configuration files for the dev server | +| [505438cc4](https://github.com/angular/angular-cli/commit/505438cc4146b1950038531ce30e1f62f7c41d00) | feat | support TypeScript 4.4 | +| [32dbf659a](https://github.com/angular/angular-cli/commit/32dbf659acb632fac1d76d99d8191ea9c5e6350b) | feat | update `webpack-dev-server` to version 4 | +| [c1efaa17f](https://github.com/angular/angular-cli/commit/c1efaa17feb1d2911dcdea12688d75086d410bf1) | fix | calculate valid Angular versions from peerDependencies | +| [d7af4a7b5](https://github.com/angular/angular-cli/commit/d7af4a7b536a7c43704f808ea208bc9f230d2403) | fix | enable custom `es2020` and `es2015` conditional exports | +| [f383f3201](https://github.com/angular/angular-cli/commit/f383f3201b69d28f8755c0bd63134619f9da408d) | fix | ESM-interop loaded plugin creators of `@angular/localize/tools` not respected | +| [7934becb5](https://github.com/angular/angular-cli/commit/7934becb581d07c8e1f74898ddd4c20f050be659) | fix | generate unique webpack runtimes | +| [b14e0a547](https://github.com/angular/angular-cli/commit/b14e0a54727352a6939c7a0ff13dffe2deaa67d2) | fix | improve sourcemaps fidelity when code coverage is enabled | +| [e19287453](https://github.com/angular/angular-cli/commit/e19287453c10740ea21b31a6c8a3cd5f3714955d) | fix | move `@angular/localize` detection prior to webpack initialization | +| [76d6d8826](https://github.com/angular/angular-cli/commit/76d6d8826f9968f84edf219f67b84673d70bbe95) | fix | set browserslist defaults | +| [167eed465](https://github.com/angular/angular-cli/commit/167eed4654be4480c45d7fdfe7a0b9f160170289) | fix | update Angular peer dependencies to v13.0 prerelease | +| [1d8cdf853](https://github.com/angular/angular-cli/commit/1d8cdf853dc8fdea78b067a715b3342ed9427caa) | fix | update esbuild to 0.13.12 | +| [884111ac0](https://github.com/angular/angular-cli/commit/884111ac0b8a73dca06d844b2ed795a3e3ed3289) | fix | update IE unsupported and deprecation messages | +| [4be6537dd](https://github.com/angular/angular-cli/commit/4be6537ddf4b32e8d204dbaa75f1a53712fe9d44) | fix | update TS/JS regexp checks to latest extensions | +| [427a9ee97](https://github.com/angular/angular-cli/commit/427a9ee9738c0911caeaba5fb4b59d183ffe6244) | fix | update workspace tsconfig lib es2020 | +| [ea926db25](https://github.com/angular/angular-cli/commit/ea926db257ad3b042af86178e472b5763a695146) | fix | use es2015 when generating server bundles | +| [13cceab8e](https://github.com/angular/angular-cli/commit/13cceab8e737a12d0809f184f852ceb5620d81fb) | fix | use URLs for absolute import paths with ESM | +| [4e0743c8a](https://github.com/angular/angular-cli/commit/4e0743c8ad5879f212f2ea232ac9492848a8df2c) | perf | change webpack hashing function to `xxhash64` | +| [cb7d156c2](https://github.com/angular/angular-cli/commit/cb7d156c23a7ef2f1c2f338db1487b85f8b98690) | perf | use esbuild as a CSS optimizer for global styles | +| [8e82263c5](https://github.com/angular/angular-cli/commit/8e82263c5e7da6ca25bdd4e2ce9ad2c775d623b7) | perf | use esbuild/terser combination to optimize global scripts | +| [e82eef924](https://github.com/angular/angular-cli/commit/e82eef924eb172a98fa157a958bde2cfcaa52ce6) | refactor | remove WOFF handling from inline-fonts processor | + +### @angular-devkit/build-webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------- | +| [a0b5897d5](https://github.com/angular/angular-cli/commit/a0b5897d50a00ee4668029c2cbc47cacd2ab925f) | feat | update `webpack-dev-server` to version 4 | +| [9efcb32e3](https://github.com/angular/angular-cli/commit/9efcb32e378442714eae4caec43281123c5e30f6) | fix | better handle concurrent dev-servers | + +### @angular-devkit/core + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------- | +| [0c92ea5ca](https://github.com/angular/angular-cli/commit/0c92ea5ca34d82849862d55c4210cf62c819d514) | feat | remove deprecated schema id handling | +| [9874aff71](https://github.com/angular/angular-cli/commit/9874aff71ecb5f3baf6c1dcc489581d1dcb58491) | fix | add missing option peer dependency on `chokidar` | +| [a54e5e065](https://github.com/angular/angular-cli/commit/a54e5e06551c828eb5cf08695674e04fd8a78bf3) | fix | support Node.js v16 with `NodeJsSyncHost`/`NodeJsAsyncHost` delete operation | +| [d722fdf1f](https://github.com/angular/angular-cli/commit/d722fdf1f67c394762906794605bc1ad657670d1) | refactor | remove deprecated JSON parser | + +### @angular-devkit/schematics + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [0565ed62e](https://github.com/angular/angular-cli/commit/0565ed62eb08c1e82cffb2533e6afde216c37eb7) | feat | add UpdateBuffer2 based on magic-string | +| [8954d1152](https://github.com/angular/angular-cli/commit/8954d1152b6c1a33dd7d4b63d2fa430d91e7b370) | feat | remove deprecated `isAction` | +| [053b7d66c](https://github.com/angular/angular-cli/commit/053b7d66c269423804891e4d43d61f8605838e24) | feat | remove deprecated tslint APIs | +| [bdd89ae84](https://github.com/angular/angular-cli/commit/bdd89ae84ad6919b670dde862de72f562c86d0c5) | fix | handle zero or negative length removals in update buffer | + +### @ngtools/webpack + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------- | +| [d2a97f919](https://github.com/angular/angular-cli/commit/d2a97f9193fcf7e454fe8eb48c0ed732d3b2f24f) | fix | update Angular peer dependencies to v13.0 prerelease | +| [7928b18ed](https://github.com/angular/angular-cli/commit/7928b18edf34243a404b5a4f40a5d6e40247d797) | perf | reduce repeat path mapping analysis during resolution | +| [8ce8e4edc](https://github.com/angular/angular-cli/commit/8ce8e4edc5ca2984d6a36fe4c7d308fa7f089102) | refactor | remove deprecated `inlineStyleMimeType` option | +| [7d98ab3df](https://github.com/angular/angular-cli/commit/7d98ab3df9f7c15612c69cedca5a01a535301508) | refactor | support an ESM-only `@angular/compiler-cli` package | + +## Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Douglas Parker, Joey Perrott, Kristiyan Kostadinov, Lukas Spirig and Paul Gschwendtner + + + + + +# 12.2.9 (2021-10-06) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- | +| [9d45b7752](https://github.com/angular/angular-cli/commit/9d45b77522a9693c4876fdfd741e8869e89e0268) | fix | add web-streams-polyfill to downlevel exclusion list | +| [ccedf53a8](https://github.com/angular/angular-cli/commit/ccedf53a820a748b56c84528294b36c7af30dbaf) | fix | update `esbuild` to `0.13.4` | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 12.2.8 (2021-10-01) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [821a1b5a9](https://github.com/angular/angular-cli/commit/821a1b5a949d53f2e82f734062b711a166d42e24) | fix | babel adjust enum plugin incorrectly transforming loose enums | + +## Special Thanks + +Paul Gschwendtner + + + + + +# 12.2.7 (2021-09-22) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | +| [d856b4d23](https://github.com/angular/angular-cli/commit/d856b4d2369bea76ce65fc5f6d1585145ad41618) | fix | support WASM-based esbuild optimizer fallback | + +## Special Thanks + +Alan Agius and Charles Lyding + + + + + +# 12.2.6 (2021-09-15) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [8b21effad](https://github.com/angular/angular-cli/commit/8b21effad673877cf1a82ef7d0601393a65517fb) | fix | handle `FORCE_COLOR` when stdout is not instance of `WriteStream` | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [ea60f0f52](https://github.com/angular/angular-cli/commit/ea60f0f527f2ab8fc5acc967138c4ae993946923) | fix | handle `FORCE_COLOR` when stdout is not instance of `WriteStream` | + +## Special Thanks + +Alan Agius + + + + + +# 12.2.5 (2021-09-08) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------- | +| [0498768c5](https://github.com/angular/angular-cli/commit/0498768c54de225a40c28fdf27bb1fc43959ba20) | fix | disable dev-server response compression | +| [367fce2e9](https://github.com/angular/angular-cli/commit/367fce2e9f9389c41f2ed5361ef6749198c49785) | fix | improve Safari browserslist to esbuild target conversion | + +## Special Thanks: + +Alan Agius and Charles Lyding + + + + + +# 12.2.4 (2021-09-01) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------- | +| [aaadef026](https://github.com/angular/angular-cli/commit/aaadef02698ba729ca04ccd4159bda5b6582babb) | fix | update `esbuild` to `0.12.24` | +| [f8a9f4a01](https://github.com/angular/angular-cli/commit/f8a9f4a0100286b7cf656ffbe486c3424cad5172) | fix | update `mini-css-extract-plugin` to `2.2.1` | + +## Special Thanks + +Alan Agius + + + + + +# 12.2.3 (2021-08-26) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------- | +| [3e3321857](https://github.com/angular/angular-cli/commit/3e33218578007f93a131dc8be569e9985179098f) | fix | RGBA converted to hex notation in component styles breaks IE11 | + +## Special Thanks: + +Alan Agius and Trevor Karjanis + + + + + +# 12.2.2 (2021-08-18) + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| [a55118a75](https://github.com/angular/angular-cli/commit/a55118a753555c0082cfd434379559df7e3eb7f9) | fix: provide supported browsers to esbuild | +| [81baa4f95](https://github.com/angular/angular-cli/commit/81baa4f956443fcc718f9021fd23ab7064d04607) | fix: update Angular peer dependencies to 12.2 stable | +| [297410ae8](https://github.com/angular/angular-cli/commit/297410ae860860d71905639cf38b49ff05813845) | fix: handle undefined entrypoints when marking async chunks | + +### @ngtools/webpack + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| [b7199f366](https://github.com/angular/angular-cli/commit/b7199f366841d976b502ad5f1923e24ea2f6b302) | fix: update Angular peer dependencies to 12.2 stable | + +## Special Thanks: + +Alan Agius, Charles Lyding, Joey Perrott and Simon Primetzhofer + + + + + +# 12.2.1 (2021-08-11) + +### @angular/cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| [8dc3c895a](https://github.com/angular/angular-cli/commit/8dc3c895a6531316e672031c8d0815781f0c089a) | fix(@angular/cli): show error when using non-TTY terminal without passing `--skip-confirmation` during `ng add` | + +### @angular-devkit/schematics-cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| [eded01270](https://github.com/angular/angular-cli/commit/eded01270f9aa70f6ba4806a068de8d1c0a52454) | fix(@angular-devkit/schematics-cli): log when in debug and/or dry run modes | + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| [22e0208a9](https://github.com/angular/angular-cli/commit/22e0208a9ee6257213b3bf93ac61a2c3d4ac9504) | fix(@angular-devkit/build-angular): ensure native async is downlevelled in third-party libraries | +| [9b4b86fb0](https://github.com/angular/angular-cli/commit/9b4b86fb0d9c88a3c714f5eabf925859bb7b71bb) | fix(@angular-devkit/build-angular): support both pure annotation forms for static properties | +| [cea028090](https://github.com/angular/angular-cli/commit/cea0280908db39308ac5fa37374b138ceb79ecea) | fix(@angular-devkit/build-angular): do not consume inline sourcemaps when vendor sourcemaps is disabled. | +| [e7ec0346e](https://github.com/angular/angular-cli/commit/e7ec0346e69c090ded7d9ec6d3574deb79926db0) | fix(@angular-devkit/build-angular): avoid attempting to optimize copied JavaScript assets | +| [4f757c2bc](https://github.com/angular/angular-cli/commit/4f757c2bcf1356d33eaa86bc3b715c0a6b7c2ed8) | fix(@angular-devkit/build-angular): handle null maps in JavaScript optimizer worker | + +## Special Thanks: + +Alan Agius and Charles Lyding + + + + + +# 12.2.0 (2021-08-04) + +### @angular/cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| [259e26979](https://github.com/angular/angular-cli/commit/259e26979ebc712ee08fd36fb68a9576c1e02447) | fix(@angular/cli): merge npmrc files values | +| [c1eddbdc9](https://github.com/angular/angular-cli/commit/c1eddbdc98631fdfff287ce566d79ed43b601e0f) | fix(@angular/cli): handle `YARN_` environment variables during `ng update` and `ng add` | +| [6b00d1270](https://github.com/angular/angular-cli/commit/6b00d1270acaf33f32ee68c4254ce06951ddcb8c) | fix(@angular/cli): handle NPM_CONFIG environment variables during ng update and ng add | +| [88ee85c41](https://github.com/angular/angular-cli/commit/88ee85c4178e37b72001e8946b70a46ba739a0b7) | fix(@angular/cli): disable update notifier when retrieving package manager version during `ng version` | + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| [d750c686f](https://github.com/angular/angular-cli/commit/d750c686fd26f3ccfccb039027bd816a91279497) | fix(@angular-devkit/build-angular): add priority to copy-webpack-plugin patterns | +| [4bcd1dc9e](https://github.com/angular/angular-cli/commit/4bcd1dc9ee744343a465d73d51d4a062964a3714) | fix(@angular-devkit/build-angular): allow classes with pure annotated static properties to be optimized | +| [ceade0c27](https://github.com/angular/angular-cli/commit/ceade0c27e4b8b0e731e6ca5128fd86cf071d029) | fix(@angular-devkit/build-angular): dasherize disable-host-check suggestion | +| [8383c6b42](https://github.com/angular/angular-cli/commit/8383c6b421f7005a25a3bff0826048f3a24f3030) | fix(@angular-devkit/build-angular): silence Sass compiler warnings from 3rd party stylesheets | +| [07763702f](https://github.com/angular/angular-cli/commit/07763702fd244ba44aebb714a295dbf5ba72b91d) | fix(@angular-devkit/build-angular): force linker `sourceMapping` option to false. | +| [a5c69722f](https://github.com/angular/angular-cli/commit/a5c69722ffeceb72dcd46901c2bb983e5dc8bf32) | fix(@angular-devkit/build-angular): ensure `NG_PERSISTENT_BUILD_CACHE` always creates a cache in the specified cache directory | +| [c65b04999](https://github.com/angular/angular-cli/commit/c65b049996a8de9d9fcc66631872424cbe5f13f9) | fix(@angular-devkit/build-angular): fail browser build when index generation fails | +| [3d71c63b3](https://github.com/angular/angular-cli/commit/3d71c63b3a11946ebfca3f0d97d4fbf8dca16255) | fix(@angular-devkit/build-angular): fix issue were `@media all` causing critical CSS inling to fail | +| [9a04975a2](https://github.com/angular/angular-cli/commit/9a04975a2170c3ecc2c09c32bd15a89c613e198f) | fix(@angular-devkit/build-angular): `extractLicenses` didn't have an effect when using server builder | +| [2ac8e9c0e](https://github.com/angular/angular-cli/commit/2ac8e9c0e131bf7fcb2c6e92500eeaa112efcefb) | fix(@angular-devkit/build-angular): display incompatibility errors | +| [2c2b49919](https://github.com/angular/angular-cli/commit/2c2b499193fb319e1c9cb92318610353b7720e2b) | fix(@angular-devkit/build-angular): limit advanced terser passes to two | +| [1be3b0783](https://github.com/angular/angular-cli/commit/1be3b07836659487e4aa9b8c71c673635e268a60) | fix(@angular-devkit/build-angular): exclude `outputPath` from persistent build cache key | +| [fefd6d042](https://github.com/angular/angular-cli/commit/fefd6d04213e61d3f48c0484d8c6a8dcff1ecd34) | perf(@angular-devkit/build-angular): use `esbuild` as a CSS optimizer for component styles | +| [18cfa0431](https://github.com/angular/angular-cli/commit/18cfa04317230f934ccba798c080543bb389725f) | feat(@angular-devkit/build-angular): add support to inline Adobe Fonts | +| [9a751f0f8](https://github.com/angular/angular-cli/commit/9a751f0f81919d67f5eeeaecbe807d5c216f6a7a) | fix(@angular-devkit/build-angular): handle `ENOENT` and `ENOTDIR` errors when deleting outputs | +| [41e645792](https://github.com/angular/angular-cli/commit/41e64579213b9d4a7c976ea45daa6b32d980df10) | fix(@angular-devkit/build-angular): downlevel `for await...of` when targeting ES2018+ | +| [070a13364](https://github.com/angular/angular-cli/commit/070a1336478d721bbbb474622f50fab455cda26c) | fix(@angular-devkit/build-angular): configure webpack target in common configuration | +| [da32daa75](https://github.com/angular/angular-cli/commit/da32daa75d08d4be177af5fa16088398d7fb427b) | perf(@angular-devkit/build-angular): use combination of `esbuild` and `terser` as a JavaScript optimizer | +| [6a2b11906](https://github.com/angular/angular-cli/commit/6a2b11906e4173562a82b3654ff662dd05513049) | perf(@angular-devkit/build-angular): cache JavaScriptOptimizerPlugin results | +| [ab17b1721](https://github.com/angular/angular-cli/commit/ab17b1721c05366e592cf805ad6d25e672b314bf) | fix(@angular-devkit/build-angular): handle ng-packagr errors more gracefully. | +| [d4c5f8518](https://github.com/angular/angular-cli/commit/d4c5f8518d4801b9fd76de289a015dcbb8d8f69b) | fix(@angular-devkit/build-angular): control linker template sourcemapping via builder sourcemap options | +| [06181c2fb](https://github.com/angular/angular-cli/commit/06181c2fbf5a20396b2d0e2b3925ceb1276947fb) | fix(@angular-devkit/build-angular): parse web-workers in tests when webWorkerTsConfig is defined | + +### @angular-devkit/build-webpack + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [615353022](https://github.com/angular/angular-cli/commit/61535302204a2a767f85053b7efaa6ac5ac64098) | fix(@angular-devkit/build-webpack): emit result when webpack is closed | + +### @ngtools/webpack + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| [dbbcf5c8c](https://github.com/angular/angular-cli/commit/dbbcf5c8c4ec4427609942f4ef7053c1b51773c9) | fix(@ngtools/webpack): only track file dependencies | +| [7536338e0](https://github.com/angular/angular-cli/commit/7536338e0becc7f9cde62becbde58e18a270cb31) | fix(@ngtools/webpack): allow generated assets of Angular component resources | +| [720feee34](https://github.com/angular/angular-cli/commit/720feee34f910fc11c40e2f68d919d61b7d6cbec) | fix(@ngtools/webpack): avoid non-actionable template type-checker syntax diagnostics | +| [6a7bcf330](https://github.com/angular/angular-cli/commit/6a7bcf3300b459aef80fcf98f2475c977f6244dc) | fix(@ngtools/webpack): encode component style data | +| [12c14b565](https://github.com/angular/angular-cli/commit/12c14b56537d65d6986e245ab1ae4dd9aa8dd378) | fix(@ngtools/webpack): remove no longer needed component styles workaround | + +### @schematics/angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| [20fd33f6d](https://github.com/angular/angular-cli/commit/20fd33f6d4ce6cef1feb508a0221222e83a85630) | feat(@schematics/angular): destroy test module after every test | +| [5b10d4f54](https://github.com/angular/angular-cli/commit/5b10d4f549ebc12645ad08cba8ab7b91eaa87d28) | fix(@schematics/angular): remove unsafe any usage in application spec file | +| [1b5e18e7b](https://github.com/angular/angular-cli/commit/1b5e18e7b401efb7ec73d99c4d77d9b29e956724) | fix(@schematics/angular): replace interactive `div` with `button` in application component template | +| [0907b6941](https://github.com/angular/angular-cli/commit/0907b694174d6d684d965baf6cd37b87f49742e8) | fix(@schematics/angular): use stricter semver for `karma-jasmine-html-reporter` | +| [8ad1539c5](https://github.com/angular/angular-cli/commit/8ad1539c5e73bad30eb6eb340379d64db208098c) | fix(@schematics/angular): add 'none' value for the 'style' option of the component schematic | +| [e5ba29c7d](https://github.com/angular/angular-cli/commit/e5ba29c7d54cbd83057cf23a21119ea5a3146993) | fix(@schematics/angular): display warning during migrations when using third-party builders | +| [a44dc02fe](https://github.com/angular/angular-cli/commit/a44dc02feecaf8735f2dc6128a5b6cc5666b4434) | fix(@schematics/angular): add devtools to ng new | + +## Special Thanks: + +Alan Agius, Charles Lyding, David Scourfield, Doug Parker, hien-pham, Joey Perrott, LeonEck, Mike +Jancar, twerske, Vaibhav Singh and originalfrostig + + + + + +# 12.2.0-rc.0 (2021-07-28) + +### @angular/cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| [259e26979](https://github.com/angular/angular-cli/commit/259e26979ebc712ee08fd36fb68a9576c1e02447) | fix(@angular/cli): merge npmrc files values | + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| [d750c686f](https://github.com/angular/angular-cli/commit/d750c686fd26f3ccfccb039027bd816a91279497) | fix(@angular-devkit/build-angular): add priority to copy-webpack-plugin patterns | + +### @angular-devkit/build-webpack + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [615353022](https://github.com/angular/angular-cli/commit/61535302204a2a767f85053b7efaa6ac5ac64098) | fix(@angular-devkit/build-webpack): emit result when webpack is closed | + +## Special Thanks: + +Alan Agius, Charles Lyding, Joey Perrott and originalfrostig + + + + + +# 12.1.4 (2021-07-28) + +### @angular/cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| [e02c97dd0](https://github.com/angular/angular-cli/commit/e02c97dd09399443438b32cf1ad47fa0f7011df3) | fix(@angular/cli): merge npmrc files values | + +### @schematics/angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| [cfc267426](https://github.com/angular/angular-cli/commit/cfc267426716e9ecf0c9833720cb35298284f699) | fix(@schematics/angular): ensure valid SemVer range for new project Angular packages | + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| [55c0bddc8](https://github.com/angular/angular-cli/commit/55c0bddc8b2425309f00733eca96c06f60f867d5) | fix(@angular-devkit/build-angular): add priority to copy-webpack-plugin patterns | + +### @angular-devkit/build-webpack + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [b3736a3c0](https://github.com/angular/angular-cli/commit/b3736a3c09f39f5ee5dc12d98535fe4b6803ea3b) | fix(@angular-devkit/build-webpack): emit result when webpack is closed | + +## Special Thanks: + +Alan Agius, Charles Lyding, Joey Perrott and originalfrostig + + + + + +# 12.2.0-next.3 (2021-07-21) + +### @angular/cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| [c1eddbdc9](https://github.com/angular/angular-cli/commit/c1eddbdc98631fdfff287ce566d79ed43b601e0f) | fix(@angular/cli): handle `YARN_` environment variables during `ng update` and `ng add` | +| [6b00d1270](https://github.com/angular/angular-cli/commit/6b00d1270acaf33f32ee68c4254ce06951ddcb8c) | fix(@angular/cli): handle NPM_CONFIG environment variables during ng update and ng add | + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| [4bcd1dc9e](https://github.com/angular/angular-cli/commit/4bcd1dc9ee744343a465d73d51d4a062964a3714) | fix(@angular-devkit/build-angular): allow classes with pure annotated static properties to be optimized | +| [ceade0c27](https://github.com/angular/angular-cli/commit/ceade0c27e4b8b0e731e6ca5128fd86cf071d029) | fix(@angular-devkit/build-angular): dasherize disable-host-check suggestion | + +## Special Thanks: + +Alan Agius, Charles Lyding, Joey Perrott, LeonEck and Mike Jancar + + + + + +# 12.1.3 (2021-07-21) + +### @angular/cli + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| [eaa2378b6](https://github.com/angular/angular-cli/commit/eaa2378b6bc69a2485cce742ef95b0b94ae994c6) | fix(@angular/cli): handle `YARN_` environment variables during `ng update` and `ng add` | +| [4b9a41bde](https://github.com/angular/angular-cli/commit/4b9a41bdedcdc4e115e8956d31126c5bf6f442ca) | fix(@angular/cli): handle NPM_CONFIG environment variables during ng update and ng add | + +### @angular-devkit/build-angular + +| Commit | Description | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| [04e9ffe4f](https://github.com/angular/angular-cli/commit/04e9ffe4f6b262ce5ef630310bed318e1466d238) | fix(@angular-devkit/build-angular): allow classes with pure annotated static properties to be optimized | +| [6ae17e265](https://github.com/angular/angular-cli/commit/6ae17e26547a0174f7a8910c514016db60fe4c7a) | fix(@angular-devkit/build-angular): dasherize disable-host-check suggestion | + +## Special Thanks: + +Alan Agius, Charles Lyding, Joey Perrott, LeonEck and Mike Jancar + + + + + +# v12.2.0-next.2 (2021-07-14) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.2.0-next.2)

Commit + Description + Notes +
+ + + silence Sass compiler warnings from 3rd party stylesheets + + [Closes #21235]
+
+ +
+ + + force linker `sourceMapping` option to false. + + [Closes #21271]
+
+ +
+ + + ensure `NG_PERSISTENT_BUILD_CACHE` always creates a cache in the specified cache directory +
+ + + fail browser build when index generation fails +
+ + + fix issue were `@media all` causing critical CSS inling to fail + + [Closes #20804]
+
+ +
+ + + `extractLicenses` didn't have an effect when using server builder +
+ + + display incompatibility errors + + [Closes #21322]
+
+ +
+ + + limit advanced terser passes to two +
+ + + exclude `outputPath` from persistent build cache key + + [Closes #21275]
+
+ +
+ + + use `esbuild` as a CSS optimizer for component styles +

@ngtools/webpack (12.2.0-next.2)

Commit + Description + Notes +
+ + + only track file dependencies + + [Closes #21228]
+
+ +
+ + + allow generated assets of Angular component resources +
+ + + avoid non-actionable template type-checker syntax diagnostics +

@schematics/angular (12.2.0-next.2)

Commit + Description + Notes +
+ + + destroy test module after every test + + [Closes #21280]
+
+ +
+ + + remove unsafe any usage in application spec file +
+ + + replace interactive `div` with `button` in application component template +
+ + + use stricter semver for `karma-jasmine-html-reporter` +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott + + + + + +# v12.1.2 (2021-07-14) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.2)

Commit + Description + Notes +
+ + + silence Sass compiler warnings from 3rd party stylesheets + + [Closes #21235]
+
+ +
+ + + ensure `NG_PERSISTENT_BUILD_CACHE` always creates a cache in the specified cache directory +
+ + + force linker `sourceMapping` option to false. + + [Closes #21271]
+
+ +
+ + + fail browser build when index generation fails +
+ + + `extractLicenses` didn't have an effect when using server builder +
+ + + fix issue were `@media all` causing critical CSS inling to fail + + [Closes #20804]
+
+ +
+ + + display incompatibility errors + + [Closes #21322]
+
+ +
+ + + exclude `outputPath` from persistent build cache key + + [Closes #21275]
+
+ +

@ngtools/webpack (12.1.2)

Commit + Description + Notes +
+ + + only track file dependencies + + [Closes #21228]
+
+ +
+ + + allow generated assets of Angular component resources +
+ + + avoid non-actionable template type-checker syntax diagnostics +

@schematics/angular (12.1.2)

Commit + Description + Notes +
+ + + remove unsafe any usage in application spec file +
+ + + replace interactive `div` with `button` in application component template +
+ + + use stricter semver for `karma-jasmine-html-reporter` +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott, Terence D. Honles + + + + + +# v12.1.1 (2021-07-01) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.1)

Commit + Description + Notes +
+ + + handle `ENOENT` and `ENOTDIR` errors when deleting outputs + + [Closes #21202]
+
+ +
+ + + downlevel `for await...of` when targeting ES2018+ + + [Closes #21196]
+
+ +
+ + + configure webpack target in common configuration + + [Closes #21239]
+
+ +
+ + + update `mini-css-extract-plugin` to `1.6.2` +
+ + + update `webpack` to `5.41.1` +

@angular/cli (12.1.1)

Commit + Description + Notes +
+ + + disable update notifier when retrieving package manager version during `ng version` + + [Closes #21172]
+
+ +

@ngtools/webpack (12.1.1)

Commit + Description + Notes +
+ + + encode component style data + + [Closes #21236]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Doug Parker + + + + + +# v12.2.0-next.1 (2021-07-01) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.2.0-next.1)

Commit + Description + Notes +
+ + + add support to inline Adobe Fonts + + [Closes #21186]
+
+ +
+ + + handle `ENOENT` and `ENOTDIR` errors when deleting outputs + + [Closes #21202]
+
+ +
+ + + downlevel `for await...of` when targeting ES2018+ + + [Closes #21196]
+
+ +
+ + + configure webpack target in common configuration + + [Closes #21239]
+
+ +
+ + + use combination of `esbuild` and `terser` as a JavaScript optimizer +
+ + + cache JavaScriptOptimizerPlugin results +

@angular/cli (12.2.0-next.1)

Commit + Description + Notes +
+ + + disable update notifier when retrieving package manager version during `ng version` + + [Closes #21172]
+
+ +

@ngtools/webpack (12.2.0-next.1)

Commit + Description + Notes +
+ + + encode component style data + + [Closes #21236]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Doug Parker + + + + + +# v12.2.0-next.0 (2021-06-24) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0)

Commit + Description + Notes +
+ + + handle ng-packagr errors more gracefully. +
+ + + control linker template sourcemapping via builder sourcemap options +
+ + + parse web-workers in tests when webWorkerTsConfig is defined +

@ngtools/webpack (12.1.0)

Commit + Description + Notes +
+ + + remove no longer needed component styles workaround +

@schematics/angular (12.1.0)

Commit + Description + Notes +
+ + + add 'none' value for the 'style' option of the component schematic +
+ + + display warning during migrations when using third-party builders +
+ + + add devtools to ng new +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Vaibhav Singh, Joey Perrott, twerske, David Scourfield, hien-pham + + + + + +# v12.1.0 (2021-06-24) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0)

Commit + Description + Notes +
+ + + enable webpack Trusted Types support +
+ + + deprecate protractor builder +
+ + + support using TypeScript 4.3 +
+ + + revert open to 8.0.2 + + [Closes #20807]
+
+ +
+ + + correctly ignore inline styles during i18n extraction +
+ + + use the name as chunk filename instead of id +
+ + + handle ng-packagr errors more gracefully. +
+ + + control linker template sourcemapping via builder sourcemap options +
+ + + parse web-workers in tests when webWorkerTsConfig is defined +
+ + + use CSS optimization plugin that leverages workers +
+ + + enable opt-in usage of file system cache +

@angular/cli (12.1.0)

Commit + Description + Notes +
+ + + show Node.js version support status in version command + + [Closes #20879]
+
+ +
+ + + handle unscoped authentication details in `.npmrc` files +
+ + + don't resolve `.npmrc` from parent directories +

@ngtools/webpack (12.1.0)

Commit + Description + Notes +
+ + + support using TypeScript 4.3 +
+ + + remove redundant inline style cache +
+ + + ensure plugin provided Webpack instance is used +
+ + + disable caching for ngcc synchronous Webpack resolver +
+ + + remove no longer needed component styles workaround +

@schematics/angular (12.1.0)

Commit + Description + Notes +
+ + + create new projects with TypeScript 4.3 +
+ + + add migration to replace deprecated `--prod` + + [Closes #21036]
+
+ +
+ + + add 'none' value for the 'style' option of the component schematic +
+ + + display warning during migrations when using third-party builders +
+ + + add devtools to ng new +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Joey Perrott, Bjarki, Vaibhav Singh, twerske, David Scourfield, hien-pham, Alberto Calvo, Paul Gschwendtner, Keen Yee Liau + + + + + +# v12.1.0-next.6 (2021-06-17) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0-next.6)

Commit + Description + Notes +
+ + + don't parse `new Worker` syntax when `webWorkerTsConfig` is not defined in karma builder + + [Closes #21108]
+
+ +
+ + + explicitly set compilation target in test configuration + + [Closes #21111]
+
+ +
+ + + use the name as chunk filename instead of id +
+ + + enable opt-in usage of file system cache +

@angular/cli (12.1.0-next.6)

Commit + Description + Notes +
+ + + handle unscoped authentication details in `.npmrc` files +
+ + + don't resolve `.npmrc` from parent directories +

@schematics/angular (12.1.0-next.6)

Commit + Description + Notes +
+ + + add migration to replace deprecated `--prod` + + [Closes #21036]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Joey Perrott, Alberto Calvo, Charles Lyding + + + + + +# v12.0.5 (2021-06-17) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.5)

Commit + Description + Notes +
+ + + don't parse `new Worker` syntax when `webWorkerTsConfig` is not defined in karma builder + + [Closes #21108]
+
+ +
+ + + explicitly set compilation target in test configuration + + [Closes #21111]
+
+ +

@angular/cli (12.0.5)

Commit + Description + Notes +
+ + + handle unscoped authentication details in .npmrc files +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Joey Perrott + + + + + +# v12.1.0-next.5 (2021-06-10) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0-next.5)

Commit + Description + Notes +
+ + + support using TypeScript 4.3 +
+ + + ensure all Webpack Stats assets are present on rebuilds + + [Closes #21038]
+
+ +
+ + + dispose Sass worker resources on Webpack shutdown + + [Closes #20985]
+
+ +
+ + + show progress during re-builds +
+ + + correctly mark async chunks as non initial in dev-server +
+ + + add web-workers in lazy chunks in stats output + + [Closes #21059]
+
+ +
+ + + styles CSS files not available in unit tests + + [Closes #21054]
+
+ +
+ + + reduce memory usage by cleaning output directory before emitting +

@angular-devkit/schematics (12.1.0-next.5)

Commit + Description + Notes +
+ + + handle updating renamed files + + [Closes #14255]
+
+ + + [Closes #21083]
+
+ +

@angular/cli (12.1.0-next.5)

Commit + Description + Notes +
+ + + avoid shell exec when bootstrapping update command +
+ + + correctly redirect nested Angular schematic dependency requests + + [Closes #21075]
+
+ +

@ngtools/webpack (12.1.0-next.5)

Commit + Description + Notes +
+ + + support using TypeScript 4.3 +
+ + + ensure plugin provided Webpack instance is used +
+ + + disable caching for ngcc synchronous Webpack resolver +

@schematics/angular (12.1.0-next.5)

Commit + Description + Notes +
+ + + create new projects with TypeScript 4.3 +
+ + + added webWorkerTsConfig into test option +
+ + + working with formatting +
+ +--- + +--- + +# Special Thanks + +Charles Lyding, Alan Agius, Doug Parker, Santosh Mahto, Joey Perrott + + + + + +# v12.0.4 (2021-06-09) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.4)

Commit + Description + Notes +
+ + + ensure all Webpack Stats assets are present on rebuilds + + [Closes #21038]
+
+ +
+ + + dispose Sass worker resources on Webpack shutdown + + [Closes #20985]
+
+ +
+ + + show progress during re-builds +
+ + + correctly mark async chunks as non initial in dev-server +
+ + + add web-workers in lazy chunks in stats output + + [Closes #21059]
+
+ +
+ + + styles CSS files not available in unit tests + + [Closes #21054]
+
+ +
+ + + reduce memory usage by cleaning output directory before emitting +

@angular-devkit/schematics (12.0.4)

Commit + Description + Notes +
+ + + handle updating renamed files + + [Closes #14255]
+
+ + + [Closes #21083]
+
+ +

@angular/cli (12.0.4)

Commit + Description + Notes +
+ + + avoid shell exec when bootstrapping update command +
+ + + correctly redirect nested Angular schematic dependency requests + + [Closes #21075]
+
+ +

@ngtools/webpack (12.0.4)

Commit + Description + Notes +
+ + + ensure plugin provided Webpack instance is used +

@schematics/angular (12.0.4)

Commit + Description + Notes +
+ + + added webWorkerTsConfig into test option +
+ + + working with formatting +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Santosh Mahto, Joey Perrott, Doug Parker + + + + + +# v12.0.3 (2021-06-02) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.3)

Commit + Description + Notes +
+ + + do not resolve web-workers in server builds + + [Closes #20877]
+
+ +
+ + + provided earlier build feedback in console + + [Closes #20957]
+
+ +
+ + + correctly ignore inline styles during i18n extraction + + [Closes #20968]
+
+ +
+ + + update `license-webpack-plugin` to `2.3.19` +

@angular-devkit/build-webpack (0.1200.3)

Commit + Description + Notes +
+ + + include only required stats in webpackStats +

@angular-devkit/core (12.0.3)

Commit + Description + Notes +
+ + + show allowed enum values when validation on enum fails +
+ + + handle complex smart defaults in schemas +
+ + + handle async schema validations +
+ + + transform path using getSystemPath for NodeJsAsyncHost's `exists` method +

@angular/cli (12.0.3)

Commit + Description + Notes +
+ + + update supported range of node versions to be less restrictive + + [Closes #20796]
+
+ +

@ngtools/webpack (12.0.3)

Commit + Description + Notes +
+ + + normalize paths when adding file dependencies + + [Closes #20891]
+
+ +
+ + + remove redundant inline style cache +

@schematics/angular (12.0.3)

Commit + Description + Notes +
+ + + make version 12 workspace config migration idempotent + + [Closes #20979]
+
+ +
+ + + show better error when non existing project is passed to the component schematic +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Doug Parker, Charles Lyding, why520crazy + + + + + +# v12.1.0-next.4 (2021-06-02) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0-next.4)

Commit + Description + Notes +
+ + + do not resolve web-workers in server builds + + [Closes #20877]
+
+ +
+ + + provided earlier build feedback in console + + [Closes #20957]
+
+ +
+ + + correctly ignore inline styles during i18n extraction +

@angular-devkit/build-webpack (0.1201.0-next.4)

Commit + Description + Notes +
+ + + include only required stats in webpackStats +

@angular-devkit/core (12.1.0-next.4)

Commit + Description + Notes +
+ + + show allowed enum values when validation on enum fails +
+ + + handle complex smart defaults in schemas +
+ + + handle async schema validations +
+ + + transform path using getSystemPath for NodeJsAsyncHost's `exists` method +

@angular/cli (12.1.0-next.4)

Commit + Description + Notes +
+ + + update supported range of node versions to be less restrictive + + [Closes #20796]
+
+ +

@ngtools/webpack (12.1.0-next.4)

Commit + Description + Notes +
+ + + normalize paths when adding file dependencies + + [Closes #20891]
+
+ +
+ + + remove redundant inline style cache +

@schematics/angular (12.1.0-next.4)

Commit + Description + Notes +
+ + + make version 12 workspace config migration idempotent + + [Closes #20979]
+
+ +
+ + + show better error when non existing project is passed to the component schematic +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Doug Parker, Charles Lyding, why520crazy + + + + + +# v12.1.0-next.3 (2021-05-26) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0-next.3)

Commit + Description + Notes +
+ + + enable webpack Trusted Types support +
+ + + deprecate protractor builder +
+ + + ensure Sass worker implementation supports Node.js 12.14 +
+ + + don't add `.hot-update.js` script tags + + [Closes #20855]
+
+ +
+ + + correctly generate ServiceWorker config on Windows + + [Closes #20894]
+
+ +
+ + + ensure latest inline stylesheet data is used during rebuilds +
+ + + allow i18n extraction on application that uses web-workers + + [Closes #20930]
+
+ +
+ + + hide stacktraces from dart-sass errors +
+ + + resolve absolute outputPath properly + + [Closes #20935]
+
+ +
+ + + show `--disable-host-check` warning only when not using `disableHostCheck` + + [Closes #20951]
+
+ +
+ + + disable CSS optimization parallelism for components styles + + [Closes #20883]
+
+ +
+ + + load postcss-preset-env configuration once +

@angular/cli (12.1.0-next.3)

Commit + Description + Notes +
+ + + show Node.js version support status in version command + + [Closes #20879]
+
+ +
+ + + ng update on windows to allow path +

@ngtools/webpack (12.1.0-next.3)

Commit + Description + Notes +
+ + + re-emit component stylesheet assets + + [Closes #20882]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Bjarki, Hassan Sani, JoostK, George Kalpakas, Joey Perrott + + + + + +# v12.0.2 (2021-05-26) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.2)

Commit + Description + Notes +
+ + + ensure Sass worker implementation supports Node.js 12.14 +
+ + + don't add `.hot-update.js` script tags + + [Closes #20855]
+
+ +
+ + + correctly generate ServiceWorker config on Windows + + [Closes #20894]
+
+ +
+ + + ensure latest inline stylesheet data is used during rebuilds +
+ + + allow i18n extraction on application that uses web-workers + + [Closes #20930]
+
+ +
+ + + hide stacktraces from dart-sass errors +
+ + + resolve absolute outputPath properly + + [Closes #20935]
+
+ +
+ + + show `--disable-host-check` warning only when not using `disableHostCheck` + + [Closes #20951]
+
+ +
+ + + update PostCSS to 8.3 +
+ + + disable CSS optimization parallelism for components styles + + [Closes #20883]
+
+ +
+ + + load postcss-preset-env configuration once +

@angular/cli (12.0.2)

Commit + Description + Notes +
+ + + ng update on windows to allow path +

@ngtools/webpack (12.0.2)

Commit + Description + Notes +
+ + + re-emit component stylesheet assets + + [Closes #20882]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Doug Parker, Hassan Sani, JoostK, George Kalpakas, Joey Perrott + + + + + +# v12.0.1 (2021-05-19) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.1)

Commit + Description + Notes +
+ + + add experimental web-assembly + + [Closes #20762]
+
+ +
+ + + fix error with inline styles when running extract-i18n +
+ + + add `NG_BUILD_MAX_WORKERS` settimgs to control maximum number of workers +
+ + + non injected styles should not count as initial + + [Closes #20781]
+
+ +
+ + + revert open to 8.0.2 + + [Closes #20807]
+
+ +
+ + + correctly resolve babel runtime helpers + + [Closes #20800]
+
+ +
+ + + compile schema in synchronously + + [Closes #20847]
+
+ +
+ + + execute dart-sass in a worker +
+ + + reduce JSON stats +
+ + + use CSS optimization plugin that leverages workers +
+ + + render Sass using a pool of workers +
+ + + clean no-longer used assets during builds +

@angular/cli (12.0.1)

Commit + Description + Notes +
+ + + cannot locate bin for temporary package +
+ + + clean node modules directory prior to updating +
+ + + improve `--prod` deprecation warning + + [Closes #20806]
+
+ +

@ngtools/webpack (12.0.1)

Commit + Description + Notes +
+ + + reduce non-watch mode TypeScript diagnostic analysis overhead +

@schematics/angular (12.0.1)

Commit + Description + Notes +
+ + + remove --prod option from README template +
+ + + don't add `skipTest` option to module schematic options + + [Closes #20811]
+
+ +
+ + + add migration to remove `skipTests` from `@schematics/angular:module` + + [Closes #20848]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott, Keen Yee Liau, Luca Vazzano, Pankaj Patil, Ryan Lester, Terence D. Honles, Alan Cohen + + + + + +# v12.1.0-next.2 (2021-05-19) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.1.0-next.2)

Commit + Description + Notes +
+ + + add experimental web-assembly + + [Closes #20762]
+
+ +
+ + + add `NG_BUILD_MAX_WORKERS` settimgs to control maximum number of workers +
+ + + non injected styles should not count as initial + + [Closes #20781]
+
+ +
+ + + revert open to 8.0.2 + + [Closes #20807]
+
+ +
+ + + correctly resolve babel runtime helpers + + [Closes #20800]
+
+ +
+ + + compile schema in synchronously + + [Closes #20847]
+
+ +
+ + + execute dart-sass in a worker +
+ + + reduce JSON stats +
+ + + use CSS optimization plugin that leverages workers +
+ + + render Sass using a pool of workers +
+ + + clean no-longer used assets during builds +

@angular/cli (12.1.0-next.2)

Commit + Description + Notes +
+ + + cannot locate bin for temporary package +
+ + + clean node modules directory prior to updating +
+ + + improve `--prod` deprecation warning + + [Closes #20806]
+
+ +

@ngtools/webpack (12.1.0-next.2)

Commit + Description + Notes +
+ + + reduce non-watch mode TypeScript diagnostic analysis overhead +

@schematics/angular (12.1.0-next.2)

Commit + Description + Notes +
+ + + remove --prod option from README template +
+ + + don't add `skipTest` option to module schematic options + + [Closes #20811]
+
+ +
+ + + add migration to remove `skipTests` from `@schematics/angular:module` + + [Closes #20848]
+
+ +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott, Keen Yee Liau, Luca Vazzano, Pankaj Patil, Ryan Lester, Alan Cohen, Paul Gschwendtner + + + + + +# v12.0.0 (2021-05-12) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/architect (0.1200.0)

Commit + Description + Notes +
+ + + add implementation for defaultConfiguration +

@angular-devkit/build-angular (12.0.0)

Commit + Description + Notes +
+ + + add `postcss-preset-env` with stage 3 features +
+ + + drop support for karma version 5.2 +
+ + + drop support for ng-packagr version 11 +
+ + + enable inlineCritical by default +
+ + + show warning during build when project requires IE 11 support +
+ + + expose legacy-migrate message format +
+ + + integrate JIT mode linker + + [Closes #20281]
+
+ +
+ + + upgrade to Webpack 5 throughout the build system +
+ + + support processing component inline CSS styles +
+ + + support specifying stylesheet language for inline component styles +
+ + + remove left-over `experimentalRollupPass` option +
+ + + support writing large Webpack stat outputs +
+ + + ensure output directory is present before writing stats JSON +
+ + + remove deprecated View Engine support for i18n extraction +
+ + + remove usage of deprecated View Engine compiler +
+ + + remove deprecated i18nLocale and i18nFormat options from i18n-extract +
+ + + update karma builder to use non-deprecated API +
+ + + disable webpack cache when using `NG_BUILD_CACHE` +
+ + + remove duplicate application bundle generation complete message +
+ + + mark programmatic builder execution functions as experimental +
+ + + avoid double build optimizer processing +
+ + + replace Webpack 4 `hashForChunk` hook usage +
+ + + use new Webpack watch API in karma webpack plugin +
+ + + recover from CSS optimization errors +
+ + + disable Webpack 5 automatic public path support +
+ + + always inject live reload client when using live reload +
+ + + change several builder options defaults +
+ + + show warning when using stylus +
+ + + avoid triggering file change after file build +
+ + + remove left-over `forkTypeChecker` option +
+ + + disable CSS declaration sorting optimizations + + [Closes #20693]
+
+ +
+ + + disable `showCircularDependencies` by default +
+ + + use Webpack's GC memory caching in watch mode +
+ + + improve incremental time during Karma tests +
+ + + avoid async downlevel for known ES2015 code +

@angular-devkit/build-optimizer (0.1200.0)

Commit + Description + Notes +
+ + + support Webpack 5 +

@angular-devkit/build-webpack (0.1200.0)

Commit + Description + Notes +
+ + + provide output path in builder results +
+ + + support Webpack 5 +

@angular-devkit/core (12.0.0)

Commit + Description + Notes +
+ + + add handling for `defaultConfiguration` target definition property +
+ + + update schema validator +
+ + + ensure job input values are processed in order +
+ + + improve handling of set schema values + + [Closes #20594]
+
+ +

@angular/cli (12.0.0)

Commit + Description + Notes +
+ + + add `defaultConfiguration` property to architect schema +
+ + + deprecate `--prod` command line argument +
+ + + confirm ng add action before installation +
+ + + support TypeScript 4.2 +
+ + + ensure odd number Node.js version message is a warning +
+ + + remove npm 7 incompatibility notification +
+ + + avoid exceptions for expected errors in architect commands +
+ + + ensure update migrations are fully executed +
+ + + exclude deprecated packages with removal migrations from update +
+ + + add message update updating from non LTS versions of the CLI +
+ + + ignore `tsickle` during updates +
+ + + run all migrations when updating from or between prereleases +
+ + + add package manager name and version in `ng version` output +
+ + + Support XDG Base Directory Specification +
+ + + don't display options multiple times in schematics help output +
+ + + change package installation to async +
+ + + infer schematic defaults correctly when using `--project` + + [Closes #20666]
+
+ +
+ + + propagate update's force option to package managers +
+ + + allow unsetting config when value is `undefined` +
+ + + allow config object to be of JSON. +
+ + + disallow additional properties in builders sections +

@ngtools/webpack (12.0.0)

Commit + Description + Notes +
+ + + support Webpack 5 +
+ + + drop support for string based lazy loading +
+ + + support multiple plugin instances per compilation +
+ + + support generating data URIs for inline component styles in JIT +
+ + + support processing inline component styles in AOT +
+ + + remove Webpack 5 deprecation warning in resource loader +
+ + + use correct Webpack asset stage in resource loader +
+ + + remove Webpack plugin for deprecated ViewEngine compiler +
+ + + only track actual resource file dependencies +
+ + + avoid adding transitive dependencies to Webpack's dependency graph +
+ + + use precalculated dependencies in unused file check +
+ + + only check affected files for Angular semantic diagnostics +
+ + + cache results of processed inline resources +
+ + + rebuild Angular required files asynchronously +
+ + + reduce source file and Webpack module iteration +

@schematics/angular (12.0.0)

Commit + Description + Notes +
+ + + add migration to remove deprecated options from 'angular.json' +
+ + + strict mode by default +
+ + + use new zone.js entry-points +
+ + + add migration to use new zone.js entry-points +
+ + + add migration to remove emitDecoratorMetadata +
+ + + augment `universal` schematics to import `platform-server` shims + + [Closes #40559]
+
+ +
+ + + update new project dependencies version + + [Closes #20106]
+
+ +
+ + + production builds by default +
+ + + deprecate `legacyBrowsers` application and ng-new option +
+ + + add migration to remove `lazyModules` configuration option +
+ + + add migration to update lazy loading string syntax to use dynamic imports +
+ + + update several TypeScript compilation target (Syntax) +
+ + + remove tslint and codelyzer from new projects + + [Closes #20105]
+
+ + + [Closes #18465]
+
+ +
+ + + add production by default optional migration +
+ + + update new workspaces to use Karma 6.3 +
+ + + remove `entryComponent` from `component` schematic +
+ + + configure new libraries to be published in Ivy partial mode +
+ + + update `jasmine-spec-reporter` to version 7 +
+ + + migrate web workers to support Webpack 5 +
+ + + only update removed v12 options in migration +
+ + + add `additionalProperties` to all schemas +
+ + + remove references to the prod flag +
+ + + only show legacy browsers deprecation warning when option is used +
+ + + remove leftover workspace tslint config +
+ + + correctly handle adding multi-line strings to `@NgModule` metadata +
+ + + run update-i18n migration for server builder +
+ + + update web-worker to support Webpack 5 +
+ + + set `inlineStyleLanguage` when application `style` option is used +
+ + + set `inlineStyleLanguage` for universal if present in build options +
+ + + remove jasmine-spec-reporter and ts-node from default workspace +
+ + + remove Protractor from home page +
+ + + remove lint command from package.json + + [Closes #20618]
+
+ +
+ + + fix migration for namedChunks and option +
+ + + add "type" option in enum schematic +
+ + + only run `emitDecoratorMetadata` removal migration in safe workspaces +
+ + + replace `clientProject` with `project` +
+ +--- + +  + +# Breaking Changes + +

+ @schematics/angular: remove `stylus` from `style` options (fd729ac) +

+`styl` (Stylus) is no longer a supported value as `style` in `application`, `component`, `ng-new` schematics. Stylus is not actively maintained and only 0.3% of the Angular CLI users use it. + +(cherry picked from commit 0272fc55b67d1a3f986b996c8eb21aea31eedf51) + +

+ @angular-devkit/build-angular: change several builder options defaults (656f8d7) +

+A number of browser and server builder options have had their default values changed. The aim of these changes is to reduce the configuration complexity and support the new "production builds by default" initiative. + +**Browser builder** +| Option | Previous default value | New default value | +|----------------------------------------|---------------------------|-------------------| +| optimization | false | true | +| aot | false | true | +| buildOptimizer | false | true | +| sourceMap | true | false | +| extractLicenses | false | true | +| namedChunks | true | false | +| vendorChunk | true | false | + +**Server builder** +| Option | Previous default value | New default value | +|---------------|------------------------|-------------------| +| optimization | false | true | +| sourceMap | true | false | + +(cherry picked from commit 0a74d0d28daf68510459ed73ef048c91bfcabbbc) + +

+ @angular-devkit/core: update schema validator (0875313) +

+support for JSON Schema draft-04 and draft-06 is removed. If you have schemas using the `id` keyword replace them with `$id`. For an interim period we will auto rename any top level `id` keyword to `$id`. + +**NB**: This change only effects schematics and builders authors. + +

+ @angular-devkit/build-angular: upgrade to Webpack 5 throughout the build system (d883ce5) +

+Webpack 5 lazy loaded file name changes +Webpack 5 generates similar but differently named files for lazy loaded JavaScript files in development configurations (when the `namedChunks` option is enabled). +For the majority of users this change should have no effect on the application and/or build process. Production builds should also not be affected as the `namedChunks` option is disabled by default in production configurations. +However, if a project's post-build process makes assumptions as to the file names then adjustments may need to be made to account for the new naming paradigm. +Such post-build processes could include custom file transformations after the build, integration into service-side frameworks, or deployment procedures. +Example development file name change: `lazy-lazy-module.js` --> `src_app_lazy_lazy_module_ts.js` + +Webpack 5 now includes web worker support. However, the structure of the URL within the `Worker` constructor must be in a specific format that differs from the current requirement. +Web worker usage should be updated as shown below (where `./app.worker` should be replaced with the actual worker name): +Before: `new Worker('./app.worker', ...)` +After: `new Worker(new URL('./app.worker', import.meta.url), ...)` + +

+ @ngtools/webpack: remove Webpack plugin for deprecated ViewEngine compiler (160102a) +

+Removal of View Engine support from application builds +With the removal of the deprecated View Engine compiler in Angular version 12 for applications, the View Engine Webpack plugin has been removed. +The Ivy-based Webpack plugin is the default used within the Angular CLI. +If using a custom standalone Webpack configuration, the removed `AngularCompilerPlugin` should be replaced with the Ivy-based `AngularWebpackPlugin`. + +

+ @angular-devkit/build-angular: remove deprecated i18n options from server and browser builder (5cf9a08) +

+Removal of deprecated browser and server command options. +- `i18nFile`, use `locales` object in the project metadata instead. +- `i18nFormat`, No longer needed as the format will be determined automatically. +- `i18nLocale`, use `localize` option instead. + +

+ @angular-devkit/build-angular: remove deprecated i18nLocale and i18nFormat options from i18n-extract (eca5a01) +

+Removal of deprecated `extract-i18n` command options +The deprecated `i18nLocale` option has been removed and the `i18n.sourceLocale` within a project's configuration should be used instead. +The deprecated `i18nFormat` option has been removed and the `format` option should be used instead. + +

+ @angular-devkit/build-angular: remove usage of deprecated View Engine compiler (677913f) +

+Removal of View Engine support from application builds +With the removal of the deprecated View Engine compiler in Angular version 12 for applications, Ivy-based compilation will always be used when building an application. +The default behavior for applications is to use the Ivy compiler when building and no changes are required for these applications. +For applications that have opted-out of Ivy, a warning will be shown and an Ivy-based build will be attempted. If the build fails, +the application may need to be updated to become Ivy compatible. + +

+ @schematics/angular: remove `entryComponent` from `component` schematic (8582ddc) +

+`entryComponent` option has been removed from the `component` schematic as this was intended to be used with the now no longer supported ViewEngine rendering engine. + +

+ @angular-devkit/build-angular: remove view engine app-shell generation (1c2aeeb) +

+App-shell builder now only supports generation using Ivy + +

+ @angular-devkit/build-angular: remove deprecated View Engine support for i18n extraction (012700a) +

+Removal of View Engine support from i18n extraction +With the removal of the deprecated View Engine compiler in Angular version 12 for applications, the `ng extract-i18n` command will now always use the Ivy compiler. +The `--ivy` option has also been removed as Ivy-based extraction is always enabled. +The default behavior for applications is to use the Ivy compiler for building/extraction and no changes are required for these applications. +For applications that have opted-out of Ivy, a warning will be shown and Ivy-based extraction will be attempted. If the extraction fails, +the application may need to be updated to become Ivy compatible. + +

+ @angular/cli: confirm ng add action before installation (985dc1a) +

+The `ng add` command will now ask the user to confirm the package and version prior to installing and executing an uninstalled package. +This new behavior allows a user to abort the action if the version selected is not appropriate or if a typo occurred on the command line and an incorrect package would be installed. +A `--skip-confirmation` option has been added to skip the prompt and directly install and execute the package. This option is useful in CI and non-TTY scenarios such as automated scripts. + +

+ @angular-devkit/build-angular: remove deprecated `lazyModules` option (8d66912) +

+Server and Browser builder `lazyModules` option has been removed without replacement. + +

+ @ngtools/webpack: drop support for string based lazy loading (0dc7327) +

+With this change we drop support for string based lazy loading `./lazy.module#LazyModule` use dynamic imports instead. + +The following options which were used to support the above syntax were removed without replacement. + +- discoverLazyRoutes +- additionalLazyModules +- additionalLazyModuleResources +- contextElementDependencyConstructor + +

+ @angular-devkit/build-angular: enable inlineCritical by default (aa3ea88) +

+Critical CSS inlining is now enabled by default. If you wish to turn this off set `inlineCritical` to `false`. + +See: https://angular.dev/reference/configs/workspace-config#optimization-configuration + +

+ @angular-devkit/build-angular: drop support for zone.js 0.10 (f309516) +

+Minimum supported `zone.js` version is `0.11.4` + +

+ @angular-devkit/build-angular: drop support for ng-packagr version 11 (44e75be) +

+Minimum supported `ng-packagr` version is `12.0.0-next` + +

+ @angular-devkit/build-angular: drop support for karma version 5.2 (fa5cf53) +

+Minimum supported `karma` version is `6.0.0` + +

+ set minimum Node.js version to 12.13 (d1f6169) +

+Node.js version 10 will become EOL on 2021-04-30. +Angular CLI 12 will require Node.js 12.13+ or 14.15+. Node.js 12.13 and 14.15 are the first LTS releases for their respective majors. + +

+ @angular-devkit/build-angular: remove file-loader dependency (6732294) +

+The unsupported/undocumented, Webpack specific functionality to `import`/`require()` a non-module file has been removed. + +Before + +```js +import img from './images/asset.png'; +``` + +After + +```html + +``` + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Keen Yee Liau, Joey Perrott, Doug Parker, Cédric Exbrayat, Douglas Parker, George Kalpakas, Sam Bulatov, Joshua Chapman, Santosh Yadav, David Shevitz, Kristiyan Kostadinov + + + + + +# v12.0.0-rc.3 (2021-05-10) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular/cli (12.0.0-rc.3)

Commit + Description + Notes +
+ + + propagate update's force option to package managers +
+ + + allow unsetting config when value is `undefined` +
+ + + allow config object to be of JSON. +
+ + + disallow additional properties in builders sections +
+ +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott + + + + + +# v12.0.0-rc.2 (2021-05-05) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.0-rc.2)

Commit + Description + Notes +
+ + + disable CSS declaration sorting optimizations + + [Closes #20693]
+
+ +

@angular/cli (12.0.0-rc.2)

Commit + Description + Notes +
+ + + don't display options multiple times in schematics help output +
+ + + change package installation to async +
+ + + infer schematic defaults correctly when using `--project` + + [Closes #20666]
+
+ +

@ngtools/webpack (12.0.0-rc.2)

Commit + Description + Notes +
+ + + rebuild Angular required files asynchronously +
+ + + reduce source file and Webpack module iteration +

@schematics/angular (12.0.0-rc.2)

Commit + Description + Notes +
+ + + add "type" option in enum schematic +
+ + + only run `emitDecoratorMetadata` removal migration in safe workspaces +
+ + + replace `clientProject` with `project` +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Keen Yee Liau, Sam Bulatov, Doug Parker + + + + + +# v12.0.0-rc.1 (2021-04-28) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.0-rc.1)

Commit + Description + Notes +
+ + + remove left-over `forkTypeChecker` option +
+ + + output webpack-dev-server and webpack-dev-middleware errors +
+ + + improve incremental time during Karma tests +
+ + + avoid async downlevel for known ES2015 code +

@angular-devkit/core (12.0.0-rc.1)

Commit + Description + Notes +
+ + + improve handling of set schema values + + [Closes #20594]
+
+ +

@angular/cli (12.0.0-rc.1)

Commit + Description + Notes +
+ + + add package manager name and version in `ng version` output +
+ + + Support XDG Base Directory Specification +

@schematics/angular (12.0.0-rc.1)

Commit + Description + Notes +
+ + + remove jasmine-spec-reporter and ts-node from default workspace +
+ + + remove Protractor from home page +
+ + + remove lint command from package.json + + [Closes #20618]
+
+ +
+ + + avoid unuse imports for canLoad guard generation +
+ + + fix migration for namedChunks and option +

@angular-devkit/schematics-cli (12.0.0-rc.1)

Commit + Description + Notes +
+ + + accept windows like paths for schematics +
+ +--- + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Joey Perrott, Cédric Exbrayat, Doug Parker, Joshua Chapman, Billy Lando, Santosh Yadav, mzocateli + + + + + +# v12.0.0-rc.0 (2021-04-21) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.0-rc.0)

Commit + Description + Notes +
+ + + avoid double build optimizer processing +
+ + + replace Webpack 4 `hashForChunk` hook usage +
+ + + use new Webpack watch API in karma webpack plugin +
+ + + recover from CSS optimization errors +
+ + + disable Webpack 5 automatic public path support +
+ + + always inject live reload client when using live reload +
+ + + change several builder options defaults +
+ + + show warning when using stylus +
+ + + set Tailwind CSS mode when using Tailwind +
+ + + avoid triggering file change after file build +
+ + + use Webpack's GC memory caching in watch mode +

@angular/cli (12.0.0-rc.0)

Commit + Description + Notes +
+ + + ignore `tsickle` during updates +
+ + + run all migrations when updating from or between prereleases +

@ngtools/webpack (12.0.0-rc.0)

Commit + Description + Notes +
+ + + only track actual resource file dependencies +
+ + + cache results of processed inline resources +

@schematics/angular (12.0.0-rc.0)

Commit + Description + Notes +
+ + + set `inlineStyleLanguage` when application `style` option is used +
+ + + set `inlineStyleLanguage` for universal if present in build options +
+ +--- + +# Breaking Changes + +

+ @schematics/angular: remove `stylus` from `style` options (fd729ac) +

+`styl` (Stylus) is no longer a supported value as `style` in `application`, `component`, `ng-new` schematics. Stylus is not actively maintained and only 0.3% of the Angular CLI users use it. + +(cherry picked from commit 0272fc55b67d1a3f986b996c8eb21aea31eedf51) + +

+ @angular-devkit/build-angular: change several builder options defaults (656f8d7) +

+A number of browser and server builder options have had their default values changed. The aim of these changes is to reduce the configuration complexity and support the new "production builds by default" initiative. + +**Browser builder** +| Option | Previous default value | New default value | +|----------------------------------------|---------------------------|-------------------| +| optimization | false | true | +| aot | false | true | +| buildOptimizer | false | true | +| sourceMap | true | false | +| extractLicenses | false | true | +| namedChunks | true | false | +| vendorChunk | true | false | + +**Server builder** +| Option | Previous default value | New default value | +|---------------|------------------------|-------------------| +| optimization | false | true | +| sourceMap | true | false | + +(cherry picked from commit 0a74d0d28daf68510459ed73ef048c91bfcabbbc) + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Keen Yee Liau, Joey Perrott, David Shevitz + + + + + +# v12.0.0-next.9 (2021-04-14) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (12.0.0-next.9)

Commit + Description + Notes +
+ + + upgrade to Webpack 5 throughout the build system +
+ + + support processing component inline CSS styles +
+ + + support specifying stylesheet language for inline component styles +
+ + + update karma builder to use non-deprecated API +
+ + + disable webpack cache when using `NG_BUILD_CACHE` +
+ + + remove duplicate application bundle generation complete message +
+ + + mark programmatic builder execution functions as experimental +

@angular-devkit/build-webpack (0.1200.0-next.9)

Commit + Description + Notes +
+ + + support Webpack 5 +

@angular-devkit/core (12.0.0-next.9)

Commit + Description + Notes +
+ + + update schema validator +

@angular/cli (12.0.0-next.9)

Commit + Description + Notes +
+ + + add message update updating from non LTS versions of the CLI +

@ngtools/webpack (12.0.0-next.9)

Commit + Description + Notes +
+ + + support multiple plugin instances per compilation +
+ + + support generating data URIs for inline component styles in JIT +
+ + + support processing inline component styles in AOT +

@schematics/angular (12.0.0-next.9)

Commit + Description + Notes +
+ + + configure new libraries to be published in Ivy partial mode +
+ + + update `jasmine-spec-reporter` to version 7 +
+ + + migrate web workers to support Webpack 5 +
+ + + update web-worker to support Webpack 5 +
+ +--- + +# Breaking Changes + +

+ @angular-devkit/core: update schema validator (0875313) +

+support for JSON Schema draft-04 and draft-06 is removed. If you have schemas using the `id` keyword replace them with `$id`. For an interim period we will auto rename any top level `id` keyword to `$id`. + +**NB**: This change only effects schematics and builders authors. + +

+ @angular-devkit/build-angular: upgrade to Webpack 5 throughout the build system (d883ce5) +

+Webpack 5 generates similar but differently named files for lazy loaded JavaScript files in development configurations (when the `namedChunks` option is enabled). +For the majority of users this change should have no effect on the application and/or build process. Production builds should also not be affected as the `namedChunks` option is disabled by default in production configurations. +However, if a project's post-build process makes assumptions as to the file names then adjustments may need to be made to account for the new naming paradigm. +Such post-build processes could include custom file transformations after the build, integration into service-side frameworks, or deployment procedures. +Example development file name change: `lazy-lazy-module.js` --> `src_app_lazy_lazy_module_ts.js` + +

+ @angular-devkit/build-angular: upgrade to Webpack 5 throughout the build system (d883ce5) +

+Webpack 5 now includes web worker support. However, the structure of the URL within the `Worker` constructor must be in a specific format that differs from the current requirement. +Web worker usage should be updated as shown below (where `./app.worker` should be replaced with the actual worker name): + +Before: + +``` +new Worker('./app.worker', ...) +``` + +After: + +``` +new Worker(new URL('./app.worker', import.meta.url), ...) +``` + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Keen Yee Liau, Doug Parker, Douglas Parker + + + + + +# v12.0.0-next.8 (2021-04-07) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.8)

Commit + Description + Notes +
+ + + remove deprecated i18nLocale and i18nFormat options from i18n-extract +

@ngtools/webpack (12.0.0-next.8)

Commit + Description + Notes +
+ + + remove Webpack plugin for deprecated ViewEngine compiler +

@schematics/angular (12.0.0-next.8)

Commit + Description + Notes +
+ + + run update-i18n migration for server builder +
+ +--- + +# Breaking Changes + +

+ @ngtools/webpack: remove Webpack plugin for deprecated ViewEngine compiler (160102a) +

+Removal of View Engine support from application builds +With the removal of the deprecated View Engine compiler in Angular version 12 for applications, the View Engine Webpack plugin has been removed. +The Ivy-based Webpack plugin is the default used within the Angular CLI. +If using a custom standalone Webpack configuration, the removed `AngularCompilerPlugin` should be replaced with the Ivy-based `AngularWebpackPlugin`. + +

+ @angular-devkit/build-angular: remove deprecated i18n options from server and browser builder (5cf9a08) +

+Removal of deprecated browser and server command options. +- `i18nFile`, use `locales` object in the project metadata instead. +- `i18nFormat`, No longer needed as the format will be determined automatically. +- `i18nLocale`, use `localize` option instead. + +

+ @angular-devkit/build-angular: remove deprecated i18nLocale and i18nFormat options from i18n-extract (eca5a01) +

+Removal of deprecated `extract-i18n` command options +The deprecated `i18nLocale` option has been removed and the `i18n.sourceLocale` within a project's configuration should be used instead. +The deprecated `i18nFormat` option has been removed and the `format` option should be used instead. + +--- + +# Special Thanks + +Charles Lyding, Renovate Bot, Alan Agius, Doug Parker, Joey Perrott + + + + + +# v12.0.0-next.7 (2021-04-02) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.7)

Commit + Description + Notes +
+ + + validate scripts and styles bundleName + + [Closes #20360]
+
+ +
+ + + remove deprecated View Engine support for i18n extraction +
+ + + remove usage of deprecated View Engine compiler +

@angular/cli (12.0.0-next.7)

Commit + Description + Notes +
+ + + ensure update migrations are fully executed +
+ + + exclude deprecated packages with removal migrations from update +

@ngtools/webpack (12.0.0-next.7)

Commit + Description + Notes +
+ + + use correct Webpack asset stage in resource loader +
+ + + only check affected files for Angular semantic diagnostics +

@schematics/angular (12.0.0-next.7)

Commit + Description + Notes +
+ + + remove `entryComponent` from `component` schematic +
+ + + correctly handle adding multi-line strings to `@NgModule` metadata +
+ + + explicitly specify ServiceWorker registration strategy +
+ +--- + +# Breaking Changes + +

+ @angular-devkit/build-angular: remove usage of deprecated View Engine compiler (677913f) +

+Removal of View Engine support from application builds +With the removal of the deprecated View Engine compiler in Angular version 12 for applications, Ivy-based compilation will always be used when building an application. +The default behavior for applications is to use the Ivy compiler when building and no changes are required for these applications. +For applications that have opted-out of Ivy, a warning will be shown and an Ivy-based build will be attempted. If the build fails, +the application may need to be updated to become Ivy compatible. + +

+ @schematics/angular: remove `entryComponent` from `component` schematic (8582ddc) +

+`entryComponent` option has been removed from the `component` schematic as this was intended to be used with the now no longer supported ViewEngine rendering engine. + +

+ @angular-devkit/build-angular: remove view engine app-shell generation (1c2aeeb) +

+App-shell builder now only supports generation using Ivy + +

+ @angular-devkit/build-angular: remove deprecated View Engine support for i18n extraction (012700a) +

+Removal of View Engine support from i18n extraction +With the removal of the deprecated View Engine compiler in Angular version 12 for applications, the `ng extract-i18n` command will now always use the Ivy compiler. +The `--ivy` option has also been removed as Ivy-based extraction is always enabled. +The default behavior for applications is to use the Ivy compiler for building/extraction and no changes are required for these applications. +For applications that have opted-out of Ivy, a warning will be shown and Ivy-based extraction will be attempted. If the extraction fails, +the application may need to be updated to become Ivy compatible. + +--- + +# Special Thanks + +Charles Lyding, Alan Agius, Renovate Bot, George Kalpakas, Joey Perrott, Keen Yee Liau + + + + + +# v12.0.0-next.6 (2021-03-24) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.6)

Commit + Description + Notes +
+ + + ensure output directory is present before writing stats JSON +

@schematics/angular (12.0.0-next.6)

Commit + Description + Notes +
+ + + add production by default optional migration +
+ + + update new workspaces to use Karma 6.3 +
+ + + remove leftover workspace tslint config +
+ +--- + +--- + +# Special Thanks + +Renovate Bot, Alan Agius, Charles Lyding, Keen Yee Liau + + + + + +# v12.0.0-next.5 (2021-03-18) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.5)

Commit + Description + Notes +
+ + + expose legacy-migrate message format +
+ + + integrate JIT mode linker + + [Closes #20281]
+
+ +
+ + + display correct filename for bundles that are ES2016+ +
+ + + don't load an input sourcemap from file when using Babel +
+ + + support writing large Webpack stat outputs +
+ + + skip FESM2015 from `async` transformation +
+ + + remove Webpack Stats.toJson usage in analytics plugin +
+ + + remove Webpack Stats.toJson usage in karma plugin +
+ + + enforce Babel not to load sourcemaps from file +
+ + + disable `showCircularDependencies` by default +

@angular-devkit/build-webpack (0.1200.0-next.5)

Commit + Description + Notes +
+ + + provide output path in builder results +

@angular/cli (12.0.0-next.5)

Commit + Description + Notes +
+ + + confirm ng add action before installation +
+ + + support TypeScript 4.2 +
+ + + remove `project` from required properties in ng-packagr schema +

@ngtools/webpack (12.0.0-next.5)

Commit + Description + Notes +
+ + + remove Webpack 5 deprecation warning in resource loader +
+ + + avoid adding transitive dependencies to Webpack's dependency graph +
+ + + use precalculated dependencies in unused file check +

@schematics/angular (12.0.0-next.5)

Commit + Description + Notes +
+ + + update several TypeScript compilation target (Syntax) +
+ + + remove tslint and codelyzer from new projects + + [Closes #20105]
+
+ + + [Closes #18465]
+
+ +
+ + + remove references to the prod flag +
+ + + fix youtube icon margin +
+ + + only show legacy browsers deprecation warning when option is used +
+ + + remove Native value from viewEncapsulation option +
+ + + use title for svg on home page +
+ +--- + +# Breaking Changes + +

+ @angular/cli: confirm ng add action before installation (985dc1a) +

+The `ng add` command will now ask the user to confirm the package and version prior to installing and executing an uninstalled package. +This new behavior allows a user to abort the action if the version selected is not appropriate or if a typo occurred on the command line and an incorrect package would be installed. +A `--skip-confirmation` option has been added to skip the prompt and directly install and execute the package. This option is useful in CI and non-TTY scenarios such as automated scripts. + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Renovate Bot, Doug Parker, Cédric Exbrayat, Kristiyan Kostadinov, Mouad Ennaciri, Omar Hasan + + + + + +# v12.0.0-next.4 (2021-03-10) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/architect (0.1200.0-next.4)

Commit + Description + Notes +
+ + + add implementation for defaultConfiguration +

@angular-devkit/build-angular (0.1200.0-next.4)

Commit + Description + Notes +
+ + + show warning during build when project requires IE 11 support +
+ + + only remove nomodule and defer attributes empty values + + + [Closes #20207]
+
+ +

@angular-devkit/core (12.0.0-next.4)

Commit + Description + Notes +
+ + + add handling for `defaultConfiguration` target definition property +

@angular/cli (12.0.0-next.4)

Commit + Description + Notes +
+ + + deprecate `--prod` command line argument +
+ + + add `defaultConfiguration` property to architect schema +
+ + + avoid exceptions for expected errors in architect commands +
+ + + add ng-packagr builder schema in IDE schema +

@ngtools/webpack (12.0.0-next.4)

Commit + Description + Notes +
+ + + drop support for string based lazy loading +

@schematics/angular (12.0.0-next.4)

Commit + Description + Notes +
+ + + add migration to update lazy loading string syntax to use dynamic imports +
+ + + add migration to remove `lazyModules` configuration option +
+ + + deprecate `legacyBrowsers` application and ng-new option +
+ + + production builds by default +
+ + + add `additionalProperties` to all schemas +
+ +--- + +# Breaking Changes + +

+ @angular-devkit/build-angular: remove deprecated `lazyModules` option (8d66912) +

+Server and Browser builder `lazyModules` option has been removed without replacement. + +

+ @ngtools/webpack: drop support for string based lazy loading (0dc7327) +

+With this change we drop support for string based lazy loading `./lazy.module#LazyModule` use dynamic imports instead. + +The following options which were used to support the above syntax were removed without replacement. + +- discoverLazyRoutes +- additionalLazyModules +- additionalLazyModuleResources +- contextElementDependencyConstructor + +--- + +# Special Thanks + +Alan Agius, Charles Lyding, Renovate Bot, Joey Perrott + + + + + +# v12.0.0-next.3 (2021-03-03) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.3)

Commit + Description + Notes +
+ + + enable inlineCritical by default +
+ + + remove left-over `experimentalRollupPass` option +
+ + + inline critical font-face rules when using crittical css inlining +

@schematics/angular (12.0.0-next.3)

Commit + Description + Notes +
+ + + update ng new links +
+ +--- + +# Breaking Changes + +

+ @angular-devkit/build-angular: enable inlineCritical by default (aa3ea88) +

+Critical CSS inlining is now enabled by default. If you wish to turn this off set `inlineCritical` to `false`. + +See: https://angular.dev/reference/configs/workspace-config#optimization-configuration + +--- + +# Special Thanks + +Renovate Bot, Charles Lyding, Alan Agius, Keen Yee Liau, Douglas Parker, twerske + + + + + +# v12.0.0-next.2 (2021-02-24) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.2)

Commit + Description + Notes +
+ + + only show index and service worker status once +
+ + + disable declaration and declarationMap + + + [Closes #20103]
+
+ +

@angular/cli (12.0.0-next.2)

Commit + Description + Notes +
+ + + remove npm 7 incompatibility notification +

@schematics/angular (12.0.0-next.2)

Commit + Description + Notes +
+ + + update new project dependencies version + + [Closes #20106]
+
+ +
+ + + augment `universal` schematics to import `platform-server` shims + + [Closes #40559]
+
+ +
+ + + add migration to remove emitDecoratorMetadata +
+ +--- + +--- + +# Special Thanks + +Renovate Bot, Charles Lyding, Alan Agius, Doug Parker, Joey Perrott, Jefiozie, George Kalpakas, Keen Yee Liau + + + + + +# v12.0.0-next.1 (2021-02-17) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.1)

Commit + Description + Notes +
+ + + drop support for ng-packagr version 11 +
+ + + drop support for karma version 5.2 +

@angular-devkit/build-optimizer (0.1200.0-next.1)

Commit + Description + Notes +
+ + + support Webpack 5 +

@angular/cli (12.0.0-next.1)

Commit + Description + Notes +
+ + + support update migration packages with no entry points + + + [Closes #20032]
+
+ +
+ + + ensure odd number Node.js version message is a warning +
+ + + improve error logging when resolving update migrations +

@ngtools/webpack (12.0.0-next.1)

Commit + Description + Notes +
+ + + support Webpack 5 +
+ + + normalize paths when pruning AOT rebuild requests +

@schematics/angular (12.0.0-next.1)

Commit + Description + Notes +
+ + + add migration to use new zone.js entry-points +
+ + + use new zone.js entry-points +
+ +--- + +# Breaking Changes + +

+ @angular-devkit/build-angular: drop support for zone.js 0.10 (f309516) +

+Minimum supported `zone.js` version is `0.11.4` + +

+ @angular-devkit/build-angular: drop support for ng-packagr version 11 (44e75be) +

+Minimum supported `ng-packagr` version is `12.0.0-next` + +

+ @angular-devkit/build-angular: drop support for karma version 5.2 (fa5cf53) +

+Minimum supported `karma` version is `6.0.0` + +--- + +# Special Thanks + +Renovate Bot, Alan Agius, Charles Lyding, Keen Yee Liau, Aravind V Nair + + + + + +# v12.0.0-next.0 (2021-02-11) + +# Commits + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

@angular-devkit/build-angular (0.1200.0-next.0)

Commit + Description + Notes +
+ + + add `postcss-preset-env` with stage 3 features +
+ + + ensure i18n extraction sourcemaps are fully configured +
+ + + the root Tailwind configuration file is always picked +
+ + + fixed ignoring of karma plugins config + + + [Closes #19993]
+
+ +

@angular-devkit/core (12.0.0-next.0)

Commit + Description + Notes +
+ + + ensure job input values are processed in order +

@angular/cli (12.0.0-next.0)

Commit + Description + Notes +
+ + + update NPM 7 guidance +

@ngtools/webpack (12.0.0-next.0)

Commit + Description + Notes +
+ + + reduce overhead of Angular compiler rebuild requests +

@schematics/angular (12.0.0-next.0)

Commit + Description + Notes +
+ + + strict mode by default +
+ + + add migration to remove deprecated options from 'angular.json' +
+ + + only update removed v12 options in migration +
+ +--- + +# Breaking Changes + +

+ set minimum Node.js version to 12.13 (d1f6169) +

+Node.js version 10 will become EOL on 2021-04-30. +Angular CLI 12 will require Node.js 12.13+ or 14.15+. Node.js 12.13 and 14.15 are the first LTS releases for their respective majors. + +

+ @angular-devkit/build-angular: remove file-loader dependency (6732294) +

+The unsupported/undocumented, Webpack specific functionality to `import`/`require()` a non-module file has been removed. + +Before + +```js +import img from './images/asset.png'; +``` + +After + +```html + +``` + +--- + +# Special Thanks + +Renovate Bot, Charles Lyding, Alan Agius, Doug Parker, Bruno Baia, Amadou Sall, S. Iftekhar Hossain + +--- + +**Note: For release notes prior to this CHANGELOG see [release notes](https://github.com/angular/angular-cli/releases).** diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..501598396449 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,79 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering a safe and welcoming environment, we as +the Angular team pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity, gender expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Use welcoming and inclusive language +* Respect each other +* Provide and gracefully accept constructive criticism +* Show empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* The use of sexualized language or imagery +* Unwelcome sexual attention or advances +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Angular team are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Angular team have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, and to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies to all Angular communication channels - online or in person, +and it also applies when an individual is representing the project or its community in +public spaces. Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the Angular team at conduct@angular.io. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The Angular team +will maintain confidentiality with regard to the reporter of an incident. +Enforcement may result in an indefinite ban from all official Angular communication +channels, or other actions as deemed appropriate by the Angular team. + +Angular maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Appeal + +If you are banned you may contest the decision. To do so email conduct@angular.io with the subject line "Repeal Ban for {{your name here}}" and body with the responses to the following: + +* Why do you believe you did not violate the Code of Conduct? +* Were other factors involved in this situation the leadership team may have been unaware of? +* Why do you wish to be a part of the Angular community? + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10030edad3c9..06db9756c89d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to angular-cli +# Contributing to Angular DevKit -We would love for you to contribute to angular-cli and help make it even better +We would love for you to contribute to DevKit and help make it even better than it is today! As a contributor, here are the guidelines we would like you to follow: @@ -12,13 +12,28 @@ to follow: - [Coding Rules](#rules) - [Commit Message Guidelines](#commit) - [Signing the CLA](#cla) + - [Updating the Public API](#public-api) ## Code of Conduct Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct][coc]. ## Got a Question or Problem? -If you have questions about how to *use* Angular CLI, please direct them to [StackOverflow][stackoverflow]. Please note that angular-cli is still in early developer preview, and the core team's capacity to answer usage questions is limited. We are also available on [Gitter][gitter]. +Please, do not open issues for the general support questions as we want to keep GitHub issues for +bug reports and feature requests. You've got much better chances of getting your question answered +on [StackOverflow](https://stackoverflow.com/questions/tagged/angular-devkit) where the questions +should be tagged with tag `angular-cli` or `angular-devkit`. + +StackOverflow is a much better place to ask questions since: + +- There are thousands of people willing to help on StackOverflow. +- Questions and answers stay available for public viewing so your question / answer might help someone else. +- StackOverflow's voting system assures that the best answers are prominently visible. + +To save your and our time we will be systematically closing all the issues that are requests for +general support and redirecting people to StackOverflow. + +If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter]. ## Found an Issue? If you find a bug in the source code or a mistake in the documentation, you can help us by @@ -28,8 +43,7 @@ If you find a bug in the source code or a mistake in the documentation, you can ## Want a Feature? You can *request* a new feature by [submitting an issue](#submit-issue) to our [GitHub Repository][github]. If you would like to *implement* a new feature, please submit an issue with -a proposal for your work first, to be sure that we can use it. Angular CLI is in developer preview -and we are not ready to accept major contributions ahead of the full release. +a proposal for your work first, to be sure that we can use it. Please consider what kind of change it is: * For a **Major Feature**, first open an issue and outline your proposal so that it can be @@ -40,24 +54,24 @@ and help you to craft the change so that it is successfully accepted into the pr ## Submission Guidelines ### Submitting an Issue -Before you submit an issue, search the archive, maybe your question was already answered. -If your issue appears to be a bug, and hasn't been reported, open a new issue. -Help us to maximize the effort we can spend fixing issues and adding new -features, by not reporting duplicate issues. Providing the following information will increase the -chances of your issue being dealt with quickly: +Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. + +We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. Having a reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like: + +- version of Angular CLI used +- `angular.json` configuration +- version of Angular DevKit used +- 3rd-party libraries and their versions +- and most importantly - a use-case that fails + +A minimal reproduce scenario using allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. + +We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal repository. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it. -* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps -* **Angular CLI Version** - what version of the CLI is affected (e.g. 0.1.2) -* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you -* **Browsers and Operating System** - is this a problem with all browsers? -* **Reproduce the Error** - provide a live example (using [Plunker][plunker], - [JSFiddle][jsfiddle] or [Runnable][runnable]) or a unambiguous set of steps -* **Related Issues** - has a similar issue been reported before? -* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be - causing the problem (line of code or commit) +Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced. -You can file new issues by providing the above information [here](https://github.com/angular/angular-cli/issues/new). +You can file new issues by selecting from our [new issue templates](https://github.com/angular/angular-cli/issues/new/choose) and filling out the issue template. ### Submitting a Pull Request (PR) @@ -70,12 +84,12 @@ Before you submit your Pull Request (PR) consider the following guidelines: * Make your changes in a new git branch: ```shell - git checkout -b my-fix-branch master + git checkout -b my-fix-branch main ``` * Create your patch, **including appropriate test cases**. * Follow our [Coding Rules](#rules). -* Run the full Angular CLI test suite, as described in the [developer documentation][dev-doc], +* Run the full Angular CLI and DevKit test suite, as described in the [developer documentation][dev-doc], and ensure that all tests pass (coming soon). * Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). Adherence to these conventions @@ -92,18 +106,25 @@ Before you submit your Pull Request (PR) consider the following guidelines: git push origin my-fix-branch ``` -* In GitHub, send a pull request to `angular-cli:master`. +* In GitHub, send a pull request to `angular/angular-cli:main`. * If we suggest changes then: * Make the required updates. - * Re-run the Angular CLI test suites for JS and Dart to ensure tests are still passing. - * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): + * Re-run the Angular DevKit test suites to ensure tests are still passing. +* Once your PR is approved and you are done with any follow up changes: + * Rebase to the current main to pre-emptively address any merge conflicts. ```shell - git rebase master -i + git rebase upstream/main -i git push -f ``` + * Add the `action: merge` label and the correct +[target label](https://github.com/angular/angular/blob/main/docs/TRIAGE_AND_LABELS.md#pr-target) + (if PR author has the project collaborator status, or else the last reviewer + should do this). + * The current caretaker will merge the PR to the target branch(es) within 1-2 + business days. -That's it! Thank you for your contribution! +That's it! 🎉 Thank you for your contribution! #### After your pull request is merged @@ -116,10 +137,10 @@ from the main (upstream) repository: git push origin --delete my-fix-branch ``` -* Check out the master branch: +* Check out the main branch: ```shell - git checkout master -f + git checkout main -f ``` * Delete the local branch: @@ -128,26 +149,25 @@ from the main (upstream) repository: git branch -D my-fix-branch ``` -* Update your master with the latest upstream version: +* Update your local `main` with the latest upstream version: ```shell - git pull --ff upstream master + git pull --ff upstream main ``` ## Coding Rules To ensure consistency throughout the source code, keep these rules in mind as you are working: -* All features or bug fixes **must be tested** by one or more specs (unit-tests). +* All features or bug fixes **must be tested** by one or more specs (unit-tests or e2e-tests). * All public API methods **must be documented**. (Details TBC). * We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at - **100 characters**. An automated formatter is available, see - [DEVELOPER.md](DEVELOPER.md#clang-format). + **100 characters**. ## Commit Message Guidelines We have very precise rules over how our git commit messages can be formatted. This leads to **more readable messages** that are easy to follow when looking through the **project history**. But also, -we use the git commit messages to **generate the Angular CLI change log**. +we use the git commit messages to **generate the Angular DevKit change log**. ### Commit Message Format Each commit message consists of a **header**, a **body** and a **footer**. The header has a special @@ -172,39 +192,94 @@ If the commit reverts a previous commit, it should begin with `revert: `, follow ### Type Must be one of the following: -* **feat**: A new feature -* **fix**: A bug fix -* **docs**: Documentation only changes -* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing - semi-colons, etc) -* **refactor**: A code change that neither fixes a bug nor adds a feature -* **perf**: A code change that improves performance -* **test**: Adding missing tests or correcting existing tests -* **build** Changes that affect the build system, CI configuration or external dependencies (example scopes: gulp, broccoli, npm) -* **chore**: Other changes that don't modify `src` or `test` files +* **build**: Changes to local repository build system and tooling +* **ci**: Changes to CI configuration and CI specific tooling [2] +* **docs**: Changes which exclusively affects documentation. +* **feat**: Creates a new feature [1] +* **fix**: Fixes a previously discovered failure/bug [1] +* **perf**: Improves performance without any change in functionality or API [1] +* **refactor**: Refactor without any change in functionality or API (includes style changes) +* **release**: A release point in the repository [2] +* **test**: Improvements or corrections made to the project's test suite + + +[1] This type MUST have a scope. See the next section for more information.
+[2] This type MUST NOT have a scope. It only applies to general scripts and tooling. ### Scope -The scope could be anything specifying place of the commit change. For example -`Compiler`, `ElementInjector`, etc. +The scope should be the name of the npm package affected as perceived by the person reading changelog generated from the commit messages. + +The following is the list of supported scopes: + +* **@angular/build** +* **@angular/cli** +* **@angular/create** +* **@angular/pwa** +* **@angular/ssr** +* **@angular-devkit/architect** +* **@angular-devkit/architect-cli** +* **@angular-devkit/build-angular** +* **@angular-devkit/build-webpack** +* **@angular-devkit/core** +* **@angular-devkit/schematics** +* **@angular-devkit/schematics-cli** +* **@ngtools/webpack** +* **@schematics/angular** + ### Subject The subject contains succinct description of the change: * use the imperative, present tense: "change" not "changed" nor "changes" * don't capitalize first letter +* be concise and direct * no dot (.) at the end +### Examples +Examples of valid commit messages: + +* `fix(@angular/cli): prevent the flubber from grassing` +* `refactor(@schematics/angular): move all JSON classes together` + +Examples of invalid commit messages: +* `fix(@angular/cli): add a new XYZ command` + + This is a feature, not a fix. +* `ci(@angular/cli): fix publishing workflow` + + The `ci` type cannot have a scope. + ### Body Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior. ### Footer -The footer should contain any information about **Breaking Changes** and is also the place to -reference GitHub issues that this commit **Closes**. +The footer can contain information about breaking changes and deprecations. It is also the place to reference GitHub issues, Jira tickets, and other PRs that are related to this commit or that this commit will close. +For example: -**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this. +``` +BREAKING CHANGE: + + + + +Fixes # +``` -A detailed explanation can be found in this [document][commit-message-format]. +or + +``` +DEPRECATED: + + + + +Closes # +``` + +Breaking Change section should start with the phrase "BREAKING CHANGE: " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions. + +Similarly, a Deprecation section should start with "DEPRECATED: " followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path. ## Signing the CLA @@ -216,15 +291,13 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise [print, sign and one of scan+email, fax or mail the form][corporate-cla]. -[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md +[coc]: https://github.com/angular/code-of-conduct/blob/main/CODE_OF_CONDUCT.md [commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit# -[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html -[dev-doc]: https://github.com/angular/angular/blob/master/DEVELOPER.md -[github]: https://github.com/angular/angular-cli +[corporate-cla]: https://code.google.com/legal/corporate-cla-v1.0.html +[dev-doc]: https://github.com/angular/angular-cli/blob/main/docs/DEVELOPER.md +[GitHub]: https://github.com/angular/angular-cli [gitter]: https://gitter.im/angular/angular-cli -[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html -[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml -[jsfiddle]: http://jsfiddle.net/ -[plunker]: http://plnkr.co/edit -[runnable]: http://runnable.com/ -[stackoverflow]: http://stackoverflow.com/questions/tagged/angular-cli +[individual-cla]: https://code.google.com/legal/individual-cla-v1.0.html +[js-style-guide]: https://google.github.io/styleguide/jsguide.html +[stackoverflow]: https://stackoverflow.com/questions/tagged/angular-devkit + diff --git a/LICENSE b/LICENSE index 13a6fd789f83..48adc1eb1829 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2016 Google, Inc. +Copyright (c) 2010-2025 Google LLC. https://angular.dev/license Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000000..b4bf8a27a1aa --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,173 @@ +"""Rules/toolchains for angular_cli with Bazel.""" + +module( + name = "angular_cli", +) + +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "yq.bzl", version = "0.3.2") +bazel_dep(name = "rules_nodejs", version = "6.6.2") +bazel_dep(name = "aspect_rules_js", version = "2.8.3") +bazel_dep(name = "aspect_rules_ts", version = "3.8.1") +bazel_dep(name = "rules_pkg", version = "1.1.0") +bazel_dep(name = "rules_cc", version = "0.2.15") +bazel_dep(name = "aspect_bazel_lib", version = "2.22.0") +bazel_dep(name = "bazel_skylib", version = "1.9.0") +bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0") +bazel_dep(name = "aspect_rules_jasmine", version = "2.0.2") +bazel_dep(name = "rules_angular") +git_override( + module_name = "rules_angular", + commit = "7133b97252508f8528e5c5818a9a73cacc2e2a0e", + remote = "https://github.com/devversion/rules_angular.git", +) + +bazel_dep(name = "devinfra") +git_override( + module_name = "devinfra", + commit = "942d738d8f4d65b161d06e6c399fefec318cdbfe", + remote = "https://github.com/angular/dev-infra.git", +) + +bazel_dep(name = "rules_sass") +git_override( + module_name = "rules_sass", + commit = "1184a80751a21af8348f308abc5b38a41f26850e", + remote = "https://github.com/devversion/rules_sass.git", +) + +bazel_dep(name = "rules_browsers") +git_override( + module_name = "rules_browsers", + commit = "8ef3e996d5fc040a35770f860987d8b9cad8ef3d", + remote = "https://github.com/devversion/rules_browsers.git", +) + +node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") +node.toolchain(node_version_from_nvmrc = "//:.nvmrc") +use_repo( + node, + "nodejs_darwin_amd64", + "nodejs_darwin_arm64", + "nodejs_linux_amd64", + "nodejs_linux_arm64", + "nodejs_toolchains", + "nodejs_windows_amd64", +) + +node_dev = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node", dev_dependency = True) + +# Node.js 20 +node_dev.toolchain( + name = "node20", + node_version = "20.19.0", +) + +# Node.js 22 +node_dev.toolchain( + name = "node22", + node_version = "22.12.0", +) + +# Node.js 24 +node_dev.toolchain( + name = "node24", + node_version = "24.0.0", +) +use_repo( + node_dev, + "node20_darwin_amd64", + "node20_darwin_arm64", + "node20_linux_amd64", + "node20_linux_arm64", + "node20_toolchains", + "node20_windows_amd64", + "node22_darwin_amd64", + "node22_darwin_arm64", + "node22_linux_amd64", + "node22_linux_arm64", + "node22_toolchains", + "node22_windows_amd64", + "node24_darwin_amd64", + "node24_darwin_arm64", + "node24_linux_amd64", + "node24_linux_arm64", + "node24_toolchains", + "node24_windows_amd64", +) + +pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") +pnpm.pnpm( + name = "pnpm", + pnpm_version = "10.26.0", + pnpm_version_integrity = "sha512-Oz9scl6+cSUGwKsa1BM8+GsfS2h+/85iqbOLTXLjlUJC5kMZD8UfoWQpScc19APevUT1yw7dZXq+Y6i2p+HkAg==", +) +use_repo(pnpm, "pnpm") + +npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm") +npm.npm_translate_lock( + name = "npm", + custom_postinstalls = { + # TODO: Standardize browser management for `rules_js` + "webdriver-manager": "node ./bin/webdriver-manager update --standalone false --gecko false --versions.chrome 106.0.5249.21", + }, + data = [ + "//:package.json", + "//:pnpm-workspace.yaml", + "//modules/testing/builder:package.json", + "//packages/angular/build:package.json", + "//packages/angular/cli:package.json", + "//packages/angular/create/package.json", + "//packages/angular/pwa:package.json", + "//packages/angular/ssr:package.json", + "//packages/angular_devkit/architect:package.json", + "//packages/angular_devkit/architect_cli:package.json", + "//packages/angular_devkit/build_angular:package.json", + "//packages/angular_devkit/build_webpack:package.json", + "//packages/angular_devkit/core:package.json", + "//packages/angular_devkit/schematics:package.json", + "//packages/angular_devkit/schematics_cli:package.json", + "//packages/ngtools/webpack:package.json", + "//packages/schematics/angular:package.json", + "//tests:package.json", + ], + lifecycle_hooks_envs = { + # TODO: Standardize browser management for `rules_js` + "puppeteer": ["PUPPETEER_DOWNLOAD_PATH=./downloads"], + }, + lifecycle_hooks_execution_requirements = { + # Needed for downloading chromedriver. + # Also `update-config` of webdriver manager would store an absolute path; + # which would then break execution. + "webdriver-manager": ["local"], + }, + npmrc = "//:.npmrc", + pnpm_lock = "//:pnpm-lock.yaml", +) +use_repo(npm, "npm") + +rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext") +rules_ts_ext.deps( + name = "angular_cli_npm_typescript", + # Obtained by: npm info typescript@5.9.3 dist.integrity + ts_integrity = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + ts_version = "5.9.3", +) +use_repo(rules_ts_ext, **{"npm_typescript": "angular_cli_npm_typescript"}) + +rules_angular = use_extension("@rules_angular//setup:extensions.bzl", "rules_angular") +rules_angular.setup( + name = "components_rules_angular_configurable_deps", + angular_compiler_cli = "//:node_modules/@angular/compiler-cli", + typescript = "//:node_modules/typescript", +) +use_repo(rules_angular, **{"rules_angular_configurable_deps": "components_rules_angular_configurable_deps"}) + +register_toolchains( + "@devinfra//bazel/git-toolchain:git_linux_toolchain", + "@devinfra//bazel/git-toolchain:git_macos_x86_toolchain", + "@devinfra//bazel/git-toolchain:git_macos_arm64_toolchain", + "@devinfra//bazel/git-toolchain:git_windows_toolchain", + "//tools/toolchains:dummy_cc_windows_no_exec_toolchain", + "//tools/toolchains:node22_windows_no_exec_toolchain", +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000000..1f9bb1615d4e --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,4460 @@ +{ + "lockFileVersion": 24, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.17.1/MODULE.bazel": "9b027af55f619c7c444cead71061578fab6587e5e1303fa4ed61d49d2b1a7262", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "7fe0191f047d4fe4a4a46c1107e2350cbb58a8fc2e10913aa4322d3190dec0bf", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/source.json": "369df5b7f2eae82f200fff95cf1425f90dee90a0d0948122060b48150ff0e224", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.0/MODULE.bazel": "5fef5ec709c837312823f9bcf0f276661e2cb48ad52f17c4e01176bbf1e9bf58", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.0/source.json": "5e42968c6d23ab8bd95c02634b16864d866334347827cb6a8425b86c11cc4363", + "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.2/MODULE.bazel": "45f054400ff242c4433f6d7f20f6123a9a72739cb7a1f44247d738db1644f46c", + "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.2/source.json": "3ed399a5654259a822448f9cdbf21f6c738f16ccd7f89249c7507e374cbdd1e3", + "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", + "https://bcr.bazel.build/modules/aspect_rules_js/2.4.2/MODULE.bazel": "0d01db38b96d25df7ed952a5e96eac4b3802723d146961974bf020f6dd07591d", + "https://bcr.bazel.build/modules/aspect_rules_js/2.6.2/MODULE.bazel": "ed2a871f4ab8fbde0cab67c425745069d84ea64b64313fa1a2954017326511f5", + "https://bcr.bazel.build/modules/aspect_rules_js/2.8.3/MODULE.bazel": "807ce5f624124a8bc586c743394d174e85f0f9c6c4e0e2410b4088aebe790ac8", + "https://bcr.bazel.build/modules/aspect_rules_js/2.8.3/source.json": "c35cb4e04f61a09c17f8c569894b80de884b1e3dee2d33829704786e3f778037", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.3/MODULE.bazel": "d09db394970f076176ce7bab5b5fa7f0d560fd4f30b8432ea5e2c2570505b130", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.7.0/MODULE.bazel": "5aace216caf88638950ef061245d23c36f57c8359e56e97f02a36f70bb09c50f", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.1/MODULE.bazel": "796622c65ae3008374fc2d77c32ddb4ef6da9fe891826ce648f70033a48b3667", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.1/source.json": "a7c4f332f5c21f4e63d073f8dda40bf278d5307499fb307b35058dba558f417a", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.3/MODULE.bazel": "20f53b145f40957a51077ae90b37b7ce83582a1daf9350349f0f86179e19dd0d", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.6/MODULE.bazel": "cafb8781ad591bc57cc765dca5fefab08cf9f65af363d162b79d49205c7f8af7", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "aa975a83e72bcaac62ee61ab12b788ea324a1d05c4aab28aadb202f647881679", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", + "https://bcr.bazel.build/modules/bazel_features/1.34.0/source.json": "dfa5c4b01110313153b484a735764d247fee5624bbab63d25289e43b151a657a", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.0/MODULE.bazel": "2ab127ef8d56a739a99bb2ce00ec4c7d1ecc7977d4370c0ca6efd0d8f03d6d99", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", + "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.2.15/MODULE.bazel": "6a0a4a75a57aa6dc888300d848053a58c6b12a29f89d4304e1c41448514ec6e8", + "https://bcr.bazel.build/modules/rules_cc/0.2.15/source.json": "197965c6dcca5c98a9288f93849e2e1c69d622e71b0be8deb524e22d48c88e32", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", + "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", + "https://bcr.bazel.build/modules/rules_nodejs/6.5.0/MODULE.bazel": "546d0cf79f36f9f6e080816045f97234b071c205f4542e3351bd4424282a8810", + "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", + "https://bcr.bazel.build/modules/rules_nodejs/6.6.2/MODULE.bazel": "9fdb5e1d50246a25761f150fcc820dc47e4052330a8408451e628804f9ca64a6", + "https://bcr.bazel.build/modules/rules_nodejs/6.6.2/source.json": "6e8c1ecc64ff8da147c1620f862ad77d7b19c5d1b52b3aa5e847d5b3d0de4cc3", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453", + "https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.0.0/source.json": "b0162a65c6312e45e7912e39abd1a7f8856c2c7e41ecc9b6dc688a6f6400a917", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", + "https://bcr.bazel.build/modules/tar.bzl/0.7.0/MODULE.bazel": "cc1acd85da33c80e430b65219a620d54d114628df24a618c3a5fa0b65e988da9", + "https://bcr.bazel.build/modules/tar.bzl/0.7.0/source.json": "9becb80306f42d4810bfa16379fb48aad0b01ce5342bc12fe47dcd6af3ac4d7a", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.2.0/MODULE.bazel": "6f3a675677db8885be4d607fde14cc51829715e3a879fb016eb9bf336786ce6d", + "https://bcr.bazel.build/modules/yq.bzl/0.3.2/MODULE.bazel": "0384efa70e8033d842ea73aa4b7199fa099709e236a7264345c03937166670b6", + "https://bcr.bazel.build/modules/yq.bzl/0.3.2/source.json": "c4ec3e192477e154f08769e29d69e8fd36e8a4f0f623997f3e1f6f7d328f7d7d", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": { + "general": { + "bzlTransitiveDigest": "0aod3RK04ALA/OKTExzd7QqyeqcC4O2GWuDwUxhQHp4=", + "usagesDigest": "ToTaCONCN/E05krnHXLM1kpV1zrHNxHrGpUip973II4=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "esbuild_darwin-x64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "integrity": "", + "platform": "darwin-x64" + } + }, + "esbuild_darwin-arm64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "integrity": "", + "platform": "darwin-arm64" + } + }, + "esbuild_linux-x64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "integrity": "", + "platform": "linux-x64" + } + }, + "esbuild_linux-arm64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "integrity": "", + "platform": "linux-arm64" + } + }, + "esbuild_win32-x64": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild:repositories.bzl%esbuild_repositories", + "attributes": { + "esbuild_version": "0.19.9", + "integrity": "", + "platform": "win32-x64" + } + }, + "esbuild_toolchains": { + "repoRuleId": "@@aspect_rules_esbuild+//esbuild/private:toolchains_repo.bzl%toolchains_repo", + "attributes": { + "esbuild_version": "0.19.9", + "user_repository_name": "esbuild" + } + }, + "npm__esbuild_0.19.9": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", + "attributes": { + "package": "esbuild", + "version": "0.19.9", + "root_package": "", + "link_workspace": "", + "link_packages": {}, + "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "url": "", + "commit": "", + "patch_args": [ + "-p0" + ], + "patches": [], + "custom_postinstall": "", + "npm_auth": "", + "npm_auth_basic": "", + "npm_auth_username": "", + "npm_auth_password": "", + "lifecycle_hooks": [], + "extra_build_content": "", + "generate_bzl_library_targets": false, + "extract_full_archive": false, + "exclude_package_contents": [] + } + }, + "npm__esbuild_0.19.9__links": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", + "attributes": { + "package": "esbuild", + "version": "0.19.9", + "dev": false, + "root_package": "", + "link_packages": {}, + "deps": {}, + "transitive_closure": {}, + "lifecycle_build_target": false, + "lifecycle_hooks_env": [], + "lifecycle_hooks_execution_requirements": [ + "no-sandbox" + ], + "lifecycle_hooks_use_default_shell_env": false, + "bins": {}, + "package_visibility": [ + "//visibility:public" + ], + "replace_package": "", + "exclude_package_contents": [] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "aspect_bazel_lib+", + "tar.bzl", + "tar.bzl+" + ], + [ + "aspect_rules_esbuild+", + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_esbuild+", + "aspect_tools_telemetry_report", + "aspect_tools_telemetry++telemetry+aspect_tools_telemetry_report" + ], + [ + "aspect_rules_esbuild+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_rules_js+", + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_js+", + "aspect_tools_telemetry_report", + "aspect_tools_telemetry++telemetry+aspect_tools_telemetry_report" + ], + [ + "aspect_rules_js+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_rules_js+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "tar.bzl+", + "bazel_lib", + "bazel_lib+" + ], + [ + "tar.bzl+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "tar.bzl+", + "tar.bzl", + "tar.bzl+" + ] + ] + } + }, + "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { + "general": { + "bzlTransitiveDigest": "tQ+7EwLfQwqi/T4v5/N3NNHTmP6Wu/FqXxRDndEB2OU=", + "usagesDigest": "fkR8y929BQ1GFezNYBR/HXJUcMa3NtJvhzsZrG8I9vI=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "pnpm": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", + "attributes": { + "package": "pnpm", + "version": "10.26.0", + "root_package": "", + "link_workspace": "", + "link_packages": {}, + "integrity": "sha512-Oz9scl6+cSUGwKsa1BM8+GsfS2h+/85iqbOLTXLjlUJC5kMZD8UfoWQpScc19APevUT1yw7dZXq+Y6i2p+HkAg==", + "url": "", + "commit": "", + "patch_args": [ + "-p0" + ], + "patches": [], + "custom_postinstall": "", + "npm_auth": "", + "npm_auth_basic": "", + "npm_auth_username": "", + "npm_auth_password": "", + "lifecycle_hooks": [], + "extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])", + "generate_bzl_library_targets": false, + "extract_full_archive": true, + "exclude_package_contents": [] + } + }, + "pnpm__links": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", + "attributes": { + "package": "pnpm", + "version": "10.26.0", + "dev": false, + "root_package": "", + "link_packages": {}, + "deps": {}, + "transitive_closure": {}, + "lifecycle_build_target": false, + "lifecycle_hooks_env": [], + "lifecycle_hooks_execution_requirements": [ + "no-sandbox" + ], + "lifecycle_hooks_use_default_shell_env": false, + "bins": {}, + "package_visibility": [ + "//visibility:public" + ], + "replace_package": "", + "exclude_package_contents": [] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "aspect_bazel_lib+", + "tar.bzl", + "tar.bzl+" + ], + [ + "aspect_rules_js+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "aspect_rules_js+", + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_js+", + "aspect_tools_telemetry_report", + "aspect_tools_telemetry++telemetry+aspect_tools_telemetry_report" + ], + [ + "aspect_rules_js+", + "bazel_features", + "bazel_features+" + ], + [ + "aspect_rules_js+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_rules_js+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_features+", + "bazel_features_globals", + "bazel_features++version_extension+bazel_features_globals" + ], + [ + "bazel_features+", + "bazel_features_version", + "bazel_features++version_extension+bazel_features_version" + ], + [ + "bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "tar.bzl+", + "bazel_lib", + "bazel_lib+" + ], + [ + "tar.bzl+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "tar.bzl+", + "tar.bzl", + "tar.bzl+" + ] + ] + } + }, + "@@aspect_rules_ts+//ts:extensions.bzl%ext": { + "general": { + "bzlTransitiveDigest": "7k3bewVApw4Kc6Rpho1Rrs1nrW/5jphUA5Mh1iHE2U4=", + "usagesDigest": "aaqqxEFKCRGFkeAf0pKmXvZZTLGYIk3pQsDFG28ZbNg=", + "recordedFileInputs": { + "@@rules_browsers+//package.json": "84dc1ba9b1c667a25894e97218bd8f247d54f24bb694efb397a881be3c06a4c5" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "angular_cli_npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "5.9.3", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + }, + "rules_angular_npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "5.9.2", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + }, + "npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "5.9.3", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + }, + "npm_rules_browsers_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "", + "version_from": "@@rules_browsers+//:package.json", + "integrity": "", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_rules_ts+", + "aspect_rules_ts", + "aspect_rules_ts+" + ], + [ + "aspect_rules_ts+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { + "general": { + "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", + "usagesDigest": "jHASCmhI+ziv94KZ5hlx6t1ixFDdVXFm2VnOVVbAqww=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "aspect_tools_telemetry_report": { + "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", + "attributes": { + "deps": { + "aspect_rules_js": "2.8.3", + "aspect_rules_ts": "3.8.1", + "aspect_rules_esbuild": "0.25.0", + "aspect_rules_jasmine": "2.0.2", + "aspect_tools_telemetry": "0.3.3" + } + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_tools_telemetry+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_tools_telemetry+", + "bazel_skylib", + "bazel_skylib+" + ] + ] + } + }, + "@@pybind11_bazel+//:python_configure.bzl%extension": { + "general": { + "bzlTransitiveDigest": "c9ZWWeXeu6bctL4/SsY2otFWyeFN0JJ20+ymGyJZtWk=", + "usagesDigest": "fycyB39YnXIJkfWCIXLUKJMZzANcuLy9ZE73hRucjFk=", + "recordedFileInputs": { + "@@pybind11_bazel+//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_python": { + "repoRuleId": "@@pybind11_bazel+//:python_configure.bzl%python_configure", + "attributes": {} + }, + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11.BUILD", + "strip_prefix": "pybind11-2.11.1", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.11.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "pybind11_bazel+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_angular+//setup:extensions.bzl%rules_angular": { + "general": { + "bzlTransitiveDigest": "fkaH7HMicL3g7/NDaFzlq39kcLopMyQ3KdbDn+5CRzA=", + "usagesDigest": "ZinuLP7QHxaW5achD0Vz19qElMu4r2LvGvh96Z5zYlA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "components_rules_angular_configurable_deps": { + "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", + "attributes": { + "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", + "typescript": "@@rules_angular+//:node_modules/typescript" + } + }, + "rules_angular_configurable_deps": { + "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", + "attributes": { + "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", + "typescript": "@@rules_angular+//:node_modules/typescript-local" + } + }, + "dev_infra_rules_angular_configurable_deps": { + "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", + "attributes": { + "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", + "typescript": "@@rules_angular+//:node_modules/typescript" + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_browsers+//browsers:extensions.bzl%browsers": { + "general": { + "bzlTransitiveDigest": "ljZlVgWkQJnI6EvlHVfYit2EttUE52gDTbvmota5YO8=", + "usagesDigest": "FS7q5WaIwg3KirS3njhuPFkTIBYvDaTInVGrlzu0XL8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "rules_browsers_chrome_linux": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "1419fa328bd7ea2697f26412ec693867516e4ef23c32eb13143a0b0b179b604b", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/linux64/chrome-headless-shell-linux64.zip" + ], + "named_files": { + "CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell" + }, + "exclude_patterns": [ + "**/*.log" + ], + "exports_files": [ + "chrome-headless-shell-linux64/chrome-headless-shell" + ] + } + }, + "rules_browsers_chrome_mac": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "792cbf9b77219b4476e41c49647bcd15e55f0988002fa1e4e6a720eb430c7eda", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-x64/chrome-headless-shell-mac-x64.zip" + ], + "named_files": { + "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell" + }, + "exclude_patterns": [ + "**/*.log" + ], + "exports_files": [ + "chrome-headless-shell-mac-x64/chrome-headless-shell" + ] + } + }, + "rules_browsers_chrome_mac_arm": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "f0c1917769775e826dfa69936381d0d95b06fe67cf631ecd842380d5de0e4c7f", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" + ], + "named_files": { + "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell" + }, + "exclude_patterns": [ + "**/*.log" + ], + "exports_files": [ + "chrome-headless-shell-mac-arm64/chrome-headless-shell" + ] + } + }, + "rules_browsers_chrome_win64": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "6ce0f20dd743a804890f45f5349370e1aa7cd3ac3482c04686fcff5fafd01bb3", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/win64/chrome-headless-shell-win64.zip" + ], + "named_files": { + "CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe" + }, + "exclude_patterns": [ + "**/*.log" + ], + "exports_files": [ + "chrome-headless-shell-win64/chrome-headless-shell.exe" + ] + } + }, + "rules_browsers_chromedriver_linux": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "baf4bf9d22881265487732f17d35a49e9aadd0837aa5c1c1eea520c8aa24a97f", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/linux64/chromedriver-linux64.zip" + ], + "named_files": { + "CHROMEDRIVER": "chromedriver-linux64/chromedriver" + }, + "exclude_patterns": [], + "exports_files": [ + "chromedriver-linux64/chromedriver" + ] + } + }, + "rules_browsers_chromedriver_mac": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "87560768d5aa203b37c0a1b8459a35b05e4ece54afee2df530f3bc33de4f63c5", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-x64/chromedriver-mac-x64.zip" + ], + "named_files": { + "CHROMEDRIVER": "chromedriver-mac-x64/chromedriver" + }, + "exclude_patterns": [], + "exports_files": [ + "chromedriver-mac-x64/chromedriver" + ] + } + }, + "rules_browsers_chromedriver_mac_arm": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "99821795fa7c87eb92fb15248e23b237c83f397486d22ad9a10771622c36a5a0", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-arm64/chromedriver-mac-arm64.zip" + ], + "named_files": { + "CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver" + }, + "exclude_patterns": [], + "exports_files": [ + "chromedriver-mac-arm64/chromedriver" + ] + } + }, + "rules_browsers_chromedriver_win64": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "6e180e234a710c3cbf69566f64a662ed85473db6ae82275fd359f80ab288df99", + "urls": [ + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/win64/chromedriver-win64.zip" + ], + "named_files": { + "CHROMEDRIVER": "chromedriver-win64/chromedriver.exe" + }, + "exclude_patterns": [], + "exports_files": [ + "chromedriver-win64/chromedriver.exe" + ] + } + }, + "rules_browsers_firefox_linux": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "00fb922cda6bab971e02bcbfb77923b0a234388ed7d77c23506ca0a1a61d4a86", + "urls": [ + "https://archive.mozilla.org/pub/firefox/releases/145.0/linux-x86_64/en-US/firefox-145.0.tar.xz" + ], + "named_files": { + "FIREFOX": "firefox/firefox" + }, + "exclude_patterns": [], + "exports_files": [ + "firefox/firefox" + ] + } + }, + "rules_browsers_firefox_mac": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "1c4556480deac8424049f3081a6de1e2c6de619bab3e8ce53e5a497b8d6d919e", + "urls": [ + "https://archive.mozilla.org/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.dmg" + ], + "named_files": { + "FIREFOX": "Firefox.app/Contents/MacOS/firefox" + }, + "exclude_patterns": [], + "exports_files": [ + "Firefox.app/Contents/MacOS/firefox" + ] + } + }, + "rules_browsers_firefox_mac_arm": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "1c4556480deac8424049f3081a6de1e2c6de619bab3e8ce53e5a497b8d6d919e", + "urls": [ + "https://archive.mozilla.org/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.dmg" + ], + "named_files": { + "FIREFOX": "Firefox.app/Contents/MacOS/firefox" + }, + "exclude_patterns": [], + "exports_files": [ + "Firefox.app/Contents/MacOS/firefox" + ] + } + }, + "rules_browsers_firefox_win64": { + "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", + "attributes": { + "sha256": "4b0345c113242653d923b369fcbd48e3089c57658f8c1542f887c8a375d50306", + "urls": [ + "https://archive.mozilla.org/pub/firefox/releases/145.0/win64/en-US/Firefox%20Setup%20145.0.exe" + ], + "named_files": { + "FIREFOX": "core/firefox.exe" + }, + "exclude_patterns": [], + "exports_files": [ + "core/firefox.exe" + ] + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { + "general": { + "bzlTransitiveDigest": "WHRlQQnxW7e7XMRBhq7SARkDarLDOAbg6iLaJpk5QYM=", + "usagesDigest": "wy6ISK6UOcBEjj/mvJ/S3WeXoO67X+1llb9yPyFtPgc=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "platforms": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz" + ], + "sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74" + } + }, + "rules_python": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8", + "strip_prefix": "rules_python-0.28.0", + "url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz" + } + }, + "bazel_skylib": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", + "urls": [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" + ] + } + }, + "com_google_absl": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip" + ], + "strip_prefix": "abseil-cpp-20240116.1", + "integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=" + } + }, + "rules_fuzzing_oss_fuzz": { + "repoRuleId": "@@rules_fuzzing+//fuzzing/private/oss_fuzz:repository.bzl%oss_fuzz_repository", + "attributes": {} + }, + "honggfuzz": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@rules_fuzzing+//:honggfuzz.BUILD", + "sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e", + "url": "https://github.com/google/honggfuzz/archive/2.5.zip", + "strip_prefix": "honggfuzz-2.5" + } + }, + "rules_fuzzing_jazzer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", + "attributes": { + "sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2", + "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar" + } + }, + "rules_fuzzing_jazzer_api": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", + "attributes": { + "sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b", + "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_fuzzing+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_nodejs+//nodejs:extensions.bzl%node": { + "general": { + "bzlTransitiveDigest": "NwcLXHrbh2hoorA/Ybmcpjxsn/6avQmewDglodkDrgo=", + "usagesDigest": "S8pbOD3W4rSYjK/dNi5FSVLmT25mLbwVs9g/9fC2SN8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.5", + "node_version_from_nvmrc": "@@//:.nvmrc", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "node20_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "node20_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "node20_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "node20_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "node20_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "node20_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "node20_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "node20_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "20.19.0", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "node20": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "node20" + } + }, + "node20_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "node20" + } + }, + "node20_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "node20" + } + }, + "node22_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "node22_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "node22_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "node22_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "node22_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "node22_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "node22_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "node22_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.12.0", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "node22": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "node22" + } + }, + "node22_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "node22" + } + }, + "node22_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "node22" + } + }, + "node24_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "node24_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "node24_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "node24_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "node24_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "node24_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "node24_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "node24_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": {}, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.0.0", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "node24": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "node24" + } + }, + "node24_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "node24" + } + }, + "node24_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "node24" + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_python+//python/extensions:pip.bzl%pip": { + "general": { + "bzlTransitiveDigest": "39J6fxZx6VyebAMZs6LDsQSGw91SROMECqQx77bSJqE=", + "usagesDigest": "AK1R124YPWwAs8z1CQYyjYuci8RO5Ofot+EP5ZCNQDc=", + "recordedFileInputs": { + "@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5", + "@@rules_fuzzing+//fuzzing/requirements.txt": "ab04664be026b632a0d2a2446c4f65982b7654f5b6851d2f9d399a19b7242a5b", + "@@rules_python+//tools/publish/requirements_darwin.txt": "2994136eab7e57b083c3de76faf46f70fad130bc8e7360a7fed2b288b69e79dc", + "@@rules_python+//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", + "@@rules_python+//tools/publish/requirements_windows.txt": "7673adc71dc1a81d3661b90924d7a7c0fc998cd508b3cb4174337cef3f2de556" + }, + "recordedDirentsInputs": {}, + "envVariables": { + "RULES_PYTHON_REPO_DEBUG": null, + "RULES_PYTHON_REPO_DEBUG_VERBOSITY": null + }, + "generatedRepoSpecs": { + "pip_deps_310_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "pip_deps_310", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_310_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "pip_deps_310", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_311_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "pip_deps_311", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_311_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "pip_deps_311", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_312_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "pip_deps_312", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_312_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "pip_deps_312", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_38_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "pip_deps_38", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_38_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "pip_deps_38", + "requirement": "setuptools<=70.3.0" + } + }, + "pip_deps_39_numpy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "pip_deps_39", + "requirement": "numpy<=1.26.1" + } + }, + "pip_deps_39_setuptools": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "pip_deps_39", + "requirement": "setuptools<=70.3.0" + } + }, + "rules_fuzzing_py_deps_310_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "rules_fuzzing_py_deps_310", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_310_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_10_host//:python", + "repo": "rules_fuzzing_py_deps_310", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_311_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_fuzzing_py_deps_311", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_311_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_fuzzing_py_deps_311", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_312_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "rules_fuzzing_py_deps_312", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_312_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_12_host//:python", + "repo": "rules_fuzzing_py_deps_312", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_38_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "rules_fuzzing_py_deps_38", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_38_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_8_host//:python", + "repo": "rules_fuzzing_py_deps_38", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_fuzzing_py_deps_39_absl_py": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "rules_fuzzing_py_deps_39", + "requirement": "absl-py==2.0.0 --hash=sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3" + } + }, + "rules_fuzzing_py_deps_39_six": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_fuzzing_py_deps//{name}:{target}", + "extra_pip_args": [ + "--require-hashes" + ], + "python_interpreter_target": "@@rules_python++python+python_3_9_host//:python", + "repo": "rules_fuzzing_py_deps_39", + "requirement": "six==1.16.0 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + }, + "rules_python_publish_deps_311_backports_tarfile_py3_none_any_77e284d7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "backports.tarfile-1.2.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "backports-tarfile==1.2.0", + "sha256": "77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", + "urls": [ + "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_backports_tarfile_sdist_d75e02c2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "backports_tarfile-1.2.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "backports-tarfile==1.2.0", + "sha256": "d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", + "urls": [ + "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_certifi_py3_none_any_922820b5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "certifi-2024.8.30-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "certifi==2024.8.30", + "sha256": "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "urls": [ + "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_certifi_sdist_bec941d2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "certifi-2024.8.30.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "certifi==2024.8.30", + "sha256": "bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", + "urls": [ + "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_a1ed2dd2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "urls": [ + "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_46bf4316": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "urls": [ + "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_s390x_a24ed04c": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "urls": [ + "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_610faea7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "urls": [ + "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_aarch64_a9b15d49": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "urls": [ + "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_fc48c783": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", + "urls": [ + "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cffi_sdist_1c39c601": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "cffi-1.17.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cffi==1.17.1", + "sha256": "1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "urls": [ + "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0d99dd8f": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "urls": [ + "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_c57516e5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "urls": [ + "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_6dba5d19": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "urls": [ + "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_bf4475b8": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "urls": [ + "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_ce031db0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "urls": [ + "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8ff4e7cd": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "urls": [ + "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_3710a975": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "urls": [ + "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_aarch64_47334db7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "urls": [ + "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_ppc64le_f1a2f519": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "urls": [ + "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_s390x_63bc5c4a": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "urls": [ + "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_x86_64_bcb4f8ea": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "urls": [ + "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_cee4373f": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "urls": [ + "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_py3_none_any_fe9f97fe": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "charset_normalizer-3.4.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "urls": [ + "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_charset_normalizer_sdist_223217c3": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "charset_normalizer-3.4.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "charset-normalizer==3.4.0", + "sha256": "223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "urls": [ + "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_aarch64_846da004": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", + "urls": [ + "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_x86_64_0f996e72": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", + "urls": [ + "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_aarch64_f7b178f1": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", + "urls": [ + "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_x86_64_c2e6fc39": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", + "urls": [ + "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_aarch64_e1be4655": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", + "urls": [ + "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_x86_64_df6b6c6d": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", + "urls": [ + "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_cryptography_sdist_315b9001": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "cryptography-43.0.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "cryptography==43.0.3", + "sha256": "315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", + "urls": [ + "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_docutils_py3_none_any_dafca5b9": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "docutils-0.21.2-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "docutils==0.21.2", + "sha256": "dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", + "urls": [ + "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_docutils_sdist_3a6b1873": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "docutils-0.21.2.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "docutils==0.21.2", + "sha256": "3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", + "urls": [ + "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_idna_py3_none_any_946d195a": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "idna-3.10-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "idna==3.10", + "sha256": "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", + "urls": [ + "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_idna_sdist_12f65c9b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "idna-3.10.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "idna==3.10", + "sha256": "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "urls": [ + "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_importlib_metadata_py3_none_any_45e54197": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "importlib_metadata-8.5.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "importlib-metadata==8.5.0", + "sha256": "45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", + "urls": [ + "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_importlib_metadata_sdist_71522656": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "importlib_metadata-8.5.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "importlib-metadata==8.5.0", + "sha256": "71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", + "urls": [ + "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jaraco_classes_py3_none_any_f662826b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "jaraco.classes-3.4.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-classes==3.4.0", + "sha256": "f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", + "urls": [ + "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jaraco_classes_sdist_47a024b5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jaraco.classes-3.4.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-classes==3.4.0", + "sha256": "47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "urls": [ + "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jaraco_context_py3_none_any_f797fc48": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "jaraco.context-6.0.1-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-context==6.0.1", + "sha256": "f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", + "urls": [ + "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jaraco_context_sdist_9bae4ea5": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jaraco_context-6.0.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-context==6.0.1", + "sha256": "9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", + "urls": [ + "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jaraco_functools_py3_none_any_ad159f13": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "jaraco.functools-4.1.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-functools==4.1.0", + "sha256": "ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", + "urls": [ + "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jaraco_functools_sdist_70f7e0e2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jaraco_functools-4.1.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jaraco-functools==4.1.0", + "sha256": "70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", + "urls": [ + "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "jeepney-0.8.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jeepney==0.8.0", + "sha256": "c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755", + "urls": [ + "https://files.pythonhosted.org/packages/ae/72/2a1e2290f1ab1e06f71f3d0f1646c9e4634e70e1d37491535e19266e8dc9/jeepney-0.8.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_jeepney_sdist_5efe48d2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "jeepney-0.8.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "jeepney==0.8.0", + "sha256": "5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", + "urls": [ + "https://files.pythonhosted.org/packages/d6/f4/154cf374c2daf2020e05c3c6a03c91348d59b23c5366e968feb198306fdf/jeepney-0.8.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_keyring_py3_none_any_5426f817": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "keyring-25.4.1-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "keyring==25.4.1", + "sha256": "5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf", + "urls": [ + "https://files.pythonhosted.org/packages/83/25/e6d59e5f0a0508d0dca8bb98c7f7fd3772fc943ac3f53d5ab18a218d32c0/keyring-25.4.1-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_keyring_sdist_b07ebc55": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "keyring-25.4.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "keyring==25.4.1", + "sha256": "b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b", + "urls": [ + "https://files.pythonhosted.org/packages/a5/1c/2bdbcfd5d59dc6274ffb175bc29aa07ecbfab196830e0cfbde7bd861a2ea/keyring-25.4.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_markdown_it_py_py3_none_any_35521684": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "markdown_it_py-3.0.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "markdown-it-py==3.0.0", + "sha256": "355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "urls": [ + "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_markdown_it_py_sdist_e3f60a94": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "markdown-it-py-3.0.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "markdown-it-py==3.0.0", + "sha256": "e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", + "urls": [ + "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_mdurl_py3_none_any_84008a41": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "mdurl-0.1.2-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "mdurl==0.1.2", + "sha256": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "urls": [ + "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_mdurl_sdist_bb413d29": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "mdurl-0.1.2.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "mdurl==0.1.2", + "sha256": "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", + "urls": [ + "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_more_itertools_py3_none_any_037b0d32": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "more_itertools-10.5.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "more-itertools==10.5.0", + "sha256": "037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", + "urls": [ + "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_more_itertools_sdist_5482bfef": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "more-itertools-10.5.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "more-itertools==10.5.0", + "sha256": "5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", + "urls": [ + "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_14c5a72e": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", + "urls": [ + "https://files.pythonhosted.org/packages/b3/89/1daff5d9ba5a95a157c092c7c5f39b8dd2b1ddb4559966f808d31cfb67e0/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_7b7c2a3c": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", + "urls": [ + "https://files.pythonhosted.org/packages/2c/b6/42fc3c69cabf86b6b81e4c051a9b6e249c5ba9f8155590222c2622961f58/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_aarch64_42c64511": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", + "urls": [ + "https://files.pythonhosted.org/packages/45/b9/833f385403abaf0023c6547389ec7a7acf141ddd9d1f21573723a6eab39a/nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_armv7l_0411beb0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", + "urls": [ + "https://files.pythonhosted.org/packages/05/2b/85977d9e11713b5747595ee61f381bc820749daf83f07b90b6c9964cf932/nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64_5f36b271": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", + "urls": [ + "https://files.pythonhosted.org/packages/72/f2/5c894d5265ab80a97c68ca36f25c8f6f0308abac649aaf152b74e7e854a8/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64le_34c03fa7": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", + "urls": [ + "https://files.pythonhosted.org/packages/ab/a7/375afcc710dbe2d64cfbd69e31f82f3e423d43737258af01f6a56d844085/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_s390x_19aaba96": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", + "urls": [ + "https://files.pythonhosted.org/packages/c2/a8/3bb02d0c60a03ad3a112b76c46971e9480efa98a8946677b5a59f60130ca/nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_x86_64_de3ceed6": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", + "urls": [ + "https://files.pythonhosted.org/packages/1b/63/6ab90d0e5225ab9780f6c9fb52254fa36b52bb7c188df9201d05b647e5e1/nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_aarch64_f0eca9ca": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe", + "urls": [ + "https://files.pythonhosted.org/packages/a3/da/0c4e282bc3cff4a0adf37005fa1fb42257673fbc1bbf7d1ff639ec3d255a/nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_armv7l_3a157ab1": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", + "urls": [ + "https://files.pythonhosted.org/packages/de/81/c291231463d21da5f8bba82c8167a6d6893cc5419b0639801ee5d3aeb8a9/nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_x86_64_36c95d4b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", + "urls": [ + "https://files.pythonhosted.org/packages/eb/61/73a007c74c37895fdf66e0edcd881f5eaa17a348ff02f4bb4bc906d61085/nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_cp37_abi3_win_amd64_8ce0f819": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "nh3-0.2.18-cp37-abi3-win_amd64.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", + "urls": [ + "https://files.pythonhosted.org/packages/26/8d/53c5b19c4999bdc6ba95f246f4ef35ca83d7d7423e5e38be43ad66544e5d/nh3-0.2.18-cp37-abi3-win_amd64.whl" + ] + } + }, + "rules_python_publish_deps_311_nh3_sdist_94a16692": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "nh3-0.2.18.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "nh3==0.2.18", + "sha256": "94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", + "urls": [ + "https://files.pythonhosted.org/packages/62/73/10df50b42ddb547a907deeb2f3c9823022580a7a47281e8eae8e003a9639/nh3-0.2.18.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pkginfo_py3_none_any_889a6da2": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "pkginfo-1.10.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pkginfo==1.10.0", + "sha256": "889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097", + "urls": [ + "https://files.pythonhosted.org/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pkginfo_sdist_5df73835": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pkginfo-1.10.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pkginfo==1.10.0", + "sha256": "5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "urls": [ + "https://files.pythonhosted.org/packages/2f/72/347ec5be4adc85c182ed2823d8d1c7b51e13b9a6b0c1aae59582eca652df/pkginfo-1.10.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pycparser_py3_none_any_c3702b6d": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "pycparser-2.22-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pycparser==2.22", + "sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", + "urls": [ + "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pycparser_sdist_491c8be9": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pycparser-2.22.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pycparser==2.22", + "sha256": "491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "urls": [ + "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pygments_py3_none_any_b8e6aca0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "pygments-2.18.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pygments==2.18.0", + "sha256": "b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", + "urls": [ + "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pygments_sdist_786ff802": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pygments-2.18.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pygments==2.18.0", + "sha256": "786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "urls": [ + "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_pywin32_ctypes_py3_none_any_8a151337": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_windows_x86_64" + ], + "filename": "pywin32_ctypes-0.2.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pywin32-ctypes==0.2.3", + "sha256": "8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", + "urls": [ + "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_pywin32_ctypes_sdist_d162dc04": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "pywin32-ctypes-0.2.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "pywin32-ctypes==0.2.3", + "sha256": "d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", + "urls": [ + "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_readme_renderer_py3_none_any_2fbca89b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "readme_renderer-44.0-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "readme-renderer==44.0", + "sha256": "2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "urls": [ + "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_readme_renderer_sdist_8712034e": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "readme_renderer-44.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "readme-renderer==44.0", + "sha256": "8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", + "urls": [ + "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_requests_py3_none_any_70761cfe": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "requests-2.32.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests==2.32.3", + "sha256": "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", + "urls": [ + "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_requests_sdist_55365417": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "requests-2.32.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests==2.32.3", + "sha256": "55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "urls": [ + "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_requests_toolbelt_py2_none_any_cccfdd66": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "requests_toolbelt-1.0.0-py2.py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests-toolbelt==1.0.0", + "sha256": "cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", + "urls": [ + "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_requests_toolbelt_sdist_7681a0a3": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "requests-toolbelt-1.0.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "requests-toolbelt==1.0.0", + "sha256": "7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "urls": [ + "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "rfc3986-2.0.0-py2.py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rfc3986==2.0.0", + "sha256": "50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "urls": [ + "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_rfc3986_sdist_97aacf9d": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "rfc3986-2.0.0.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rfc3986==2.0.0", + "sha256": "97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", + "urls": [ + "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_rich_py3_none_any_9836f509": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "rich-13.9.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rich==13.9.3", + "sha256": "9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", + "urls": [ + "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_rich_sdist_bc1e01b8": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "rich-13.9.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "rich==13.9.3", + "sha256": "bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", + "urls": [ + "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "filename": "SecretStorage-3.3.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "secretstorage==3.3.3", + "sha256": "f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", + "urls": [ + "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_secretstorage_sdist_2403533e": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "SecretStorage-3.3.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "secretstorage==3.3.3", + "sha256": "2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", + "urls": [ + "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_twine_py3_none_any_215dbe7b": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "twine-5.1.1-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "twine==5.1.1", + "sha256": "215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "urls": [ + "https://files.pythonhosted.org/packages/5d/ec/00f9d5fd040ae29867355e559a94e9a8429225a0284a3f5f091a3878bfc0/twine-5.1.1-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_twine_sdist_9aa08251": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "twine-5.1.1.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "twine==5.1.1", + "sha256": "9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db", + "urls": [ + "https://files.pythonhosted.org/packages/77/68/bd982e5e949ef8334e6f7dcf76ae40922a8750aa2e347291ae1477a4782b/twine-5.1.1.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_urllib3_py3_none_any_ca899ca0": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "urllib3-2.2.3-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "urllib3==2.2.3", + "sha256": "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "urls": [ + "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_urllib3_sdist_e7d814a8": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "urllib3-2.2.3.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "urllib3==2.2.3", + "sha256": "e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", + "urls": [ + "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz" + ] + } + }, + "rules_python_publish_deps_311_zipp_py3_none_any_a817ac80": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "filename": "zipp-3.20.2-py3-none-any.whl", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "zipp==3.20.2", + "sha256": "a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", + "urls": [ + "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl" + ] + } + }, + "rules_python_publish_deps_311_zipp_sdist_bc9eb26f": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@rules_python_publish_deps//{name}:{target}", + "experimental_target_platforms": [ + "cp311_linux_aarch64", + "cp311_linux_arm", + "cp311_linux_ppc", + "cp311_linux_s390x", + "cp311_linux_x86_64", + "cp311_osx_aarch64", + "cp311_osx_x86_64", + "cp311_windows_x86_64" + ], + "extra_pip_args": [ + "--index-url", + "https://pypi.org/simple" + ], + "filename": "zipp-3.20.2.tar.gz", + "python_interpreter_target": "@@rules_python++python+python_3_11_host//:python", + "repo": "rules_python_publish_deps_311", + "requirement": "zipp==3.20.2", + "sha256": "bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", + "urls": [ + "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz" + ] + } + }, + "pip_deps": { + "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", + "attributes": { + "repo_name": "pip_deps", + "extra_hub_aliases": {}, + "whl_map": { + "numpy": "{\"pip_deps_310_numpy\":[{\"version\":\"3.10\"}],\"pip_deps_311_numpy\":[{\"version\":\"3.11\"}],\"pip_deps_312_numpy\":[{\"version\":\"3.12\"}],\"pip_deps_38_numpy\":[{\"version\":\"3.8\"}],\"pip_deps_39_numpy\":[{\"version\":\"3.9\"}]}", + "setuptools": "{\"pip_deps_310_setuptools\":[{\"version\":\"3.10\"}],\"pip_deps_311_setuptools\":[{\"version\":\"3.11\"}],\"pip_deps_312_setuptools\":[{\"version\":\"3.12\"}],\"pip_deps_38_setuptools\":[{\"version\":\"3.8\"}],\"pip_deps_39_setuptools\":[{\"version\":\"3.9\"}]}" + }, + "packages": [ + "numpy", + "setuptools" + ], + "groups": {} + } + }, + "rules_fuzzing_py_deps": { + "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", + "attributes": { + "repo_name": "rules_fuzzing_py_deps", + "extra_hub_aliases": {}, + "whl_map": { + "absl_py": "{\"rules_fuzzing_py_deps_310_absl_py\":[{\"version\":\"3.10\"}],\"rules_fuzzing_py_deps_311_absl_py\":[{\"version\":\"3.11\"}],\"rules_fuzzing_py_deps_312_absl_py\":[{\"version\":\"3.12\"}],\"rules_fuzzing_py_deps_38_absl_py\":[{\"version\":\"3.8\"}],\"rules_fuzzing_py_deps_39_absl_py\":[{\"version\":\"3.9\"}]}", + "six": "{\"rules_fuzzing_py_deps_310_six\":[{\"version\":\"3.10\"}],\"rules_fuzzing_py_deps_311_six\":[{\"version\":\"3.11\"}],\"rules_fuzzing_py_deps_312_six\":[{\"version\":\"3.12\"}],\"rules_fuzzing_py_deps_38_six\":[{\"version\":\"3.8\"}],\"rules_fuzzing_py_deps_39_six\":[{\"version\":\"3.9\"}]}" + }, + "packages": [ + "absl_py", + "six" + ], + "groups": {} + } + }, + "rules_python_publish_deps": { + "repoRuleId": "@@rules_python+//python/private/pypi:hub_repository.bzl%hub_repository", + "attributes": { + "repo_name": "rules_python_publish_deps", + "extra_hub_aliases": {}, + "whl_map": { + "backports_tarfile": "{\"rules_python_publish_deps_311_backports_tarfile_py3_none_any_77e284d7\":[{\"filename\":\"backports.tarfile-1.2.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_backports_tarfile_sdist_d75e02c2\":[{\"filename\":\"backports_tarfile-1.2.0.tar.gz\",\"version\":\"3.11\"}]}", + "certifi": "{\"rules_python_publish_deps_311_certifi_py3_none_any_922820b5\":[{\"filename\":\"certifi-2024.8.30-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_certifi_sdist_bec941d2\":[{\"filename\":\"certifi-2024.8.30.tar.gz\",\"version\":\"3.11\"}]}", + "cffi": "{\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_a1ed2dd2\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_46bf4316\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_s390x_a24ed04c\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_610faea7\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_aarch64_a9b15d49\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_fc48c783\":[{\"filename\":\"cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cffi_sdist_1c39c601\":[{\"filename\":\"cffi-1.17.1.tar.gz\",\"version\":\"3.11\"}]}", + "charset_normalizer": "{\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0d99dd8f\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_c57516e5\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_6dba5d19\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_bf4475b8\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_ce031db0\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8ff4e7cd\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_3710a975\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_aarch64_47334db7\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_ppc64le_f1a2f519\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_s390x_63bc5c4a\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_2_x86_64_bcb4f8ea\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_cee4373f\":[{\"filename\":\"charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_py3_none_any_fe9f97fe\":[{\"filename\":\"charset_normalizer-3.4.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_charset_normalizer_sdist_223217c3\":[{\"filename\":\"charset_normalizer-3.4.0.tar.gz\",\"version\":\"3.11\"}]}", + "cryptography": "{\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_aarch64_846da004\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_17_x86_64_0f996e72\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_aarch64_f7b178f1\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_manylinux_2_28_x86_64_c2e6fc39\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_aarch64_e1be4655\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_cp39_abi3_musllinux_1_2_x86_64_df6b6c6d\":[{\"filename\":\"cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_cryptography_sdist_315b9001\":[{\"filename\":\"cryptography-43.0.3.tar.gz\",\"version\":\"3.11\"}]}", + "docutils": "{\"rules_python_publish_deps_311_docutils_py3_none_any_dafca5b9\":[{\"filename\":\"docutils-0.21.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_docutils_sdist_3a6b1873\":[{\"filename\":\"docutils-0.21.2.tar.gz\",\"version\":\"3.11\"}]}", + "idna": "{\"rules_python_publish_deps_311_idna_py3_none_any_946d195a\":[{\"filename\":\"idna-3.10-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_idna_sdist_12f65c9b\":[{\"filename\":\"idna-3.10.tar.gz\",\"version\":\"3.11\"}]}", + "importlib_metadata": "{\"rules_python_publish_deps_311_importlib_metadata_py3_none_any_45e54197\":[{\"filename\":\"importlib_metadata-8.5.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_importlib_metadata_sdist_71522656\":[{\"filename\":\"importlib_metadata-8.5.0.tar.gz\",\"version\":\"3.11\"}]}", + "jaraco_classes": "{\"rules_python_publish_deps_311_jaraco_classes_py3_none_any_f662826b\":[{\"filename\":\"jaraco.classes-3.4.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_classes_sdist_47a024b5\":[{\"filename\":\"jaraco.classes-3.4.0.tar.gz\",\"version\":\"3.11\"}]}", + "jaraco_context": "{\"rules_python_publish_deps_311_jaraco_context_py3_none_any_f797fc48\":[{\"filename\":\"jaraco.context-6.0.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_context_sdist_9bae4ea5\":[{\"filename\":\"jaraco_context-6.0.1.tar.gz\",\"version\":\"3.11\"}]}", + "jaraco_functools": "{\"rules_python_publish_deps_311_jaraco_functools_py3_none_any_ad159f13\":[{\"filename\":\"jaraco.functools-4.1.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jaraco_functools_sdist_70f7e0e2\":[{\"filename\":\"jaraco_functools-4.1.0.tar.gz\",\"version\":\"3.11\"}]}", + "jeepney": "{\"rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad\":[{\"filename\":\"jeepney-0.8.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_jeepney_sdist_5efe48d2\":[{\"filename\":\"jeepney-0.8.0.tar.gz\",\"version\":\"3.11\"}]}", + "keyring": "{\"rules_python_publish_deps_311_keyring_py3_none_any_5426f817\":[{\"filename\":\"keyring-25.4.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_keyring_sdist_b07ebc55\":[{\"filename\":\"keyring-25.4.1.tar.gz\",\"version\":\"3.11\"}]}", + "markdown_it_py": "{\"rules_python_publish_deps_311_markdown_it_py_py3_none_any_35521684\":[{\"filename\":\"markdown_it_py-3.0.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_markdown_it_py_sdist_e3f60a94\":[{\"filename\":\"markdown-it-py-3.0.0.tar.gz\",\"version\":\"3.11\"}]}", + "mdurl": "{\"rules_python_publish_deps_311_mdurl_py3_none_any_84008a41\":[{\"filename\":\"mdurl-0.1.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_mdurl_sdist_bb413d29\":[{\"filename\":\"mdurl-0.1.2.tar.gz\",\"version\":\"3.11\"}]}", + "more_itertools": "{\"rules_python_publish_deps_311_more_itertools_py3_none_any_037b0d32\":[{\"filename\":\"more_itertools-10.5.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_more_itertools_sdist_5482bfef\":[{\"filename\":\"more-itertools-10.5.0.tar.gz\",\"version\":\"3.11\"}]}", + "nh3": "{\"rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_14c5a72e\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_macosx_10_12_x86_64_7b7c2a3c\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_aarch64_42c64511\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_armv7l_0411beb0\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64_5f36b271\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_ppc64le_34c03fa7\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_s390x_19aaba96\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_manylinux_2_17_x86_64_de3ceed6\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_aarch64_f0eca9ca\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_armv7l_3a157ab1\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_musllinux_1_2_x86_64_36c95d4b\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_cp37_abi3_win_amd64_8ce0f819\":[{\"filename\":\"nh3-0.2.18-cp37-abi3-win_amd64.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_nh3_sdist_94a16692\":[{\"filename\":\"nh3-0.2.18.tar.gz\",\"version\":\"3.11\"}]}", + "pkginfo": "{\"rules_python_publish_deps_311_pkginfo_py3_none_any_889a6da2\":[{\"filename\":\"pkginfo-1.10.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pkginfo_sdist_5df73835\":[{\"filename\":\"pkginfo-1.10.0.tar.gz\",\"version\":\"3.11\"}]}", + "pycparser": "{\"rules_python_publish_deps_311_pycparser_py3_none_any_c3702b6d\":[{\"filename\":\"pycparser-2.22-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pycparser_sdist_491c8be9\":[{\"filename\":\"pycparser-2.22.tar.gz\",\"version\":\"3.11\"}]}", + "pygments": "{\"rules_python_publish_deps_311_pygments_py3_none_any_b8e6aca0\":[{\"filename\":\"pygments-2.18.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pygments_sdist_786ff802\":[{\"filename\":\"pygments-2.18.0.tar.gz\",\"version\":\"3.11\"}]}", + "pywin32_ctypes": "{\"rules_python_publish_deps_311_pywin32_ctypes_py3_none_any_8a151337\":[{\"filename\":\"pywin32_ctypes-0.2.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_pywin32_ctypes_sdist_d162dc04\":[{\"filename\":\"pywin32-ctypes-0.2.3.tar.gz\",\"version\":\"3.11\"}]}", + "readme_renderer": "{\"rules_python_publish_deps_311_readme_renderer_py3_none_any_2fbca89b\":[{\"filename\":\"readme_renderer-44.0-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_readme_renderer_sdist_8712034e\":[{\"filename\":\"readme_renderer-44.0.tar.gz\",\"version\":\"3.11\"}]}", + "requests": "{\"rules_python_publish_deps_311_requests_py3_none_any_70761cfe\":[{\"filename\":\"requests-2.32.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_requests_sdist_55365417\":[{\"filename\":\"requests-2.32.3.tar.gz\",\"version\":\"3.11\"}]}", + "requests_toolbelt": "{\"rules_python_publish_deps_311_requests_toolbelt_py2_none_any_cccfdd66\":[{\"filename\":\"requests_toolbelt-1.0.0-py2.py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_requests_toolbelt_sdist_7681a0a3\":[{\"filename\":\"requests-toolbelt-1.0.0.tar.gz\",\"version\":\"3.11\"}]}", + "rfc3986": "{\"rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b\":[{\"filename\":\"rfc3986-2.0.0-py2.py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_rfc3986_sdist_97aacf9d\":[{\"filename\":\"rfc3986-2.0.0.tar.gz\",\"version\":\"3.11\"}]}", + "rich": "{\"rules_python_publish_deps_311_rich_py3_none_any_9836f509\":[{\"filename\":\"rich-13.9.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_rich_sdist_bc1e01b8\":[{\"filename\":\"rich-13.9.3.tar.gz\",\"version\":\"3.11\"}]}", + "secretstorage": "{\"rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662\":[{\"filename\":\"SecretStorage-3.3.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_secretstorage_sdist_2403533e\":[{\"filename\":\"SecretStorage-3.3.3.tar.gz\",\"version\":\"3.11\"}]}", + "twine": "{\"rules_python_publish_deps_311_twine_py3_none_any_215dbe7b\":[{\"filename\":\"twine-5.1.1-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_twine_sdist_9aa08251\":[{\"filename\":\"twine-5.1.1.tar.gz\",\"version\":\"3.11\"}]}", + "urllib3": "{\"rules_python_publish_deps_311_urllib3_py3_none_any_ca899ca0\":[{\"filename\":\"urllib3-2.2.3-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_urllib3_sdist_e7d814a8\":[{\"filename\":\"urllib3-2.2.3.tar.gz\",\"version\":\"3.11\"}]}", + "zipp": "{\"rules_python_publish_deps_311_zipp_py3_none_any_a817ac80\":[{\"filename\":\"zipp-3.20.2-py3-none-any.whl\",\"version\":\"3.11\"}],\"rules_python_publish_deps_311_zipp_sdist_bc9eb26f\":[{\"filename\":\"zipp-3.20.2.tar.gz\",\"version\":\"3.11\"}]}" + }, + "packages": [ + "backports_tarfile", + "certifi", + "charset_normalizer", + "docutils", + "idna", + "importlib_metadata", + "jaraco_classes", + "jaraco_context", + "jaraco_functools", + "keyring", + "markdown_it_py", + "mdurl", + "more_itertools", + "nh3", + "pkginfo", + "pygments", + "readme_renderer", + "requests", + "requests_toolbelt", + "rfc3986", + "rich", + "twine", + "urllib3", + "zipp" + ], + "groups": {} + } + } + }, + "recordedRepoMappingEntries": [ + [ + "bazel_features+", + "bazel_features_globals", + "bazel_features++version_extension+bazel_features_globals" + ], + [ + "bazel_features+", + "bazel_features_version", + "bazel_features++version_extension+bazel_features_version" + ], + [ + "rules_python+", + "bazel_features", + "bazel_features+" + ], + [ + "rules_python+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "rules_python+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python+", + "pypi__build", + "rules_python++internal_deps+pypi__build" + ], + [ + "rules_python+", + "pypi__click", + "rules_python++internal_deps+pypi__click" + ], + [ + "rules_python+", + "pypi__colorama", + "rules_python++internal_deps+pypi__colorama" + ], + [ + "rules_python+", + "pypi__importlib_metadata", + "rules_python++internal_deps+pypi__importlib_metadata" + ], + [ + "rules_python+", + "pypi__installer", + "rules_python++internal_deps+pypi__installer" + ], + [ + "rules_python+", + "pypi__more_itertools", + "rules_python++internal_deps+pypi__more_itertools" + ], + [ + "rules_python+", + "pypi__packaging", + "rules_python++internal_deps+pypi__packaging" + ], + [ + "rules_python+", + "pypi__pep517", + "rules_python++internal_deps+pypi__pep517" + ], + [ + "rules_python+", + "pypi__pip", + "rules_python++internal_deps+pypi__pip" + ], + [ + "rules_python+", + "pypi__pip_tools", + "rules_python++internal_deps+pypi__pip_tools" + ], + [ + "rules_python+", + "pypi__pyproject_hooks", + "rules_python++internal_deps+pypi__pyproject_hooks" + ], + [ + "rules_python+", + "pypi__setuptools", + "rules_python++internal_deps+pypi__setuptools" + ], + [ + "rules_python+", + "pypi__tomli", + "rules_python++internal_deps+pypi__tomli" + ], + [ + "rules_python+", + "pypi__wheel", + "rules_python++internal_deps+pypi__wheel" + ], + [ + "rules_python+", + "pypi__zipp", + "rules_python++internal_deps+pypi__zipp" + ], + [ + "rules_python+", + "pythons_hub", + "rules_python++python+pythons_hub" + ], + [ + "rules_python++python+pythons_hub", + "python_3_10_host", + "rules_python++python+python_3_10_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_11_host", + "rules_python++python+python_3_11_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_12_host", + "rules_python++python+python_3_12_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_8_host", + "rules_python++python+python_3_8_host" + ], + [ + "rules_python++python+pythons_hub", + "python_3_9_host", + "rules_python++python+python_3_9_host" + ] + ] + } + }, + "@@rules_sass+//src/toolchain:extensions.bzl%sass": { + "general": { + "bzlTransitiveDigest": "RA58Nyrsn03Z5YmQnpmBw3mqlVck++XIrx34amsqU/E=", + "usagesDigest": "R0KshhzIouLWuexMUCrl4HY+FUDwlVVgF9Z7UnwyUWA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "linux_amd64_sass": { + "repoRuleId": "@@rules_sass+//src/toolchain:configure_sass.bzl%configure_sass", + "attributes": { + "file": "@rules_sass//src/compiler/built:sass_linux_x64", + "sha256": "", + "constraints": [ + "@@platforms//os:linux", + "@@platforms//cpu:x86_64" + ] + } + }, + "linux_arm64_sass": { + "repoRuleId": "@@rules_sass+//src/toolchain:configure_sass.bzl%configure_sass", + "attributes": { + "file": "@rules_sass//src/compiler/built:sass_linux_arm", + "sha256": "", + "constraints": [ + "@@platforms//os:linux", + "@@platforms//cpu:arm64" + ] + } + }, + "darwin_amd64_sass": { + "repoRuleId": "@@rules_sass+//src/toolchain:configure_sass.bzl%configure_sass", + "attributes": { + "file": "@rules_sass//src/compiler/built:sass_mac_x64", + "sha256": "", + "constraints": [ + "@@platforms//os:macos", + "@@platforms//cpu:x86_64" + ] + } + }, + "darwin_arm64_sass": { + "repoRuleId": "@@rules_sass+//src/toolchain:configure_sass.bzl%configure_sass", + "attributes": { + "file": "@rules_sass//src/compiler/built:sass_mac_arm", + "sha256": "", + "constraints": [ + "@@platforms//os:macos", + "@@platforms//cpu:arm64" + ] + } + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@yq.bzl+//yq:extensions.bzl%yq": { + "general": { + "bzlTransitiveDigest": "61Uz+o5PnlY0jJfPZEUNqsKxnM/UCLeWsn5VVCc8u5Y=", + "usagesDigest": "iZPDKVC6SAQMLLGWulzoS8hhxbx9Eh5DsJBUjn69u2Q=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "yq_darwin_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "darwin_amd64", + "version": "4.45.1" + } + }, + "yq_darwin_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "darwin_arm64", + "version": "4.45.1" + } + }, + "yq_linux_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_amd64", + "version": "4.45.1" + } + }, + "yq_linux_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_arm64", + "version": "4.45.1" + } + }, + "yq_linux_s390x": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_s390x", + "version": "4.45.1" + } + }, + "yq_linux_riscv64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_riscv64", + "version": "4.45.1" + } + }, + "yq_linux_ppc64le": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_ppc64le", + "version": "4.45.1" + } + }, + "yq_windows_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_amd64", + "version": "4.45.1" + } + }, + "yq_toolchains": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:toolchain.bzl%yq_toolchains_repo", + "attributes": { + "user_repository_name": "yq" + } + } + }, + "recordedRepoMappingEntries": [] + } + } + }, + "facts": {} +} diff --git a/README.md b/README.md index ccf857b94f3b..21a7d3f13398 100644 --- a/README.md +++ b/README.md @@ -1,253 +1,197 @@ -## Angular-CLI + -## Note +

Angular CLI - The CLI tool for Angular.

-This project is very much still a work in progress. +

+
+ Angular CLI logo +

+ The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, +
and maintain Angular applications directly from a command shell.
+
+

-We still have a long way before getting out of our alpha stage. -If you wish to collaborate while the project is still young, check out [our issue list](https://github.com/angular/angular-cli/issues). +

+ angular.dev/tools/cli +
+

-## Prerequisites +

+ Contributing Guidelines + · + Submit an Issue + · + Blog +
+
+

-The generated project has dependencies that require **Node 4 or greater**. +
-## Table of Contents +## Documentation -* [Installation](#installation) -* [Usage](#usage) -* [Generating a New Project](#generating-and-serving-an-angular2-project-via-a-development-server) -* [Generating Components, Directives, Pipes and Services](#generating-other-scaffolds) -* [Generating a Route](#generating-a-route) -* [Creating a Build](#creating-a-build) -* [Installing a 3rd Party Library](#installing-a-3rd-party-library) -* [Running Unit Tests](#running-unit-tests) -* [Running End-to-End Tests](#running-end-to-end-tests) -* [Deploying the App via GitHub Pages](#deploying-the-app-via-github-pages) -* [Known Issues](#known-issues) +Get started with Angular CLI, learn the fundamentals and explore advanced topics on our documentation website. -## Installation +- [Getting started][quickstart] +- [CLI][cli] +- [Workspace and project file structure][filestructure] +- [Workspace configuration][workspaceconfig] +- [Schematics][schematics] -```bash -npm install -g angular-cli -``` - -## Usage - -```bash -ng --help -``` - -### Generating and serving an Angular2 project via a development server - -```bash -ng new PROJECT_NAME -cd PROJECT_NAME -ng serve -``` -Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -You can configure the default HTTP port and the one used by the LiveReload server with two command-line options : - -```bash -ng serve --port 4201 --live-reload-port 49153 -``` - -### Generating other scaffolds - -You can use the `ng generate` (or just `ng g`) command to generate Angular components: - -```bash -ng generate component my-new-component -ng g component my-new-component # using the alias -``` -You can find all possible blueprints in the table below: +## Development Setup -Scaffold | Usage ---- | --- -Component | `ng g component my-new-component` -Directive | `ng g directive my-new-directive` -Pipe | `ng g pipe my-new-pipe` -Service | `ng g service my-new-service` -### Generating a route +### Prerequisites -You can generate a new route by with the following command (note the singular -used in `hero`): +- Install [Node.js] which includes [Node Package Manager][npm] -```bash -ng generate route hero -``` +### Setting Up a Project -This will create a folder with a routable component (`hero-root.component.ts`) -with two sub-routes. The file structure will be as follows: +Install the Angular CLI globally: ``` -... -|-- app -| |-- hero -| | |-- hero-detail.component.html -| | |-- hero-detail.component.css -| | |-- hero-detail.component.spec.ts -| | |-- hero-detail.component.ts -| | |-- hero-list.component.html -| | |-- hero-list.component.css -| | |-- hero-list.component.spec.ts -| | |-- hero-list.component.ts -| | |-- hero-root.component.spec.ts -| | |-- hero-root.component.ts -| | |-- hero.service.spec.ts -| | |-- hero.service.ts -| |-- ... -|-- app.ts -... +npm install -g @angular/cli ``` -Afterwards to use the new route open your main app component, import -`hero-root.component.ts` and add it in the route config: +Create workspace: ``` -@RouteConfig([ - {path:'/hero/...', name: 'HeroRoot', component: HeroRoot} -]) +ng new [PROJECT NAME] ``` -Visiting `http://localhost:4200/hero` will show the hero list. - - -### Creating a build +Run the application: -```bash -ng build ``` - -The build artifacts will be stored in the `dist/` directory. - -### Installing a 3rd party library - -```bash -ng install ng2-cli-test-lib +cd [PROJECT NAME] +ng serve ``` -[You can read more about this here.](https://github.com/angular/angular-cli/blob/master/docs/ng-install.md) +Angular is cross-platform, fast, scalable, has incredible tooling, and is loved by millions. +## Quickstart -### Running unit tests +[Get started in 5 minutes][quickstart]. -```bash -ng test -``` +## Ecosystem -Tests will execute after a build is executed via [Karma](http://karma-runner.github.io/0.13/index.html) +

+ angular ecosystem logos +

+- [Angular Framework][adev] +- [Angular Material][angularmaterial] -### Running end-to-end tests +## Changelog -Before running the tests make sure that you have an updated webdriver and that -the tests are built: +[Learn about the latest improvements][changelog]. -```bash -$(npm bin)/webdriver-manager update -$(npm bin)/tsc -p e2e/ -``` +## Upgrading -Afterwards you only need to run `$(npm bin)/protractor` while serving via -`ng serve`. +Check out our [upgrade guide](https://update.angular.dev/) to find out the best way to upgrade your project. +## Contributing -### Deploying the app via GitHub Pages +### Contributing Guidelines -The CLI currently comes bundled with [angular-cli-github-pages addon](https://github.com/IgorMinar/angular-cli-github-pages). +Read through our [contributing guidelines][contributing] to learn about our submission process, coding rules and more. -This means that you can deploy your apps quickly via: +### Want to Help? -``` -git commit -a -m "final tweaks before deployment - what could go wrong?" -ng github-pages:deploy -``` +Want to report a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for [contributing][contributing] and then check out one of our issues labeled as [help wanted](https://github.com/angular/angular-cli/labels/help%20wanted) or [good first issue](https://github.com/angular/angular-cli/labels/good%20first%20issue). -Checkout [angular-cli-github-pages addon](https://github.com/IgorMinar/angular-cli-github-pages) docs for more info. +### Code of Conduct -### Linting and formatting code +Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct][codeofconduct]. -You can lint or format your app code by running `ng lint` or `ng format` respectively. -This will use the `lint`/`format` npm script that in generated projects uses `tslint`/`clang-format`. +### Developer Guide -You can modify the these scripts in `package.json` to run whatever tool you prefer. +Read through our [developer guide][developer] to learn about how to build and test the Angular CLI locally. -### Formatting code +## Community -You can format your app code by running `ng format`. -This will use the `format` npm script that in generated projects uses `clang-format`. +Join the conversation and help the community. -You can modify the `format` script in `package.json` to run whatever formatting tool -you prefer and `ng format` will still run it. +- [X (formerly Twitter)][twitter] +- [Discord][discord] +- [Gitter][gitter] +- [YouTube][youtube] +- [StackOverflow][stackoverflow] +- Find a Local [Meetup][meetup] +## Packages -## Known issues +This is a monorepo which contains many tools and packages: -This project is currently a prototype so there are many known issues. Just to mention a few: -- All blueprints/scaffolds are in TypeScript only, in the future blueprints in all dialects officially supported by Angular will be available. -- On Windows you need to run the `build` and `serve` commands with Admin permissions, otherwise the performance is not good. -- [Protractor](https://angular.github.io/protractor/) integration is missing. -- The initial installation as well as `ng new` take too long because of lots of npm dependencies. -- Many existing ember addons are not compatible with Angular apps built via angular-cli. +### Tools -## Development Hints for hacking on angular-cli +| Project | Package | Version | Links | +|---|---|---|---| +**Angular Build System** | [`@angular/build`](https://npmjs.com/package/@angular/build) | [![latest](https://img.shields.io/npm/v/%40angular%2Fbuild/latest.svg)](https://npmjs.com/package/@angular/build) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular/build/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-build-builds) +**Angular CLI** | [`@angular/cli`](https://npmjs.com/package/@angular/cli) | [![latest](https://img.shields.io/npm/v/%40angular%2Fcli/latest.svg)](https://npmjs.com/package/@angular/cli) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular/cli/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/cli-builds) +**Architect CLI** | [`@angular-devkit/architect-cli`](https://npmjs.com/package/@angular-devkit/architect-cli) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Farchitect-cli/latest.svg)](https://npmjs.com/package/@angular-devkit/architect-cli) | [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-architect-cli-builds) +**Schematics CLI** | [`@angular-devkit/schematics-cli`](https://npmjs.com/package/@angular-devkit/schematics-cli) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fschematics-cli/latest.svg)](https://npmjs.com/package/@angular-devkit/schematics-cli) | [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-schematics-cli-builds) -### Working with master -```bash -git clone https://github.com/angular/angular-cli.git -cd angular-cli -npm link -``` +### Packages -`npm link` is very similar to `npm install -g` except that instead of downloading the package -from the repo, the just cloned `angular-cli/` folder becomes the global package. -Any changes to the files in the `angular-cli/` folder will immediately affect the global `angular-cli` package, -allowing you to quickly test any changes you make to the cli project. -Now you can use `angular-cli` via the command line: +| Project | Package | Version | Links | +|---|---|---|---| +**Angular SSR** | [`@angular/ssr`](https://npmjs.com/package/@angular/ssr) | [![latest](https://img.shields.io/npm/v/%40angular%2Fssr/latest.svg)](https://npmjs.com/package/@angular/ssr) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular/ssr/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-ssr-builds) +**Architect** | [`@angular-devkit/architect`](https://npmjs.com/package/@angular-devkit/architect) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Farchitect/latest.svg)](https://npmjs.com/package/@angular-devkit/architect) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/architect/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-architect-builds) +**Build Angular** | [`@angular-devkit/build-angular`](https://npmjs.com/package/@angular-devkit/build-angular) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbuild-angular/latest.svg)](https://npmjs.com/package/@angular-devkit/build-angular) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/build_angular/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-build-angular-builds) +**Build Webpack** | [`@angular-devkit/build-webpack`](https://npmjs.com/package/@angular-devkit/build-webpack) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbuild-webpack/latest.svg)](https://npmjs.com/package/@angular-devkit/build-webpack) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/build_webpack/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-build-webpack-builds) +**Core** | [`@angular-devkit/core`](https://npmjs.com/package/@angular-devkit/core) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fcore/latest.svg)](https://npmjs.com/package/@angular-devkit/core) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/core/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-core-builds) +**Schematics** | [`@angular-devkit/schematics`](https://npmjs.com/package/@angular-devkit/schematics) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fschematics/latest.svg)](https://npmjs.com/package/@angular-devkit/schematics) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/schematics/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-schematics-builds) -```bash -ng new foo -cd foo -npm link angular-cli -ng server -``` +#### Misc -`npm link angular-cli` is needed because by default the globally installed `angular-cli` just loads -the local `angular-cli` from the project which was fetched remotely from npm. -`npm link angular-cli` symlinks the global `angular-cli` package to the local `angular-cli` package. -Now the `angular-cli` you cloned before is in three places: -The folder you cloned it into, npm's folder where it stores global packages and the `angular-cli` project you just created. +| Project | Package | Version | Links | +|---|---|---|---| +**Angular Create** | [`@angular/create`](https://npmjs.com/package/@angular/create) | [![latest](https://img.shields.io/npm/v/%40angular%2Fcreate/latest.svg)](https://npmjs.com/package/@angular/create) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular/create/README.md) +**Webpack Angular Plugin** | [`@ngtools/webpack`](https://npmjs.com/package/@ngtools/webpack) | [![latest](https://img.shields.io/npm/v/%40ngtools%2Fwebpack/latest.svg)](https://npmjs.com/package/@ngtools/webpack) | [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/ngtools-webpack-builds) -Please read the official [npm-link documentation](https://www.npmjs.org/doc/cli/npm-link.html) -and the [npm-link cheatsheet](http://browsenpm.org/help#linkinganynpmpackagelocally) for more information. +#### Schematics +| Project | Package | Version | Links | +|---|---|---|---| +**Angular PWA Schematics** | [`@angular/pwa`](https://npmjs.com/package/@angular/pwa) | [![latest](https://img.shields.io/npm/v/%40angular%2Fpwa/latest.svg)](https://npmjs.com/package/@angular/pwa) | [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-pwa-builds) +**Angular Schematics** | [`@schematics/angular`](https://npmjs.com/package/@schematics/angular) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fangular/latest.svg)](https://npmjs.com/package/@schematics/angular) | [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/schematics-angular-builds) -## License -MIT +**Love Angular CLI? Give our repo a star :star: :arrow_up:.** -[travis-badge]: https://travis-ci.org/angular/angular-cli.svg?branch=master -[travis-badge-url]: https://travis-ci.org/angular/angular-cli -[david-badge]: https://david-dm.org/angular/angular-cli.svg -[david-badge-url]: https://david-dm.org/angular/angular-cli -[david-dev-badge]: https://david-dm.org/angular/angular-cli/dev-status.svg -[david-dev-badge-url]: https://david-dm.org/angular/angular-cli#info=devDependencies -[npm-badge]: https://img.shields.io/npm/v/angular-cli.svg -[npm-badge-url]: https://www.npmjs.com/package/angular-cli +[contributing]: CONTRIBUTING.md +[developer]: docs/DEVELOPER.md +[quickstart]: https://angular.dev/tutorials/learn-angular +[changelog]: CHANGELOG.md +[documentation]: https://angular.dev/overview +[angularmaterial]: https://material.angular.io/ +[cli]: https://angular.dev/tools/cli +[adev]: https://angular.dev/ +[workspaceconfig]: https://angular.dev/reference/configs/workspace-config +[schematics]: https://angular.dev/tools/cli/schematics +[filestructure]: https://angular.dev/reference/configs/file-structure +[node.js]: https://nodejs.org/ +[npm]: https://www.npmjs.com/get-npm +[codeofconduct]: https://github.com/angular/angular/blob/main/CODE_OF_CONDUCT.md +[twitter]: https://www.x.com/angular +[discord]: https://discord.gg/angular +[gitter]: https://gitter.im/angular/angular-cli +[stackoverflow]: https://stackoverflow.com/questions/tagged/angular-cli +[youtube]: https://youtube.com/angular +[meetup]: https://www.meetup.com/find/?keywords=angular diff --git a/REPO.bazel b/REPO.bazel new file mode 100644 index 000000000000..9eb8128879bd --- /dev/null +++ b/REPO.bazel @@ -0,0 +1,5 @@ +ignore_directories([ + ".git", + "dist", + "**/node_modules/**", +]) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..0361308689ab --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +Angular is part of Google [Open Source Software Vulnerability Reward Program](https://bughunters.google.com/about/rules/6521337925468160/google-open-source-software-vulnerability-reward-program-rules). For vulnerabilities in Angular, please submit your report [here](https://bughunters.google.com/report). + +For more information, check out [Angular's security policy](https://angular.dev/best-practices/security). diff --git a/addon/dummy-addon/README.md b/addon/dummy-addon/README.md deleted file mode 100644 index 311b0dae546b..000000000000 --- a/addon/dummy-addon/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a dummy addon, used to demonstrate the use of `internalAddonPaths`. diff --git a/addon/dummy-addon/blueprints/dummy-blueprint/files/README.md b/addon/dummy-addon/blueprints/dummy-blueprint/files/README.md deleted file mode 100644 index 96c7dfad0de2..000000000000 --- a/addon/dummy-addon/blueprints/dummy-blueprint/files/README.md +++ /dev/null @@ -1 +0,0 @@ -The dummy blueprint just got installed. \ No newline at end of file diff --git a/addon/dummy-addon/blueprints/dummy-blueprint/files/bower.json b/addon/dummy-addon/blueprints/dummy-blueprint/files/bower.json deleted file mode 100644 index e68ae5262205..000000000000 --- a/addon/dummy-addon/blueprints/dummy-blueprint/files/bower.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "dummy-blueprint", - "main": "index.js", - "version": "0.0.0", - "homepage": "https://github.com/angular/angular-cli", - "authors": [ - "rodyhaddad " - ], - "license": "MIT" -} diff --git a/addon/dummy-addon/blueprints/dummy-blueprint/files/package.json b/addon/dummy-addon/blueprints/dummy-blueprint/files/package.json deleted file mode 100644 index 269587adbe9a..000000000000 --- a/addon/dummy-addon/blueprints/dummy-blueprint/files/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "dummy-blueprint", - "version": "0.0.0", - "description": "A dummy blueprint", - "author": "rodyhaddad", - "license": "MIT" -} \ No newline at end of file diff --git a/addon/dummy-addon/blueprints/dummy-blueprint/index.js b/addon/dummy-addon/blueprints/dummy-blueprint/index.js deleted file mode 100644 index 41a516d29c0d..000000000000 --- a/addon/dummy-addon/blueprints/dummy-blueprint/index.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - description: '' - - // locals: function(options) { - // // Return custom template variables here. - // return { - // foo: options.entity.options.foo - // }; - // } - - // afterInstall: function(options) { - // // Perform extra work here. - // } -}; diff --git a/addon/dummy-addon/index.js b/addon/dummy-addon/index.js deleted file mode 100644 index 293c0d5241a9..000000000000 --- a/addon/dummy-addon/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/* jshint node: true */ -'use strict'; - -module.exports = { - name: 'dummy-addon' -}; diff --git a/addon/dummy-addon/package.json b/addon/dummy-addon/package.json deleted file mode 100644 index 2082d6a4c6be..000000000000 --- a/addon/dummy-addon/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "dummy-addon", - "version": "0.0.0", - "description": "An addon to generate an ng2 project", - "author": "rodyhaddad", - "license": "MIT", - "keywords": [ - "ember-addon" - ] -} \ No newline at end of file diff --git a/addon/ng2/blueprints/component-test/files/src/app/components/__name__/__name__.spec.ts b/addon/ng2/blueprints/component-test/files/src/app/components/__name__/__name__.spec.ts deleted file mode 100644 index 3352d5d81e4e..000000000000 --- a/addon/ng2/blueprints/component-test/files/src/app/components/__name__/__name__.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - it, - iit, - describe, - ddescribe, - expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders -} from 'angular2/testing'; -import {provide} from 'angular2/core'; -import {<%= classifiedModuleName %>} from './<%= dasherizedModuleName %>'; - - -describe('<%= classifiedModuleName %> Component', () => { - - beforeEachProviders((): any[] => []); - - - it('should ...', injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => { - return tcb.createAsync(<%= classifiedModuleName %>).then((fixture) => { - fixture.detectChanges(); - }); - })); - -}); diff --git a/addon/ng2/blueprints/component-test/index.js b/addon/ng2/blueprints/component-test/index.js deleted file mode 100644 index 8540d5f2f309..000000000000 --- a/addon/ng2/blueprints/component-test/index.js +++ /dev/null @@ -1,16 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '' - - //locals: function(options) { - // // Return custom template variables here. - // return { - // - // }; - //} - - // afterInstall: function(options) { - // // Perform extra work here. - // } -}; diff --git a/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.html b/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.html deleted file mode 100644 index 7d15d15ab3c9..000000000000 --- a/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.html +++ /dev/null @@ -1,3 +0,0 @@ -

- <%= dasherizedModuleName %> Works! -

\ No newline at end of file diff --git a/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.ts b/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.ts deleted file mode 100644 index bea0720ce449..000000000000 --- a/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Component} from 'angular2/core'; - - -@Component({ - selector: '<%= dasherizedModuleName %>', - templateUrl: 'app/components/<%= dasherizedModuleName %>/<%= dasherizedModuleName %>.html', - styleUrls: ['app/components/<%= dasherizedModuleName %>/<%= dasherizedModuleName %>.css'], - providers: [], - directives: [], - pipes: [] -}) -export class <%= classifiedModuleName %> { - - constructor() {} - -} diff --git a/addon/ng2/blueprints/component/index.js b/addon/ng2/blueprints/component/index.js deleted file mode 100644 index 8540d5f2f309..000000000000 --- a/addon/ng2/blueprints/component/index.js +++ /dev/null @@ -1,16 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '' - - //locals: function(options) { - // // Return custom template variables here. - // return { - // - // }; - //} - - // afterInstall: function(options) { - // // Perform extra work here. - // } -}; diff --git a/addon/ng2/blueprints/directive/files/src/app/directives/__name__/__name__.spec.ts b/addon/ng2/blueprints/directive/files/src/app/directives/__name__/__name__.spec.ts deleted file mode 100644 index 0e5c50473136..000000000000 --- a/addon/ng2/blueprints/directive/files/src/app/directives/__name__/__name__.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - it, - iit, - describe, - ddescribe, - expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders -} from 'angular2/testing'; -import {provide, Component} from 'angular2/core'; -import {<%= classifiedModuleName %>} from './<%= dasherizedModuleName %>'; - - -@Component({ - selector: 'test-component', - template: `
>
` -}) -class TestComponent {} - -describe('<%= classifiedModuleName %> Directive', () => { - - beforeEachProviders((): any[] => []); - - - it('should ...', injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => { - return tcb.createAsync(TestComponent).then((fixture) => { - fixture.detectChanges(); - }); - })); - -}); diff --git a/addon/ng2/blueprints/directive/files/src/app/directives/__name__/__name__.ts b/addon/ng2/blueprints/directive/files/src/app/directives/__name__/__name__.ts deleted file mode 100644 index bcbc6d6e779a..000000000000 --- a/addon/ng2/blueprints/directive/files/src/app/directives/__name__/__name__.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Directive} from 'angular2/core'; - - -@Directive({ - selector: '<%= dasherizedModuleName %>', - providers: [], - host: {}, - -}) -export class <%= classifiedModuleName %> { - - constructor() {} - -} diff --git a/addon/ng2/blueprints/ng2/files/.clang-format b/addon/ng2/blueprints/ng2/files/.clang-format deleted file mode 100644 index 8d1c3c310286..000000000000 --- a/addon/ng2/blueprints/ng2/files/.clang-format +++ /dev/null @@ -1,3 +0,0 @@ -Language: JavaScript -BasedOnStyle: Google -ColumnLimit: 100 diff --git a/addon/ng2/blueprints/ng2/files/config/environment.js b/addon/ng2/blueprints/ng2/files/config/environment.js deleted file mode 100644 index 4fa28880da9f..000000000000 --- a/addon/ng2/blueprints/ng2/files/config/environment.js +++ /dev/null @@ -1,10 +0,0 @@ -/* jshint node: true */ - -module.exports = function(environment) { - return { - environment: environment, - baseURL: '/', - locationType: 'auto' - }; -}; - diff --git a/addon/ng2/blueprints/ng2/files/e2e/app.e2e.ts b/addon/ng2/blueprints/ng2/files/e2e/app.e2e.ts deleted file mode 100644 index b299821de396..000000000000 --- a/addon/ng2/blueprints/ng2/files/e2e/app.e2e.ts +++ /dev/null @@ -1,15 +0,0 @@ -import "angular2/testing"; -import { <%= jsComponentName %>Page } from './app.po'; - -describe('<%= htmlComponentName %> App', function() { - let page: <%= jsComponentName %>Page; - - beforeEach(() => { - page = new <%= jsComponentName %>Page(); - }) - - it('should display message saying app works', () => { - page.navigateTo() - expect(page.getParagraphText()).toEqual('<%= htmlComponentName %> Works!'); - }); -}); diff --git a/addon/ng2/blueprints/ng2/files/e2e/app.po.ts b/addon/ng2/blueprints/ng2/files/e2e/app.po.ts deleted file mode 100644 index 43e99b59c38c..000000000000 --- a/addon/ng2/blueprints/ng2/files/e2e/app.po.ts +++ /dev/null @@ -1,6 +0,0 @@ -import "angular2/testing"; - -export class <%= jsComponentName %>Page { - navigateTo() { return browser.get('/'); } - getParagraphText() { return element(by.css('<%= jsComponentName %>-app p')).getText(); } -} diff --git a/addon/ng2/blueprints/ng2/files/e2e/tsconfig.json b/addon/ng2/blueprints/ng2/files/e2e/tsconfig.json deleted file mode 100644 index 304804ccabda..000000000000 --- a/addon/ng2/blueprints/ng2/files/e2e/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES5", - "module": "commonjs", - "sourceMap": true, - "declaration": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false - } -} diff --git a/addon/ng2/blueprints/ng2/files/ember-cli-build.js b/addon/ng2/blueprints/ng2/files/ember-cli-build.js deleted file mode 100644 index 6485a5c3f8ad..000000000000 --- a/addon/ng2/blueprints/ng2/files/ember-cli-build.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global require, module */ - -var Angular2App = require('angular-cli/lib/broccoli/angular2-app'); - -module.exports = function(defaults) { - var app = new Angular2App(defaults, { - vendorNpmFiles: [] - }); - return app.toTree(); -} \ No newline at end of file diff --git a/addon/ng2/blueprints/ng2/files/gitignore b/addon/ng2/blueprints/ng2/files/gitignore deleted file mode 100644 index e581b3d67893..000000000000 --- a/addon/ng2/blueprints/ng2/files/gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp - -# dependencies -/node_modules -/bower_components - -# IDEs and editors -/.idea - -# misc -/.sass-cache -/connect.lock -/coverage/* -/libpeerconnection.log -npm-debug.log -testem.log -/typings - -# e2e -/e2e/*.js -/e2e/*.map - diff --git a/addon/ng2/blueprints/ng2/files/karma-test-shim.js b/addon/ng2/blueprints/ng2/files/karma-test-shim.js deleted file mode 100644 index a62ac95bc9b7..000000000000 --- a/addon/ng2/blueprints/ng2/files/karma-test-shim.js +++ /dev/null @@ -1,47 +0,0 @@ -Error.stackTraceLimit = Infinity; - -jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; - -__karma__.loaded = function() {}; - -System.config({ - packages: { - 'base/dist/app': { - defaultExtension: false, - format: 'register', - map: Object.keys(window.__karma__.files) - .filter(onlyAppFiles) - .reduce(function(pathsMapping, appPath) { - var moduleName = appPath.replace(/^\/base\/dist\/app\//, './').replace(/\.js$/, ''); - pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath] - return pathsMapping; - }, {}) - } - } -}); - -System.import('angular2/testing').then(function(testing) { - return System.import('angular2/platform/testing/browser').then(function(providers) { - testing.setBaseTestProviders(providers.TEST_BROWSER_PLATFORM_PROVIDERS, - providers.TEST_BROWSER_APPLICATION_PROVIDERS); - }); -}).then(function() { - return Promise.all( - Object.keys(window.__karma__.files) - .filter(onlySpecFiles) - .map(function(moduleName) { - return System.import(moduleName); - })); -}).then(function() { - __karma__.start(); -}, function(error) { - __karma__.error(error.stack || error); -}); - -function onlyAppFiles(filePath) { - return /^\/base\/dist\/app\/(?!.*\.spec\.js$)([a-z0-9-_\.\/]+)\.js$/.test(filePath); -} - -function onlySpecFiles(path) { - return /\.spec\.js$/.test(path); -} diff --git a/addon/ng2/blueprints/ng2/files/karma.conf.js b/addon/ng2/blueprints/ng2/files/karma.conf.js deleted file mode 100644 index f95f0a872f3d..000000000000 --- a/addon/ng2/blueprints/ng2/files/karma.conf.js +++ /dev/null @@ -1,56 +0,0 @@ -module.exports = function(config) { - config.set({ - basePath: '', - frameworks: ['jasmine'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher') - ], - customLaunchers: { - // chrome setup for travis CI using chromium - Chrome_travis_ci: { - base: 'Chrome', - flags: ['--no-sandbox'] - }, - }, - files: [ - {pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: true, watched: true}, - {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true}, - {pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true}, - {pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', included: true, watched: true}, - {pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true}, - {pattern: 'node_modules/angular2/bundles/angular2.js', included: true, watched: true}, - {pattern: 'node_modules/angular2/bundles/http.dev.js', included: true, watched: true}, - {pattern: 'node_modules/angular2/bundles/router.dev.js', included: true, watched: true}, - {pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: true}, - - - {pattern: 'karma-test-shim.js', included: true, watched: true}, - - // paths loaded via module imports - {pattern: 'dist/**/*.js', included: false, watched: true}, - - // paths loaded via Angular's component compiler - // (these paths need to be rewritten, see proxies section) - {pattern: 'dist/**/*.html', included: false, watched: true}, - {pattern: 'dist/**/*.css', included: false, watched: true}, - - // paths to support debugging with source maps in dev tools - {pattern: 'dist/**/*.ts', included: false, watched: false}, - {pattern: 'dist/**/*.js.map', included: false, watched: false} - ], - proxies: { - // required for component assets fetched by Angular's compiler - "/app/": "/base/dist/app/" - }, - exclude: [], - preprocessors: {}, - reporters: ['progress'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false - }); -}; diff --git a/addon/ng2/blueprints/ng2/files/package.json b/addon/ng2/blueprints/ng2/files/package.json deleted file mode 100644 index 3c2779f7b510..000000000000 --- a/addon/ng2/blueprints/ng2/files/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "<%= htmlComponentName %>", - "version": "0.0.0", - "license": "Apache-2.0", - "angular-cli": { - "notifications": { - "enabled": true, - "playSound": false - } - }, - "scripts": { - "start": "ng server", - "postinstall": "typings install --ambient", - "lint": "tslint src/**/*.ts", - "format": "clang-format -i -style=file --glob=src/**/*.ts" - }, - "private": true, - "dependencies": { - "angular2": "2.0.0-beta.6", - "clang-format": "^1.0.35", - "es6-promise": "^3.0.2", - "es6-shim": "^0.33.3", - "reflect-metadata": "0.1.2", - "rxjs": "5.0.0-beta.0", - "systemjs": "0.19.20" - }, - "devDependencies": { - "angular-cli": "0.0.*", - "angular-cli-github-pages": "^0.2.0", - "ember-cli-inject-live-reload": "^1.3.0", - "glob": "^6.0.4", - "jasmine-core": "^2.3.4", - "karma": "^0.13.15", - "karma-chrome-launcher": "^0.2.1", - "karma-jasmine": "^0.3.6", - "protractor": "^3.0.0", - "tslint": "^3.3.0", - "typescript": "^1.7.3", - "typings": "^0.6.6" - } -} diff --git a/addon/ng2/blueprints/ng2/files/protractor.conf.js b/addon/ng2/blueprints/ng2/files/protractor.conf.js deleted file mode 100644 index d51992dfd1b3..000000000000 --- a/addon/ng2/blueprints/ng2/files/protractor.conf.js +++ /dev/null @@ -1,28 +0,0 @@ -exports.config = { - allScriptsTimeout: 11000, - - specs: [ - 'e2e/**/*.e2e.js' - ], - - capabilities: { - 'browserName': 'chrome' - }, - - directConnect: true, - - baseUrl: 'http://localhost:4200/', - - framework: 'jasmine', - - jasmineNodeOpts: { - defaultTimeoutInterval: 30000 - }, - - useAllAngular2AppRoots: true, - - beforeLaunch: function() { - require('zone.js'); - require('reflect-metadata'); - } -}; diff --git a/addon/ng2/blueprints/ng2/files/src/app.ts b/addon/ng2/blueprints/ng2/files/src/app.ts deleted file mode 100644 index 01383e781c51..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/app.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {bootstrap} from 'angular2/platform/browser'; -import {<%= jsComponentName %>App} from './app/<%= htmlComponentName %>'; -import {ROUTER_PROVIDERS} from 'angular2/router'; - -bootstrap(<%= jsComponentName %>App, [ - ROUTER_PROVIDERS -]); diff --git a/addon/ng2/blueprints/ng2/files/src/app/__name__.html b/addon/ng2/blueprints/ng2/files/src/app/__name__.html deleted file mode 100644 index 2661fb937f37..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/app/__name__.html +++ /dev/null @@ -1,5 +0,0 @@ -

- <%= htmlComponentName %> Works! -

- - diff --git a/addon/ng2/blueprints/ng2/files/src/app/__name__.spec.ts b/addon/ng2/blueprints/ng2/files/src/app/__name__.spec.ts deleted file mode 100644 index f118dc87b206..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/app/__name__.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {describe, it, expect, beforeEachProviders, inject} from 'angular2/testing'; -import {<%= jsComponentName %>App} from '../app/<%= htmlComponentName %>'; - -beforeEachProviders(() => [<%= jsComponentName %>App]); - -describe('App: <%= jsComponentName %>', () => { - it('should have the `defaultMeaning` as 42', inject([<%= jsComponentName %>App], (app: <%= jsComponentName %>App) => { - expect(app.defaultMeaning).toBe(42); - })); - - describe('#meaningOfLife', () => { - it('should get the meaning of life', inject([<%= jsComponentName %>App], (app: <%= jsComponentName %>App) => { - expect(app.meaningOfLife()).toBe('The meaning of life is 42'); - expect(app.meaningOfLife(22)).toBe('The meaning of life is 22'); - })); - }); -}); - diff --git a/addon/ng2/blueprints/ng2/files/src/app/__name__.ts b/addon/ng2/blueprints/ng2/files/src/app/__name__.ts deleted file mode 100644 index 4426740f4e4c..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/app/__name__.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Component} from 'angular2/core'; -import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'; - - -@Component({ - selector: '<%= htmlComponentName %>-app', - providers: [], - templateUrl: 'app/<%= htmlComponentName %>.html', - directives: [ROUTER_DIRECTIVES], - pipes: [] -}) -@RouteConfig([ - -]) -export class <%= jsComponentName %>App { - defaultMeaning: number = 42; - - meaningOfLife(meaning?: number) { - return `The meaning of life is ${meaning || this.defaultMeaning}`; - } -} diff --git a/addon/ng2/blueprints/ng2/files/src/index.html b/addon/ng2/blueprints/ng2/files/src/index.html deleted file mode 100644 index bc847cb45a05..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - <%= jsComponentName %> - - {{content-for 'head'}} - - - - <<%= htmlComponentName %>-app>Loading...-app> - - - - - - diff --git a/addon/ng2/blueprints/ng2/files/src/tsconfig.json b/addon/ng2/blueprints/ng2/files/src/tsconfig.json deleted file mode 100644 index 87d7f339fd9a..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "declaration": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "mapRoot": "", - "module": "system", - "moduleResolution": "node", - "noEmitOnError": true, - "noImplicitAny": false, - "outDir": "../dist/", - "rootDir": ".", - "sourceMap": true, - "sourceRoot": "/", - "target": "es5" - } -} diff --git a/addon/ng2/blueprints/ng2/files/src/typings.d.ts b/addon/ng2/blueprints/ng2/files/src/typings.d.ts deleted file mode 100644 index beb6cefcecec..000000000000 --- a/addon/ng2/blueprints/ng2/files/src/typings.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/addon/ng2/blueprints/ng2/files/tslint.json b/addon/ng2/blueprints/ng2/files/tslint.json deleted file mode 100644 index c9cd1dd37911..000000000000 --- a/addon/ng2/blueprints/ng2/files/tslint.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "rules": { - "max-line-length": [true, 100], - "no-inferrable-types": true, - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "indent": [ - true, - "spaces" - ], - "eofline": true, - "no-duplicate-variable": true, - "no-eval": true, - "no-arg": true, - "no-internal-module": true, - "no-trailing-whitespace": true, - "no-bitwise": true, - "no-shadowed-variable": true, - "no-unused-expression": true, - "no-unused-variable": true, - "one-line": [ - true, - "check-catch", - "check-else", - "check-open-brace", - "check-whitespace" - ], - "quotemark": [ - true, - "single", - "avoid-escape" - ], - "semicolon": true, - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "curly": true, - "variable-name": [ - true, - "ban-keywords", - "check-format", - "allow-trailing-underscore" - ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] - } -} diff --git a/addon/ng2/blueprints/ng2/files/typings.json b/addon/ng2/blueprints/ng2/files/typings.json deleted file mode 100644 index 66c0b4ee23de..000000000000 --- a/addon/ng2/blueprints/ng2/files/typings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": {}, - "devDependencies": { - }, - "ambientDevDependencies": { - "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#26c98c8a9530c44f8c801ccc3b2057e2101187ee" - }, - "ambientDependencies": { - "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2" - } -} diff --git a/addon/ng2/blueprints/ng2/index.js b/addon/ng2/blueprints/ng2/index.js deleted file mode 100644 index 1da0d1bef363..000000000000 --- a/addon/ng2/blueprints/ng2/index.js +++ /dev/null @@ -1,17 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '', - - locals: function(options) { - // Return custom template variables here. - return { - htmlComponentName: stringUtils.dasherize(options.entity.name), - jsComponentName: stringUtils.classify(options.entity.name) - }; - } - - // afterInstall: function(options) { - // // Perform extra work here. - // } -}; diff --git a/addon/ng2/blueprints/pipe/files/src/app/pipes/__name__/__name__.spec.ts b/addon/ng2/blueprints/pipe/files/src/app/pipes/__name__/__name__.spec.ts deleted file mode 100644 index c9a8817c7830..000000000000 --- a/addon/ng2/blueprints/pipe/files/src/app/pipes/__name__/__name__.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - it, - iit, - describe, - ddescribe, - expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders -} from 'angular2/testing'; -import {provide} from 'angular2/core'; -import {<%= classifiedModuleName %>} from './<%= dasherizedModuleName %>'; - - -describe('<%= classifiedModuleName %> Pipe', () => { - - beforeEachProviders(() => [<%= classifiedModuleName%>]); - - - it('should transform the input', inject([<%= classifiedModuleName %>], (pipe:<%= classifiedModuleName %>) => { - expect(pipe.transform(true)).toBe(null); - })); - -}); diff --git a/addon/ng2/blueprints/pipe/files/src/app/pipes/__name__/__name__.ts b/addon/ng2/blueprints/pipe/files/src/app/pipes/__name__/__name__.ts deleted file mode 100644 index 5d0bfcceb466..000000000000 --- a/addon/ng2/blueprints/pipe/files/src/app/pipes/__name__/__name__.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Pipe, PipeTransform} from 'angular2/core'; - - -@Pipe({ - name: '<%= classifiedModuleName %>' -}) -export class <%= classifiedModuleName %> implements PipeTransform { - - transform(value: any, args?: any): any { - return null; - } - -} diff --git a/addon/ng2/blueprints/route-test/index.js b/addon/ng2/blueprints/route-test/index.js deleted file mode 100644 index e1d2a516ee13..000000000000 --- a/addon/ng2/blueprints/route-test/index.js +++ /dev/null @@ -1,14 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '', - - // ****************************************************** - // ****************************************************** - // LEAVE THIS HERE - // Must override install to prevent ember's route tests - // ****************************************************** - // ****************************************************** - - install: function(options){} -}; diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.html b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.html deleted file mode 100644 index 163f61d7719c..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
-

"{{editName}}"

-
- - {{<%= camelizedModuleName %>.id}} -
-
- - -
- - -
diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.spec.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.spec.ts deleted file mode 100644 index 8ec3d9c298c1..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - it, - iit, - describe, - ddescribe, - expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders -} from 'angular2/testing'; -import {provide} from 'angular2/core'; -import {<%= classifiedModuleName %>DetailComponent} from './<%= dasherizedModuleName %>-detail.component'; -import {Router, RouteParams} from 'angular2/router'; -import {<%= classifiedModuleName %>, <%= classifiedModuleName %>Service} from './<%= dasherizedModuleName %>.service'; - -class Mock<%= classifiedModuleName %>Service { - get() { return Promise.resolve(new <%= classifiedModuleName %>(1, 'one')); } -} - -class MockRouter { - navigate() { } -} - -class MockRouteParams { - get() { return 1; } -} - -describe('<%= classifiedModuleName %>DetailComponent', () => { - - beforeEachProviders(() => [ - provide(<%= classifiedModuleName %>Service, {useClass: Mock<%= classifiedModuleName %>Service}), - provide(Router, {useClass: MockRouter}), - provide(RouteParams, {useClass: MockRouteParams}), - ]); - - it('should ...', injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => { - return tcb.createAsync(<%= classifiedModuleName %>DetailComponent).then((fixture) => { - fixture.detectChanges(); - }); - })); - -}); diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.ts deleted file mode 100644 index ed6f1fa4db33..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {Component, OnInit} from 'angular2/core'; -import {<%= classifiedModuleName %>, <%= classifiedModuleName %>Service} from './<%= dasherizedModuleName %>.service'; -import {RouteParams, Router} from 'angular2/router'; -import {CanDeactivate, ComponentInstruction} from 'angular2/router'; - -@Component({ - templateUrl: 'app/<%= dasherizedModuleName %>/<%= dasherizedModuleName %>-detail.component.html', - styleUrls: ['app/<%= dasherizedModuleName %>/<%= dasherizedModuleName %>-detail.component.css'] -}) -export class <%= classifiedModuleName %>DetailComponent implements OnInit, CanDeactivate { - - <%= camelizedModuleName %>: <%= classifiedModuleName %>; - editName: string; - - constructor( - private _service: <%= classifiedModuleName %>Service, - private _router: Router, - private _routeParams: RouteParams - ) { } - - ngOnInit() { - let id = +this._routeParams.get('id'); - this._service.get(id).then(<%= camelizedModuleName %> => { - if (<%= camelizedModuleName %>) { - this.editName = <%= camelizedModuleName %>.name; - this.<%= camelizedModuleName %> = <%= camelizedModuleName %>; - } else { - this.gotoList(); - } - }); - } - - routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction): any { - if (!this.<%= camelizedModuleName %> || this.<%= camelizedModuleName %>.name === this.editName) { - return true; - } - - return new Promise((resolve, reject) => resolve(window.confirm('Discard changes?'))); - } - - cancel() { - this.editName = this.<%= camelizedModuleName %>.name; - this.gotoList(); - } - - save() { - this.<%= camelizedModuleName %>.name = this.editName; - this.gotoList(); - } - - gotoList() { - this._router.navigate(['<%= classifiedModuleName %>List']); - } -} diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.html b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.html deleted file mode 100644 index 1a7902798519..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.html +++ /dev/null @@ -1,12 +0,0 @@ -

<%= classifiedModuleName %> List

- - diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.spec.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.spec.ts deleted file mode 100644 index 73027af8a895..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - it, - iit, - describe, - ddescribe, - expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders -} from 'angular2/testing'; -import {provide} from 'angular2/core'; -import {<%= classifiedModuleName %>ListComponent} from './<%= dasherizedModuleName %>-list.component'; -import {<%= classifiedModuleName %>, <%= classifiedModuleName %>Service} from './<%= dasherizedModuleName %>.service'; - -class Mock<%= classifiedModuleName %>Service { - getAll() { return Promise.resolve([new <%= classifiedModuleName %>(1, 'one')]); } -} - -describe('<%= classifiedModuleName %>ListComponent', () => { - - beforeEachProviders(() => [ - provide(<%= classifiedModuleName %>Service, {useClass: Mock<%= classifiedModuleName %>Service}), - ]); - - it('should ...', injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => { - return tcb.createAsync(<%= classifiedModuleName %>ListComponent).then((fixture) => { - fixture.detectChanges(); - }); - })); - -}); diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.ts deleted file mode 100644 index b2feeae1cac6..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-list.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Component, OnInit} from 'angular2/core'; -import {<%= classifiedModuleName %>, <%= classifiedModuleName %>Service} from './<%= dasherizedModuleName %>.service'; -import {ROUTER_DIRECTIVES} from 'angular2/router'; - -@Component({ - templateUrl: 'app/<%= dasherizedModuleName %>/<%= dasherizedModuleName %>-list.component.html', - styleUrls: ['app/<%= dasherizedModuleName %>/<%= dasherizedModuleName %>-list.component.css'], - directives: [ROUTER_DIRECTIVES] -}) -export class <%= classifiedModuleName %>ListComponent implements OnInit { - <%= camelizedModuleName %>s: <%= classifiedModuleName %>[]; - constructor( - private _service: <%= classifiedModuleName %>Service) {} - ngOnInit() { - this._service.getAll().then(<%= camelizedModuleName %>s => this.<%= camelizedModuleName %>s = <%= camelizedModuleName %>s); - } -} diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-root.component.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__-root.component.ts deleted file mode 100644 index 515ad419a4ce..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-root.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Component} from 'angular2/core'; -import {RouteConfig, RouterOutlet} from 'angular2/router'; - -import {<%= classifiedModuleName %>ListComponent} from './<%= dasherizedModuleName %>-list.component'; -import {<%= classifiedModuleName %>DetailComponent} from './<%= dasherizedModuleName %>-detail.component'; -import {<%= classifiedModuleName %>Service} from './<%= dasherizedModuleName %>.service'; - -@Component({ - template: '', - providers: [<%= classifiedModuleName %>Service], - directives: [RouterOutlet] -}) -@RouteConfig([ - {path:'/', name: '<%= classifiedModuleName %>List', component: <%= classifiedModuleName %>ListComponent, useAsDefault: true}, - {path:'/:id', name: '<%= classifiedModuleName %>Detail', component: <%= classifiedModuleName %>DetailComponent} -]) -export class <%= classifiedModuleName %>Root { - constructor() {} -} diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__.service.spec.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__.service.spec.ts deleted file mode 100644 index 09fc4e9916aa..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__.service.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {describe, it, expect, beforeEachProviders, inject} from 'angular2/testing'; -import {provide} from 'angular2/core'; -import {<%= classifiedModuleName %>Service} from './<%= dasherizedModuleName %>.service'; - -describe('<%= classifiedModuleName %>Service', () => { - - beforeEachProviders(() => [<%= classifiedModuleName %>Service]); - - it('should get all <%= camelizedModuleName %>s', inject([<%= classifiedModuleName %>Service], (<%= camelizedModuleName %>Service:<%= classifiedModuleName %>Service) => { - <%= camelizedModuleName %>Service.getAll().then(<%= camelizedModuleName %>s => expect(<%= camelizedModuleName %>s.length).toBe(3)); - })); - - it('should get one <%= camelizedModuleName %>', inject([<%= classifiedModuleName %>Service], (<%= camelizedModuleName %>Service:<%= classifiedModuleName %>Service) => { - <%= camelizedModuleName %>Service.get(1).then(<%= camelizedModuleName %> => expect(<%= camelizedModuleName %>.id).toBe(1)); - })); - -}); diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__.service.ts b/addon/ng2/blueprints/route/files/src/app/__name__/__name__.service.ts deleted file mode 100644 index 7a1122395f97..000000000000 --- a/addon/ng2/blueprints/route/files/src/app/__name__/__name__.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Injectable} from 'angular2/core'; - -export class <%= classifiedModuleName %> { - constructor(public id: number, public name: string) { } -} - -@Injectable() -export class <%= classifiedModuleName %>Service { - getAll() { return promise; } - get(id: number) { - return promise.then(all => all.find(e => e.id === id)); - } -} - -let mock = [ - new <%= classifiedModuleName %>(1, 'one'), - new <%= classifiedModuleName %>(2, 'two'), - new <%= classifiedModuleName %>(3, 'three') -]; - -let promise = Promise.resolve(mock); diff --git a/addon/ng2/blueprints/route/index.js b/addon/ng2/blueprints/route/index.js deleted file mode 100644 index 8540d5f2f309..000000000000 --- a/addon/ng2/blueprints/route/index.js +++ /dev/null @@ -1,16 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '' - - //locals: function(options) { - // // Return custom template variables here. - // return { - // - // }; - //} - - // afterInstall: function(options) { - // // Perform extra work here. - // } -}; diff --git a/addon/ng2/blueprints/service-test/index.js b/addon/ng2/blueprints/service-test/index.js deleted file mode 100644 index b5a248591b86..000000000000 --- a/addon/ng2/blueprints/service-test/index.js +++ /dev/null @@ -1,14 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '', - - // ****************************************************** - // ****************************************************** - // LEAVE THIS HERE - // Must override install to prevent ember's service tests - // ****************************************************** - // ****************************************************** - - install: function(options){} -}; diff --git a/addon/ng2/blueprints/service/files/src/app/services/__name__/__name__.spec.ts b/addon/ng2/blueprints/service/files/src/app/services/__name__/__name__.spec.ts deleted file mode 100644 index 5650fee31b81..000000000000 --- a/addon/ng2/blueprints/service/files/src/app/services/__name__/__name__.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - it, - iit, - describe, - ddescribe, - expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders -} from 'angular2/testing'; -import {provide} from 'angular2/core'; -import {<%= classifiedModuleName %>} from './<%= dasherizedModuleName %>'; - - -describe('<%= classifiedModuleName %> Service', () => { - - beforeEachProviders(() => [<%= classifiedModuleName %>]); - - - it('should ...', inject([<%= classifiedModuleName %>], (service:<%= classifiedModuleName %>) => { - - })); - -}); diff --git a/addon/ng2/blueprints/service/files/src/app/services/__name__/__name__.ts b/addon/ng2/blueprints/service/files/src/app/services/__name__/__name__.ts deleted file mode 100644 index b6affa0cde77..000000000000 --- a/addon/ng2/blueprints/service/files/src/app/services/__name__/__name__.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Injectable} from 'angular2/core'; - - -@Injectable() -export class <%= classifiedModuleName %> { - - constructor() {} - -} diff --git a/addon/ng2/blueprints/service/index.js b/addon/ng2/blueprints/service/index.js deleted file mode 100644 index 8540d5f2f309..000000000000 --- a/addon/ng2/blueprints/service/index.js +++ /dev/null @@ -1,16 +0,0 @@ -var stringUtils = require('ember-cli/lib/utilities/string'); - -module.exports = { - description: '' - - //locals: function(options) { - // // Return custom template variables here. - // return { - // - // }; - //} - - // afterInstall: function(options) { - // // Perform extra work here. - // } -}; diff --git a/addon/ng2/commands/format.js b/addon/ng2/commands/format.js deleted file mode 100644 index 29aeebab949c..000000000000 --- a/addon/ng2/commands/format.js +++ /dev/null @@ -1,20 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Command = require('ember-cli/lib/models/command'); -var FormatTask = require('../tasks/format'); - -module.exports = Command.extend({ - name: 'format', - description: 'Formats code in existing project', - works: 'insideProject', - run: function() { - var formatTask = new FormatTask({ - ui: this.ui, - analytics: this.analytics, - project: this.project - }); - - return formatTask.run(); - } -}); diff --git a/addon/ng2/commands/init.js b/addon/ng2/commands/init.js deleted file mode 100644 index b2deca2278a9..000000000000 --- a/addon/ng2/commands/init.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var InitCommand = require('ember-cli/lib/commands/init'); - -module.exports = InitCommand .extend({ - availableOptions: [ - { name: 'dry-run', type: Boolean, default: false, aliases: ['d'] }, - { name: 'verbose', type: Boolean, default: false, aliases: ['v'] }, - { name: 'blueprint', type: String, aliases: ['b'] }, - { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] }, - { name: 'skip-bower', type: Boolean, default: true, aliases: ['sb'] }, - { name: 'name', type: String, default: '', aliases: ['n'] } - ], - - _defaultBlueprint: function() { - if (this.project.isEmberCLIAddon()) { - return 'addon'; - } else { - return 'ng2'; - } - }, -}); - -module.exports.overrideCore = true; diff --git a/addon/ng2/commands/install.js b/addon/ng2/commands/install.js deleted file mode 100644 index 10cab18d6cd0..000000000000 --- a/addon/ng2/commands/install.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -var Command = require('ember-cli/lib/models/command'); -var SilentError = require('silent-error'); -var Promise = require('ember-cli/lib/ext/promise'); -var LibInstallTask = require('../tasks/lib-install'); - -module.exports = Command.extend({ - name: 'install', - description: 'Adds 3rd party library to existing project', - works: 'insideProject', - availableOptions: [ - { name: 'skip-injection', type: Boolean, default: false, aliases: ['s'] }, - { name: 'auto-injection', type: Boolean, default: false, aliases: ['ai'] }, - ], - run: function (commandOptions, rawArgs) { - if (!rawArgs.length) { - var msg = 'The `ng install` command must take an argument with ' + - 'a package name.'; - - return Promise.reject(new SilentError(msg)); - } - - var libInstallTask = new LibInstallTask({ - ui: this.ui, - analytics: this.analytics, - project: this.project - }); - - return libInstallTask.run({ - packages: rawArgs, - skipInjection: commandOptions.skipInjection, - autoInjection: commandOptions.autoInjection - }); - } -}); - -module.exports.overrideCore = true; diff --git a/addon/ng2/commands/lint.js b/addon/ng2/commands/lint.js deleted file mode 100644 index 459d3ec39fc9..000000000000 --- a/addon/ng2/commands/lint.js +++ /dev/null @@ -1,20 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Command = require('ember-cli/lib/models/command'); -var LintTask = require('../tasks/lint'); - -module.exports = Command.extend({ - name: 'lint', - description: 'Lints code in existing project', - works: 'insideProject', - run: function() { - var lintTask = new LintTask({ - ui: this.ui, - analytics: this.analytics, - project: this.project - }); - - return lintTask.run(); - } -}); diff --git a/addon/ng2/commands/new.js b/addon/ng2/commands/new.js deleted file mode 100644 index 2bb97a4750a9..000000000000 --- a/addon/ng2/commands/new.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); -var Command = require('ember-cli/lib/models/command'); -var Promise = require('ember-cli/lib/ext/promise'); -var Project = require('ember-cli/lib/models/project'); -var SilentError = require('silent-error'); -var validProjectName = require('ember-cli/lib/utilities/valid-project-name'); -var normalizeBlueprint = require('ember-cli/lib/utilities/normalize-blueprint-option'); - -var NewCommand = require('ember-cli/lib/commands/new'); -var GitInit = require('../tasks/git-init'); - -module.exports = NewCommand.extend({ - availableOptions: [ - { name: 'dry-run', type: Boolean, default: false, aliases: ['d'] }, - { name: 'verbose', type: Boolean, default: false, aliases: ['v'] }, - { name: 'blueprint', type: String, default: 'ng2', aliases: ['b'] }, - { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] }, - { name: 'skip-bower', type: Boolean, default: true, aliases: ['sb'] }, - { name: 'skip-git', type: Boolean, default: false, aliases: ['sg'] }, - { name: 'directory', type: String , aliases: ['dir'] } - ], - run: function(commandOptions, rawArgs) { - var packageName = rawArgs[0], - message; - - commandOptions.name = rawArgs.shift(); - - if (!packageName) { - message = chalk.yellow('The `ng ' + this.name + '` command requires a ' + - 'name argument to be specified. For more details, use `ng help`.'); - - return Promise.reject(new SilentError(message)); - } - - if (commandOptions.dryRun){ - commandOptions.skipGit = true; - } - - if (packageName === '.') { - message = 'Trying to generate an application structure in this directory? Use `ng init` instead.'; - - return Promise.reject(new SilentError(message)); - } - - if (!validProjectName(packageName)) { - message = 'We currently do not support a name of `' + packageName + '`.'; - - return Promise.reject(new SilentError(message)); - } - - commandOptions.blueprint = normalizeBlueprint(commandOptions.blueprint); - - if (!commandOptions.directory) { - commandOptions.directory = packageName; - } - - var createAndStepIntoDirectory = new this.tasks.CreateAndStepIntoDirectory({ - ui: this.ui, - analytics: this.analytics - }); - var InitCommand = this.commands.Init; - - var gitInit = new GitInit({ - ui: this.ui, - project: this.project - }); - - var initCommand = new InitCommand({ - ui: this.ui, - analytics: this.analytics, - tasks: this.tasks, - project: Project.nullProject(this.ui, this.cli) - }); - - return createAndStepIntoDirectory - .run({ - directoryName: commandOptions.directory, - dryRun: commandOptions.dryRun - }) - .then(initCommand.run.bind(initCommand, commandOptions, rawArgs)) - .then(gitInit.run.bind(gitInit, commandOptions, rawArgs)); - } -}); - -module.exports.overrideCore = true; diff --git a/addon/ng2/commands/test.js b/addon/ng2/commands/test.js deleted file mode 100644 index ef842062dc4e..000000000000 --- a/addon/ng2/commands/test.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -var chalk = require('chalk'); -var Command = require('ember-cli/lib/models/command'); -var Promise = require('ember-cli/lib/ext/promise'); -var Project = require('ember-cli/lib/models/project'); -var SilentError = require('silent-error'); -var validProjectName = require('ember-cli/lib/utilities/valid-project-name'); -var normalizeBlueprint = require('ember-cli/lib/utilities/normalize-blueprint-option'); - -var TestCommand = require('ember-cli/lib/commands/test'); -var TestTask = require('../tasks/test'); -var win = require('ember-cli/lib/utilities/windows-admin'); -var path = require('path'); - - -module.exports = TestCommand.extend({ - availableOptions: [ - { name: 'single-run', type: Boolean, default: true }, - { name: 'auto-watch', type: Boolean }, - { name: 'browsers', type: String }, - { name: 'colors', type: Boolean }, - { name: 'log-level', type: String }, - { name: 'port', type: Number }, - { name: 'reporters', type: String }, - ], - - run: function(commandOptions, rawArgs) { - var BuildTask = this.tasks.Build; - var buildTask = new BuildTask({ - ui: this.ui, - analytics: this.analytics, - project: this.project - }); - var testTask = new TestTask({ - ui: this.ui, - analytics: this.analytics, - project: this.project - }); - - var buildCommandOptions = { - environment: 'development', - outputPath: 'dist/', - watch: false - }; - - var projectRoot = this.project.root; - - return win.checkWindowsElevation(this.ui) - .then(function() { - return buildTask.run(buildCommandOptions); - }) - .then(function(){ - return testTask.run(commandOptions); - }); - } -}); - -module.exports.overrideCore = true; \ No newline at end of file diff --git a/addon/ng2/commands/uninstall.js b/addon/ng2/commands/uninstall.js deleted file mode 100644 index 504c09958efb..000000000000 --- a/addon/ng2/commands/uninstall.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var Command = require('ember-cli/lib/models/command'); -var SilentError = require('silent-error'); -var Promise = require('ember-cli/lib/ext/promise'); -var LibUninstallTask = require('../tasks/lib-uninstall'); - -module.exports = Command.extend({ - name: 'uninstall', - description: 'Removes 3rd party libraries from existing project', - works: 'insideProject', - availableOptions: [ - { name: 'auto-remove', type: Boolean, default: false, aliases: ['ar'] } - ], - run: function (commandOptions, rawArgs) { - if (!rawArgs.length) { - var msg = 'The `ng uninstall` command must take an argument with ' + - 'at least one package name.'; - - return Promise.reject(new SilentError(msg)); - } - - var libUninstallTask = new LibUninstallTask({ - ui: this.ui, - analytics: this.analytics, - project: this.project - }); - - return libUninstallTask.run({ - packages: rawArgs, - autoRemove: commandOptions.autoRemove - }); - } -}); diff --git a/addon/ng2/index.js b/addon/ng2/index.js deleted file mode 100644 index 59d3bacb6e1e..000000000000 --- a/addon/ng2/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/* jshint node: true */ -'use strict'; - -module.exports = { - name: 'ng2', - includedCommands: function() { - return { - 'new' : require('./commands/new'), - 'init' : require('./commands/init'), - 'install' : require('./commands/install'), - 'uninstall' : require('./commands/uninstall'), - 'test' : require('./commands/test'), - 'lint' : require('./commands/lint'), - 'format' : require('./commands/format') - }; - } -}; diff --git a/addon/ng2/package.json b/addon/ng2/package.json deleted file mode 100644 index 53d9f38255cc..000000000000 --- a/addon/ng2/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ng2", - "version": "0.0.0", - "description": "An addon to generate an ng2 project", - "author": "rodyhaddad", - "license": "MIT", - "keywords": [ - "ember-addon" - ] -} \ No newline at end of file diff --git a/addon/ng2/tasks/format.js b/addon/ng2/tasks/format.js deleted file mode 100644 index 06dcbd07802e..000000000000 --- a/addon/ng2/tasks/format.js +++ /dev/null @@ -1,23 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var Task = require('ember-cli/lib/models/task'); -var exec = Promise.denodeify(require('child_process').exec); - -module.exports = Task.extend({ - run: function() { - var chalk = require('chalk'); - var ui = this.ui; - - return exec('npm run format') - .then(function() { - ui.writeLine(chalk.green('Successfully formatted files.')); - }) - .catch(function( /*error*/ ) { - ui.writeLine(chalk.red( - 'Couldn\'t do \'npm run format\'. Please check this script exists in your package.json.' - )); - }); - } -}); diff --git a/addon/ng2/tasks/git-init.js b/addon/ng2/tasks/git-init.js deleted file mode 100644 index c9d1d6387a06..000000000000 --- a/addon/ng2/tasks/git-init.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var exec = Promise.denodeify(require('child_process').exec); -var path = require('path'); -var pkg = require('../package.json'); -var fs = require('fs'); -var template = require('lodash/string/template'); - -var gitEnvironmentVariables = { - GIT_AUTHOR_NAME: 'angular-cli', - GIT_AUTHOR_EMAIL: 'angular-cli@angular.io', - get GIT_COMMITTER_NAME(){ return this.GIT_AUTHOR_NAME; }, - get GIT_COMMITTER_EMAIL(){ return this.GIT_AUTHOR_EMAIL; } -}; - -var GitInit = require('ember-cli/lib/tasks/git-init'); - -module.exports = GitInit.extend({ - run: function(commandOptions) { - var chalk = require('chalk'); - var ui = this.ui; - - if(commandOptions.skipGit) { return Promise.resolve(); } - - return exec('git --version') - .then(function() { - return exec('git init') - .then(function() { - return exec('git add .'); - }) - .then(function(){ - var commitTemplate = fs.readFileSync(path.join(__dirname, '../utilities/INITIAL_COMMIT_MESSAGE.txt')); - var commitMessage = template(commitTemplate)(pkg); - return exec('git commit -m "' + commitMessage + '"', {env: gitEnvironmentVariables}); - }) - .then(function(){ - ui.writeLine(chalk.green('Successfully initialized git.')); - }); - }).catch(function(/*error*/){ - // if git is not found or an error was thrown during the `git` - // init process just swallow any errors here - }); - } -}); - -module.exports.overrideCore = true; diff --git a/addon/ng2/tasks/lib-install.js b/addon/ng2/tasks/lib-install.js deleted file mode 100644 index b23f8e14f3ff..000000000000 --- a/addon/ng2/tasks/lib-install.js +++ /dev/null @@ -1,595 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var Task = require('ember-cli/lib/models/task'); -var shellPromise = require ('../utilities/shell-promise'); -var existsSync = require('exists-sync'); -var chalk = require('chalk'); -var path = require('path'); -var fs = require('fs'); -var _ = require('lodash'); -var glob = require('glob'); -var appRoot = path.resolve('./src'); -var nodeModules = path.resolve('./node_modules'); - -module.exports = Task.extend({ - command: '', - completionOKMessage: '', - completionErrorMessage: 'Error installing packages. Did you misspelt it?', - - run: function(options) { - this.packages = options.packages || []; - this.skipInjection = options.skipInjection || false; - this.autoInjection = options.autoInjection || false; - this.disableLogger(); - - this.ui.startProgress(chalk.green('Installing 3rd party package:', - this.packages.join(', ')), chalk.green('.')); - - this.npmOptions = ' --loglevel error --color always --optional --save-dev ' + - '--save-exact ' + !!options['save-exact']; - - var npmCommand = 'npm install ' + this.packages.join(' ') + this.npmOptions; - - return shellPromise(npmCommand) - .then(function (npmresp) { - - if (this.skipInjection) { - return this.announceOKCompletion(); - } - - if (this.autoInjection) { - var pkg = this.packages[0]; - - if (existsSync(path.resolve(process.cwd(), 'src', 'app.ts'))) { - var entryPoint = path.resolve(process.cwd(), 'src', 'app.ts'); - var packageData = this.parseFile(pkg); - this.importInBootstrap(entryPoint, pkg, packageData); - } - - return this.announceOKCompletion(); - } - - return this.installProcedure(); - }.bind(this)) - .then(function(resp) { - return this.announceOKCompletion(); - }.bind(this)); - }, - - installProcedure: function() { - var allPackages = { toUninstall: [], toProcess: [] }; - var pkgSrcPath; - var pkgDirs; - - return new Promise(function(resolve, reject) { - this.packages.forEach(function(pkgName) { - if (this.checkIfPackageIsAuthentic(pkgName)) { - allPackages.toProcess.push(pkgName); - } - else { - allPackages.toUninstall.push(pkgName); - } - - if (allPackages.toUninstall.length) { - var msg = 'Removing packages which were not recognized ' + - 'as authentic: ' + allPackages.toUninstall.join(', '); - this.ui.writeLine(chalk.yellow(msg)); - - var npmCommand = 'npm uninstall ' + allPackages.toUninstall.join(' ') + this.npmOptions; - - return shellPromise(npmCommand) - .then(function() { - return this.processPackages(allPackages.toProcess) - .then(function(resp) { - return resolve(resp); - }); - }.bind(this)); - } - else { - return this.processPackages(allPackages.toProcess) - .then(function(resp) { - return resolve(resp); - }); - } - - }.bind(this)); - }.bind(this)); - }, - - processPackages: function (packages) { - if (!packages.length) { - this.ui.writeLine(chalk.yellow('No package to process. Quitting.')); - return Promise.resolve(); - } else { - this.ui.writeLine(chalk.green('Package successfully installed.')); - - return this.ui.prompt({ - type: 'text', - name: 'confirm', - message: 'Inject the installed package into your app? (Y/n)', - default: function() { return 'Y'; }, - validate: function(value) { - return /[YN]/i.test(value) ? true : 'Enter Y(es) or N(o)'; - } - }) - .then(function(answer) { - if (answer.confirm.toLowerCase().substring(0, 1) === 'n') { - return Promise.resolve(); - } - else { - return this.parsePackage(packages[0]); - } - }.bind(this)); - } - }, - - parsePackage: function (packageName, entryPoint) { - return new Promise(function(resolve, reject) { - var packageData; - - var msg = 'Customize the injection of ' + - chalk.yellow(path.basename(packageName)) + '? (Y/n)'; - - return this.ui.prompt({ - type: 'input', - name: 'option', - message: msg, - default: function () { return 'Y'; }, - validate: function(value) { - return /[YN]/i.test(value) ? true : 'Enter Y(es) or N(o)'; - } - }) - .then(function(answer) { - if (answer.option.toLowerCase().substring(0, 1) === 'y') { - return this.customImport(packageName) - .then(function() { - packageData = this.parseFile(packageName); - if (packageData.Provider && packageData.Provider.length) { - return this.importInBootstrapPrompt(packageName, packageData) - .then(function() { - resolve(); - }); - } else { - return resolve(); - } - }.bind(this)); - } - else { - packageData = this.parseFile(packageName); - if (packageData.Provider && packageData.Provider.length) { - return this.importInBootstrapPrompt(packageName, packageData) - .then(function() { - resolve(); - }); - } - else { - return resolve(); - } - } - }.bind(this)); - }.bind(this)); - }, - - customImport: function (packageName) { - return new Promise(function(resolve, reject) { - var componentType; - var componentIndex; - var fileIndex; - var componentKey; - var msg; - - var packageData = this.parseFile(packageName); - - msg = 'What would you like to inject from ' + chalk.yellow(path.basename(packageName)) + '?'; - Object.keys(packageData).forEach(function(item, index) { - msg += '\n' + chalk.green(index + 1) + ' ' + item; - }); - msg += '\n' + chalk.green('-----------'); - msg += '\n' + chalk.green('q') + ' Quit'; - msg += '\nEnter value:'; - - return this.ui.prompt({ - type: 'input', - name: 'component', - message: msg, - validate: function(value) { - return (value > 0 && value <= Object.keys(packageData).length) || - value === 'q' ? true : 'Enter a valid value'; - } - }) - .then(function(choice) { - componentType = choice.component; - - if (componentType.toLowerCase() === 'q') { - return resolve(); - } - - Object.keys(packageData).forEach(function(item, index) { - if ((index + 1) === parseInt(componentType, 10)) { - componentKey = item; - } - }); - - msg = '\nWhich ' + componentKey + ' would you like to inject?'; - packageData[componentKey].forEach(function(item, index) { - msg += '\n' + chalk.green(index + 1) + ' ' + item; - }); - msg += '\nEnter value:'; - - return this.ui.prompt({ - type: 'input', - name: 'sel', - message: msg, - filter: function (val) { return val.trim(); }, - validate: function(value) { - return value > 0 && value <= packageData[componentKey].length ? true : 'Enter a valid value'; - } - }) - .then(function(choice) { - componentIndex = choice.sel - 1; - - msg = '\nWhere would you like to inject it?'; - var possibleFiles = glob.sync(appRoot + '/**/*.ts').sort(); - possibleFiles.forEach(function(file, index) { - msg += '\n' + chalk.green(index + 1) + ' ' + file; - }); - msg += '\nEnter value:'; - - return this.ui.prompt({ - type: 'input', - name: 'file', - message: msg, - validate: function(value) { - return value > 0 && value <= possibleFiles.length ? true : 'Enter a valid value'; - } - }) - .then(function(choice) { - fileIndex = choice.file - 1; - - this.ui.writeLine(chalk.yellow('Injecting', componentKey + - ' (' + packageData[componentKey][componentIndex] + ') to', possibleFiles[fileIndex])); - - - this.injectImport(possibleFiles[fileIndex], - packageName, - packageData[componentKey][componentIndex]); - - this.injectItem(componentKey.toLowerCase(), - packageData[componentKey][componentIndex], - possibleFiles[fileIndex]); - - this.ui.writeLine(chalk.green('Successfully injected.')); - - return this.customImport(packageName) - .then(function() { - resolve(); - }); - - }.bind(this)); - }.bind(this)); - }.bind(this)); - }.bind(this)); - }, - - injectImport: function(entryPoint, packageName, component) { - var contents = fs.readFileSync(entryPoint, 'utf8'); - var contentsArr = flatImportLines(contents.split('\n')); - var alreadyInFile = importAlreadyInFile(contentsArr, packageName); - var match; - var imports; - var lastIndex; - - if (alreadyInFile) { - var importLine = contentsArr[alreadyInFile]; - match = importLine.match(/\{(.*?)\}/)[0].replace(/[ \{\}]/g, '').split(','); - match.push(component); - imports = 'import {' + match.join(', ') + '} from \'' + packageName + '\';'; - contentsArr[alreadyInFile] = imports; - } - else { - lastIndex = getLastImportIndex(contentsArr); - imports = 'import {' + component + '} from \'' + packageName + '\';'; - contentsArr.splice(lastIndex + 1, 0, imports); - } - - fs.writeFileSync(entryPoint, contentsArr.join('\n'), 'utf8'); - - function getLastImportIndex(arr) { - var lastImportIndex; - - arr.forEach(function(line, index) { - if (/import/.test(line) && /;/.test(line)) { - lastImportIndex = index; - } - }); - - return lastImportIndex; - } - - function importAlreadyInFile(arr, packageName) { - var inLine = false; - var checkRegex = new RegExp(packageName, 'i'); - - arr.forEach(function(line, index) { - if (/import/.test(line) && /;/.test(line) && checkRegex.test(line)) { - inLine = index; - } - }); - - return inLine; - } - - function flatImportLines(arr) { - var removeNextLine = false; - - arr.forEach(function(line, index) { - if (/import/.test(line) && !/;/.test(line)) { - arr[index] = generateFlatImportLine(arr, index); - removeNextLine = true; - } - - if (removeNextLine) { - arr.splice(index, 1); - if (/;/.test(line)) { - removeNextLine = false; - } - } - }); - - return arr; - } - - function generateFlatImportLine(arr, fromIndex) { - var lineArr = []; - var lastIndex; - - arr.forEach(function(line, index) { - if (index >= fromIndex && !lastIndex) { - lineArr.push(line); - if (/;/.test(line)) { - lastIndex = true; - } - } - }); - - return lineArr.join(''); - } - - }, - - importInBootstrapPrompt: function (packageName, packageData) { - return new Promise(function(resolve, reject) { - return this.ui.prompt({ - type: 'text', - name: 'option', - message: 'Inject providers into bootstrap script? (Y/n)', - default: function () { return 'Y'; }, - validate: function(value) { - return /[YN]/i.test(value) ? true : 'Enter Y(es) or N(o)'; - } - }) - .then(function(answer) { - if (answer.option.toLowerCase().substring(0, 1) === 'y') { - return this.ui.prompt({ - type: 'text', - name: 'entry', - message: 'Path to the file which bootstraps your app?', - default: function() { - if (existsSync(path.join(appRoot, 'app.ts'))) { - return path.join(appRoot, 'app.ts'); - } else { - var possibleFiles = glob.sync(appRoot, '*.ts'); - if (possibleFiles.length) { - return possibleFiles[0]; - } else { - return ''; - } - } - }, - validate: function(val) { - return (existsSync(val)) ? true : 'File not exists.'; - } - }) - .then(function(answer) { - var entryPoint = answer.entry; - this.importInBootstrap(entryPoint, packageName, packageData); - return resolve(); - }.bind(this)); - } - else { - return resolve(); - } - }.bind(this)); - }.bind(this)); - }, - - importInBootstrap: function(entryPoint, packageName, packageData) { - var providers = packageData.Provider; - var contents = fs.readFileSync(entryPoint, 'utf8'); - var contentsArr = contents.split('\n'); - var lastIndex; - var alreadyImported = false; - var regExp = new RegExp(packageName, 'i'); - - contentsArr.forEach(function(line, index) { - if (regExp.test(line)) { - alreadyImported = true; - } - }); - - if (alreadyImported) { - return false; - } - - var imports = 'import {' + providers.join(',') + '} from \'' + packageName + '\';'; - - if (imports.length > 100) { - imports = 'import {\n' + providers.join(' ,\n') + '}\n from \'' + packageName + '\';'; - } - - contentsArr.forEach(function(line, index) { - if (/import/.test(line)) { - lastIndex = index; - } - else if (/bootstrap\(/.test(line) && providers && providers.length) { - var replace; - var replacement; - - if (/\)/.test(line)) { - replace = line.match(/\((.*?)\)/)[0]; - } - else { - replace = contentsArr.splice(index, contentsArr.length - 1).join('\n').replace(/\n/g, ''); - replace = replace.match(/\((.*?)\)/)[0]; - } - - var current = replace.replace(/\(/, '').replace(/\)/, '').replace(/ /g, ''); - var replacementProviders; - - if (/\[/.test(current)) { - var currentProviders = current.match(/\[(.*?)\]/)[0]; - currentProviders = currentProviders.replace(/\[/, '').replace(/\]/, '').replace(/ /, ''); - currentProviders = currentProviders.split(','); - replacementProviders = currentProviders.concat(providers); - current = current.replace(/\,/, '').replace(/\[(.*?)\]/, ''); - - replacement = 'bootstrap(' + current + ', [\n ' + replacementProviders.join(', \n ') + '\n]);'; - contentsArr[index] = replacement; - } - else { - replacementProviders = providers; - - replacement = '(' + current + ', [\n ' + replacementProviders.join(', \n ') + '\n])'; - contentsArr[index] = contentsArr[index].replace(replace, replacement); - } - } - }); - - contentsArr.splice(lastIndex + 1, 0, imports); - - fs.writeFileSync(entryPoint, contentsArr.join('\n'), 'utf8'); - this.ui.writeLine(chalk.green('Providers imported in', entryPoint)); - }, - - injectItem: function(type, name, file) { - var contents = fs.readFileSync(file, 'utf8'); - var contentsArr = contents.split('\n'); - var replace; - var match; - - if (type === 'component') { - type = 'directive'; - } - - contentsArr.forEach(function(line, index) { - var regExp = new RegExp(type); - if (regExp.test(line)) { - match = line.match(/\[(.*?)\]/)[0]; - replace = match; - match = match.replace(/[\[\]]/g, ''); - match = match.split(','); - match.push(name); - match = match.filter(function(n) { return n !== ''; } ); - contentsArr[index] = contentsArr[index].replace(replace, '[' + match.join(',') + ']'); - } - }.bind(this)); - - fs.writeFileSync(file, contentsArr.join('\n'), 'utf8'); - }, - - parseFile: function (packageName) { - var packagePath = path.join(process.cwd(), 'node_modules', packageName, packageName + '.ts'); - - if (!existsSync(packagePath)) { - return false; - } - - var contents = fs.readFileSync(packagePath, 'utf8'); - var data = {}; - - data.Component = []; - data.Directive = []; - data.Pipe = []; - data.Provider = []; - data.styleUrl = []; - - var contentsArr = contents.split('\n'); - contentsArr.forEach(function(line, index) { - if (/components:/.test(line)) { - data.Component = this.parseData(line); - } - if (/directives:/.test(line)) { - data.Directive = this.parseData(line); - } - else if (/pipes:/.test(line)) { - data.Pipe = this.parseData(line); - } - else if (/providers:/.test(line)) { - data.Provider = this.parseData(line); - } - else if (/styles:/.test(line)) { - //data.styles = this.parseData(line); - } - else if (/styleUrls:/.test(line)) { - data.styleUrl = this.parseData(line); - } - }.bind(this)); - - _.each(data, function(val, key) { - if (!data[key].length) { - delete data[key]; - } - }); - - return data; - }, - - parseData: function (string) { - var match = string.match(/\[(.*?)\]/)[0].replace(/\[/, '').replace(/\]/, '').replace(' ', ''); - return match.split(','); - }, - - checkIfPackageIsAuthentic: function (pkgName) { - if (!existsSync(path.join(nodeModules, pkgName))) { - return false; - } - else { - if (existsSync(path.join(nodeModules, pkgName, pkgName + '.ts')) && - existsSync(path.join(nodeModules, pkgName, 'bundles')) && - existsSync(path.join(nodeModules, pkgName, 'bundles', pkgName + '.js'))) { - return true; - } - else { - return false; - } - } - }, - - announceOKCompletion: function() { - this.completionOKMessage = 'Done.'; - this.ui.writeLine(chalk.green(this.completionOKMessage)); - this.done(); - }, - - announceErrorCompletion: function() { - this.ui.writeLine(chalk.red(this.completionErrorMessage)); - this.done(); - }, - - done: function() { - this.ui.stopProgress(); - this.restoreLogger(); - }, - - disableLogger: function() { - this.oldLog = console.log; - console.log = function() {}; - }, - - restoreLogger: function() { - console.log = this.oldLog; // Hack, see above - } - -}); diff --git a/addon/ng2/tasks/lib-uninstall.js b/addon/ng2/tasks/lib-uninstall.js deleted file mode 100644 index d4f68e2ee6ed..000000000000 --- a/addon/ng2/tasks/lib-uninstall.js +++ /dev/null @@ -1,222 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var Task = require('ember-cli/lib/models/task'); -var shellPromise = require ('../utilities/shell-promise'); -var existsSync = require('exists-sync'); -var chalk = require('chalk'); -var path = require('path'); -var fs = require('fs'); -var _ = require('lodash'); -var glob = require('glob'); -var appRoot = path.resolve('./src'); -var nodeModules = path.resolve('./node_modules'); -var checkDirs = ['components', 'providers', 'directives', 'pipes']; - -module.exports = Task.extend({ - command: '', - completionOKMessage: '', - completionErrorMessage: 'Error uninstalling packages. Did you misspelt it?', - - run: function(options) { - this.packages = options.packages || []; - this.autoRemove = options.autoRemove || false; - this.disableLogger(); - - this.ui.startProgress(chalk.green('Uninstalling 3rd party package:', - this.packages.join(', ')), chalk.green('.')); - - this.getPackagesDataBeforeRemoved(this.packages); - - this.npmOptions = ' --loglevel error --color always --optional --save-dev ' + - '--save-exact ' + !!options['save-exact']; - - var npmCommand = 'npm uninstall ' + this.packages.join(' ') + this.npmOptions; - - return shellPromise(npmCommand) - .then(function(npmresp) { - if (!npmresp.length) { - this.completionErrorMessage = 'No packages uninstalled.'; - return this.announceErrorCompletion(); - } - else { - if (this.autoRemove) { - this.removePackagesFromApp(); - return this.announceOKCompletion(); - } - else { - return this.uninstallProcedure() - .then(function(resp) { - return this.announceOKCompletion(); - }.bind(this)); - } - } - }.bind(this)); - }, - - uninstallProcedure: function() { - this.ui.writeLine(chalk.yellow('Uninstalled package:', this.packages.join(','))); - - return this.ui.prompt({ - type: 'input', - name: 'remove', - message: 'Do you want to automatically remove package from you app?', - default: function() { return 'Y'; }, - validate: function(value) { - return /[YN]/i.test(value) ? true : 'Enter Y(es) or N(o)'; - } - }) - .then(function(confirm) { - if (confirm.remove.toLowerCase().substring(0, 1) === 'y') { - this.removePackagesFromApp(); - } - - return Promise.resolve(); - }.bind(this)); - }, - - parseFile: function (packageName) { - var packagePath = path.resolve(process.cwd(), 'node_modules', packageName, packageName + '.ts'); - - if (!existsSync(packagePath)) { - return false; - } - - var contents = fs.readFileSync(packagePath, 'utf8'); - var data = {}; - - data.Directive = []; - data.Pipe = []; - data.Provider = []; - data.styleUrl = []; - - var contentsArr = contents.split('\n'); - contentsArr.forEach(function(line, index) { - if (/directives:/.test(line)) { - data.Directive = this.parseData(line); - } - else if (/pipes:/.test(line)) { - data.Pipe = this.parseData(line); - } - else if (/providers:/.test(line)) { - data.Provider = this.parseData(line); - } - else if (/styles:/.test(line)) { - //data.styles = this.parseData(line); - } - else if (/styleUrls:/.test(line)) { - data.styleUrl = this.parseData(line); - } - }.bind(this)); - - return data; - }, - - parseData: function (string) { - var match = string.match(/\[(.*?)\]/)[0].replace(/\[/, '').replace(/\]/, '').replace(' ', ''); - return match.split(','); - }, - - removePackagesFromApp: function() { - this.removedPackagesData.forEach(function(packageData) { - var files = glob.sync(process.cwd() + '/src/**/*.ts'); - var contents; - var allComponents = [].concat(packageData.Directive) - .concat(packageData.Provider) - .concat(packageData.Pipe) - .concat(packageData.styleUrl) - .reverse(); - var packageRegex = new RegExp(packageData.pkg_name); - var compRegex = new RegExp('(' + allComponents.join('|') + ')', 'g'); - var matches; - var helperRegex; - - files.forEach(function(file, findex) { - var contents = fs.readFileSync(file, 'utf8').split('\n'); - - contents.forEach(function(line, index) { - if (/import/.test(line) && /;/.test(line) && packageRegex.test(line)) { - contents.splice(index, 1); - } - - if (compRegex.test(line)) { - matches = line.match(compRegex); - matches.forEach(function(match) { - helperRegex = new RegExp(match + '\,?'); - contents[index] = contents[index].replace(helperRegex, ''); - }); - } - }); - - contents = this.cleanAfterRemoval(contents); - - fs.writeFileSync(file, contents.join('\n'), 'utf8'); - }.bind(this)); - - this.ui.writeLine(chalk.yellow(packageData.pkg_name) + ' ' + chalk.green('successfully removed from app.')); - - }.bind(this)); - }, - - cleanAfterRemoval: function(contents) { - var bootstrap = false; - - contents = contents.filter(function(line) { - if (/bootstrap\(/.test(line)) { - bootstrap = true; - } - return (bootstrap) ? line.trim().length > 0 : true; - }); - - if (bootstrap) { - contents.forEach(function(line, index) { - if (/bootstrap\(/.test(line) && /\]\)\;/.test(contents[index + 1])) { - contents[index] = line.match(/bootstrap\((.*?)\,/)[0].replace(',', '') + ');'; - contents[index + 1] = ''; - } - }); - } - - return contents; - }, - - getPackagesDataBeforeRemoved: function(packages) { - var data; - this.removedPackagesData = []; - - packages.forEach(function(pkg) { - data = this.parseFile(pkg); - if (data) { - data.pkg_name = pkg; - this.removedPackagesData.push(data); - } - }.bind(this)); - }, - - announceOKCompletion: function() { - this.completionOKMessage = 'Done.'; - this.ui.writeLine(chalk.green(this.completionOKMessage)); - this.done(); - }, - - announceErrorCompletion: function() { - this.ui.writeLine(chalk.red(this.completionErrorMessage)); - this.done(); - }, - - done: function() { - this.ui.stopProgress(); - this.restoreLogger(); - }, - - disableLogger: function() { - this.oldLog = console.log; - console.log = function() {}; - }, - - restoreLogger: function() { - console.log = this.oldLog; // Hack, see above - } - -}); diff --git a/addon/ng2/tasks/lint.js b/addon/ng2/tasks/lint.js deleted file mode 100644 index bd24dcb438ea..000000000000 --- a/addon/ng2/tasks/lint.js +++ /dev/null @@ -1,23 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var Task = require('ember-cli/lib/models/task'); -var exec = Promise.denodeify(require('child_process').exec); - -module.exports = Task.extend({ - run: function() { - var chalk = require('chalk'); - var ui = this.ui; - - return exec('npm run lint') - .then(function() { - ui.writeLine(chalk.green('Successfully linted files.')); - }) - .catch(function( /*error*/ ) { - ui.writeLine(chalk.red( - 'Couldn\'t do \'npm run lint\'. Please check this script exists in your package.json.' - )); - }); - } -}); diff --git a/addon/ng2/tasks/test.js b/addon/ng2/tasks/test.js deleted file mode 100644 index 278a0924efcf..000000000000 --- a/addon/ng2/tasks/test.js +++ /dev/null @@ -1,33 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var Task = require('ember-cli/lib/models/task'); -var path = require('path'); - -// require dependencies within the target project -function requireDependency(root, moduleName) { - var packageJson = require(path.join(root, 'node_modules', moduleName, 'package.json')); - var main = path.normalize(packageJson.main); - return require(path.join(root, 'node_modules', moduleName, main)); -} - -module.exports = Task.extend({ - - run: function (options) { - var projectRoot = this.project.root; - return new Promise(function (resolve, reject) { - var karma = requireDependency(projectRoot, 'karma'); - var karmaConfig = path.join(projectRoot, 'karma.conf'); - - // Convert browsers from a string to an array - if (options.browsers) { - options.browsers = options.browsers.split(','); - } - options.configFile = karmaConfig; - var karmaServer = new karma.Server(options, resolve); - - karmaServer.start(); - }); - } -}); diff --git a/addon/ng2/utilities/INITIAL_COMMIT_MESSAGE.txt b/addon/ng2/utilities/INITIAL_COMMIT_MESSAGE.txt deleted file mode 100644 index 70e89ac828e7..000000000000 --- a/addon/ng2/utilities/INITIAL_COMMIT_MESSAGE.txt +++ /dev/null @@ -1 +0,0 @@ -chore: initial commit from angular-cli v<%= version %> diff --git a/addon/ng2/utilities/shell-promise.js b/addon/ng2/utilities/shell-promise.js deleted file mode 100644 index 5b74717cbe55..000000000000 --- a/addon/ng2/utilities/shell-promise.js +++ /dev/null @@ -1,16 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var Promise = require('ember-cli/lib/ext/promise'); -var sh = require('shelljs'); - -module.exports = function shellPromise(command) { - return new Promise(function(resolve, reject) { - return sh.exec(command, { - silent: true - }, function(code, stdout, stderr) { - if (code !== 0) reject(stderr); - else resolve(stdout); - }); - }); -}; diff --git a/assets/angular.png b/assets/angular.png deleted file mode 100644 index 7929242740ed..000000000000 Binary files a/assets/angular.png and /dev/null differ diff --git a/bin/ng b/bin/ng deleted file mode 100755 index 43db1f2ce11f..000000000000 --- a/bin/ng +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env node -'use strict'; - -// Provide a title to the process in `ps` -process.title = 'angular-cli'; - -var path = require('path'); -var resolve = require('resolve'); -var exit = require('exit'); -var packageJson = require('../package.json'); -var Leek = require('leek'); - -resolve('angular-cli', { - basedir: process.cwd() -}, function(error, projectLocalCli) { - var cli; - if (error) { - // If there is an error, resolve could not find the ember-cli - // library from a package.json. Instead, include it from a relative - // path to this script file (which is likely a globally installed - // npm package). Most common cause for hitting this is `ember new` - cli = require('../lib/cli'); - } else { - // No error implies a projectLocalCli, which will load whatever - // version of ember-cli you have installed in a local package.json - cli = require(projectLocalCli); - } - - cli({ - cliArgs: process.argv.slice(2), - inputStream: process.stdin, - outputStream: process.stdout, - Leek: CustomLeek - }).then(function(result) { - var exitCode = typeof result === 'object' ? result.exitCode : result; - exit(exitCode); - }); -}); - - -function CustomLeek(options) { - options.trackingCode = packageJson.trackingCode; - options.globalName = packageJson.name; - options.name = packageJson.name; - options.version = packageJson.version; - return new Leek(options); -} diff --git a/constants.bzl b/constants.bzl new file mode 100644 index 000000000000..e2224aa24c39 --- /dev/null +++ b/constants.bzl @@ -0,0 +1,32 @@ +# Engine versions to stamp in a release package.json +RELEASE_ENGINES_NODE = "^20.19.0 || ^22.12.0 || >=24.0.0" +RELEASE_ENGINES_NPM = "^6.11.0 || ^7.5.6 || >=8.0.0" +RELEASE_ENGINES_YARN = ">= 1.13.0" + +NG_PACKAGR_VERSION = "^21.1.0-next.0" +ANGULAR_FW_VERSION = "^21.1.0-next.0" +ANGULAR_FW_PEER_DEP = "^21.0.0 || ^21.1.0-next.0" +NG_PACKAGR_PEER_DEP = "^21.0.0 || ^21.1.0-next.0" + +# Baseline widely-available date in `YYYY-MM-DD` format which defines Angular's +# browser support. This date serves as the source of truth for the Angular CLI's +# default browser set used to determine what downleveling is necessary. +# +# See: https://web.dev/baseline +BASELINE_DATE = "2025-10-20" + +SNAPSHOT_REPOS = { + "@angular/cli": "angular/cli-builds", + "@angular/pwa": "angular/angular-pwa-builds", + "@angular/build": "angular/angular-build-builds", + "@angular/ssr": "angular/angular-ssr-builds", + "@angular-devkit/architect": "angular/angular-devkit-architect-builds", + "@angular-devkit/architect-cli": "angular/angular-devkit-architect-cli-builds", + "@angular-devkit/build-angular": "angular/angular-devkit-build-angular-builds", + "@angular-devkit/build-webpack": "angular/angular-devkit-build-webpack-builds", + "@angular-devkit/core": "angular/angular-devkit-core-builds", + "@angular-devkit/schematics": "angular/angular-devkit-schematics-builds", + "@angular-devkit/schematics-cli": "angular/angular-devkit-schematics-cli-builds", + "@ngtools/webpack": "angular/ngtools-webpack-builds", + "@schematics/angular": "angular/schematics-angular-builds", +} diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md new file mode 100644 index 000000000000..8204bd7dcbce --- /dev/null +++ b/docs/DEVELOPER.md @@ -0,0 +1,189 @@ +# Building and Testing Angular CLI + +## Installation + +To get started locally, follow these instructions: + +1. If you haven't done it already, [make a fork of this repo](https://github.com/angular/angular-cli/fork). +2. If you are on Windows, see [the extra steps needed for contributing on Windows](#windows) +3. Clone to your local computer using `git`. +4. Make sure that you have Node `v20.19.0` or higher installed. See instructions [here](https://nodejs.org/en/download/). +5. Install `pnpm`. + - You can install pnpm by running `npm i -g pnpm@9`. + - See detailed instructions [here](https://pnpm.io/installation). +6. Run `pnpm install` from the root of your clone of this project to install dependencies. + +## Building and Installing the CLI + +To make a local build: + +```shell +pnpm build --local +``` + +This generates a number of tarballs in the `dist/` directory. To actually use +the locally built tools, switch to another repository reproducing the specific +issue you want to fix (or just generate a local repo with `ng new`). Then +install the locally built packages: + +```shell +cd "${EXAMPLE_ANGULAR_PROJECT_REPO}" +npm install -D ${CLI_REPO}/dist/*.tgz +``` + +Builds of this example project will use tooling created from the previous local +build and include any local changes. When using the CLI, it will automatically +check for a local install and use that if present. This means you can just run: + +```shell +npm install -g @angular/cli +``` + +to get a global install of the latest CLI release. Then running any `ng` command +in the example project will automatically find and use the local build of the +CLI. + +Note: If you are testing `ng update`, be aware that installing all the tarballs +will also update the framework (`@angular/core`) to the latest version. In this +case, simply install the CLI alone with +`npm install -D ${CLI_REPO}/dist/_angular_cli.tgz`, that way the rest of the +project remains to be upgraded with `ng update`. + +## Debugging + +To debug an invocation of the CLI, [build and install the CLI for an example +project](#building-and-installing-the-cli), then run the desired `ng` command +as: + +```shell +node --inspect-brk node_modules/.bin/ng ... +``` + +This will trigger a breakpoint as the CLI starts up. You can connect to this +using the supported mechanisms for your IDE, but the simplest option is to open +Chrome to [chrome://inspect](chrome://inspect) and then click on the `inspect` +link for the `node_modules/.bin/ng` Node target. + +Unfortunately, the CLI dynamically `require()`'s other files mid-execution, so +the debugger is not aware of all the source code files before hand. As a result, +it is tough to put breakpoints on files before the CLI loads them. The easiest +workaround is to use the `debugger;` statement to stop execution in the file you +are interested in, and then you should be able to step around and set breakpoints +as expected. + +## Testing + +There are two different test suites which can be run locally: + +### Unit tests + +- Run all tests: `pnpm bazel test //packages/...` +- Run a subset of the tests, use the full Bazel target example: `pnpm bazel test //packages/schematics/angular:angular_test` +- For a complete list of test targets use the following Bazel query: `pnpm bazel query "tests(//packages/...)"` + +When debugging a specific test, change `describe()` or `it()` to `fdescribe()` +and `fit()` to focus execution to just that one test. This will keep the output clean and speed up execution by not running irrelevant tests. + +You can find more info about debugging [tests with Bazel in the docs.](https://github.com/angular/angular-cli/blob/main/docs/process/bazel.md#debugging-jasmine_node_test) + +### End to end tests + +- For a complete list of test targets use the following Bazel query: `pnpm bazel query "tests(//tests/...)"` +- Run a subset of the tests: `pnpm bazel test //tests:e2e_node22 --config=e2e --test_filter="tests/i18n/ivy-localize-*"` +- Use `bazel run` to debug failing tests debugging: `pnpm bazel run //tests:e2e_node22 --config=e2e --test_arg="--glob=tests/basic/aot.ts"` +- Provide additional `e2e_runner` options using `--test_arg`: `--test_arg="--package-manager=yarn"` + +When running the debug commands, Node will stop and wait for a debugger to attach. +You can attach your IDE to the debugger to stop on breakpoints and step through the code. Also, see [IDE Specific Usage](#ide-specific-usage) for a +simpler debug story. + +## IDE Specific Usage + +Some additional tips for developing in specific IDEs. + +### Intellij IDEA / WebStorm + +To load the project in Intellij products, simply `Open` the repository folder. +Do **not** `Import Project`, because that will overwrite the existing +configuration. + +Once opened, the editor should automatically detect run configurations in the +workspace. Use the drop down to choose which one to run and then click the `Run` +button to start it. When executing a debug target, make sure to click the +`Debug` icon to automatically attach the debugger (if you click `Run`, Node will +wait forever for a debugger to attach). + +![Intellij IDEA run configurations](images/run-configurations.png) + +### VS Code + +In order to debug some Angular CLI behaviour using Visual Studio Code, you can run `npm run build`, and then use a launch configuration like the following: + +```json +{ + "type": "node", + "request": "launch", + "name": "ng serve", + "cwd": "", + "program": "${workspaceFolder}/dist/@angular/cli/bin/ng", + "args": [ + "", + ...other arguments + ], + "console": "integratedTerminal" +} +``` + +Then you can add breakpoints in `dist/@angular` files. + +For more information about Node.js debugging in VS Code, see the related [VS Code Documentation](https://code.visualstudio.com/docs/nodejs/nodejs-debugging). + +## CPU Profiling + +In order to investigate performance issues, CPU profiling is often useful. + +### Creating a profile + +Node.js 16+ users can use the Node.js command line argument `--cpu-prof` to create a CPU profile. + +```bash +node --cpu-prof node_modules/.bin/ng build +``` + +In addition to this one, another, more elaborated way to capture a CPU profile using the Chrome Devtools is detailed in https://github.com/angular/angular-cli/issues/8259#issue-269908550. + +#### Opening a profile + +You can use the Chrome Devtools to process it. To do so: + +1. open `chrome://inspect` in Chrome +1. click on "Open dedicated DevTools for Node" +1. go to the "profiler" tab +1. click on the "Load" button and select the generated `.cpuprofile` file +1. on the left panel, select the associated file + +## Creating New Packages + +Adding a package to this repository means running two separate commands: + +1. `schematics devkit:package PACKAGE_NAME`. This will update the `.monorepo` file, and create the + base files for the new package (package.json, src/index, etc). +1. `devkit-admin templates`. This will update the README and all other template files that might + have changed when adding a new package. + +For private packages, you will need to add a `"private": true` key to your package.json manually. +This will require re-running the template admin script. + +## Windows + +To contribute to Angular using a Windows machine, you need to use the [Windows Linux Subsystem](https://learn.microsoft.com/en-us/windows/wsl/about) (also known as WSL). +Installing WSL on your machine requires a few extra steps, but we believe it's generally useful for developing on Windows: + +1. Run `wsl --install` from Powershell (as administrator). +2. Restart your machine. +3. Enter the WSL environment by running: `wsl`. +4. Continue with the developer guide as if you were on a native Linux system. + +For a more detailed guide, refer to the official Microsoft documentation: [Installing WSL](https://learn.microsoft.com/en-us/windows/wsl/install). + +**Note:** Angular continues to support native Windows development via the `ng` CLI and rigorously tests on Windows for every code change. This recommendation specifically applies to contributing to the Angular codebase itself. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000000..1aa9fc8f9262 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,17 @@ +# `/docs` Folder + +This folder is used for all documentation. It contains a number of subfolders with short +descriptions here. + +## `/docs/design` + +Design documents on GitHub, in Markdown format. The number of design documents available is limited. + +## `/docs/process` + +Description of various caretaker and workflow processes. + +## `/docs/specifications` + +Specifications for support of Angular CLI features. These are meant to be read directly on GitHub +by developers of libraries who want to integrate with the Angular CLI. diff --git a/docs/design/analytics.md b/docs/design/analytics.md new file mode 100644 index 000000000000..0eb3bad26d8f --- /dev/null +++ b/docs/design/analytics.md @@ -0,0 +1,101 @@ +# Usage Metrics Gathering + +This document list exactly what is gathered and how. + +Any change to analytics should most probably include a change to this document. + +## Dimensions and Metrics + +Google Analytics Custom Dimensions are used to track system values and flag values. These +dimensions are aggregated automatically on the backend. + +One dimension per flag, and although technically there can be an overlap between 2 commands, for +simplicity it should remain unique across all CLI commands. The dimension is the value of the +`x-user-analytics` field in the `schema.json` files. + +### Adding dimension or metic. +1. Create the dimension or metric in [Google Analytics](https://analytics.google.com/) first. These are not tracked if they aren't + defined in Google Analytics. +1. Use the ID of the dimension as the `x-user-analytics` value in the `schema.json` file. +1. New dimension and metrics PRs need to be approved by the tooling lead and require a new [Launch](http://go/launch). + +### Deleting a dimension or metic. +1. Archive the dimension and metric in [Google Analytics](https://analytics.google.com/). + + +**DO NOT ADD `x-user-analytics` FOR VALUES THAT ARE USER IDENTIFIABLE (PII), FOR EXAMPLE A +PROJECT NAME TO BUILD OR A MODULE NAME.** + +### Limits +| Item | Standard property limits | +|-------------------------------- |-------------------------- | +| Event-scoped custom dimensions | 50 | +| User-scoped custom dimensions | 25 | +| All custom metrics | 50 | + +### List of User Custom Dimensions + + +| Name | Parameter | Type | +|:---:|:---|:---| +| UserId | `up.ng_user_id` | `string` | +| OsArchitecture | `up.ng_os_architecture` | `string` | +| NodeVersion | `up.ng_node_version` | `string` | +| NodeMajorVersion | `upn.ng_node_major_version` | `number` | +| AngularCLIVersion | `up.ng_cli_version` | `string` | +| AngularCLIMajorVersion | `upn.ng_cli_major_version` | `number` | +| PackageManager | `up.ng_package_manager` | `string` | +| PackageManagerVersion | `up.ng_pkg_manager_version` | `string` | +| PackageManagerMajorVersion | `upn.ng_pkg_manager_major_v` | `number` | + + +### List of Event Custom Dimensions + + +| Name | Parameter | Type | +|:---:|:---|:---| +| Command | `ep.ng_command` | `string` | +| SchematicCollectionName | `ep.ng_schematic_collection_name` | `string` | +| SchematicName | `ep.ng_schematic_name` | `string` | +| Standalone | `ep.ng_standalone` | `string` | +| SSR | `ep.ng_ssr` | `string` | +| Style | `ep.ng_style` | `string` | +| Routing | `ep.ng_routing` | `string` | +| InlineTemplate | `ep.ng_inline_template` | `string` | +| InlineStyle | `ep.ng_inline_style` | `string` | +| BuilderTarget | `ep.ng_builder_target` | `string` | +| Aot | `ep.ng_aot` | `string` | +| Optimization | `ep.ng_optimization` | `string` | + + +### List of Event Custom Metrics + + +| Name | Parameter | Type | +|:---:|:---|:---| +| AllChunksCount | `epn.ng_all_chunks_count` | `number` | +| LazyChunksCount | `epn.ng_lazy_chunks_count` | `number` | +| InitialChunksCount | `epn.ng_initial_chunks_count` | `number` | +| ChangedChunksCount | `epn.ng_changed_chunks_count` | `number` | +| DurationInMs | `epn.ng_duration_ms` | `number` | +| CssSizeInBytes | `epn.ng_css_size_bytes` | `number` | +| JsSizeInBytes | `epn.ng_js_size_bytes` | `number` | +| NgComponentCount | `epn.ng_component_count` | `number` | +| AllProjectsCount | `epn.all_projects_count` | `number` | +| LibraryProjectsCount | `epn.libs_projects_count` | `number` | +| ApplicationProjectsCount | `epn.apps_projects_count` | `number` | + + +## Debugging + +Using `NG_DEBUG=1` will enable Google Analytics debug mode, To view the debug events, in Google Analytics go to `Configure > DebugView`. + +## Disabling Usage Analytics + +There are 2 ways of disabling usage analytics: + +1. using `ng analytics disable --global` (or changing the global configuration file yourself). This is the same + as answering "No" to the prompt. +1. There is an `NG_CLI_ANALYTICS` environment variable that overrides the global configuration. + That flag is a string that represents the User ID. If the string `"false"` is used it will + disable analytics for this run. diff --git a/docs/design/build-system-overview.dot b/docs/design/build-system-overview.dot new file mode 100644 index 000000000000..e792d7b42d81 --- /dev/null +++ b/docs/design/build-system-overview.dot @@ -0,0 +1,15 @@ +digraph G { + node [shape=rectangle]; + "assets array" -> "copy-webpack-plugin"; + "*.ts" -> "@ngtools/webpack" -> "@angular-devkit/build-optimizer"; + "*.js" -> "source-map-loader" -> "@angular-devkit/build-optimizer"; + "@angular-devkit/build-optimizer" -> "webpack module concatenation" -> "webpack bundling" -> "terser-webpack-plugin"; + "scripts array" -> "terser-webpack-plugin"; + "*.css" -> "postcss-loader with postcss-import, ./postcss-cli-resources.ts, autoprefixer"; + "*.scss\|sass" -> "sass-loader" -> "postcss-loader with postcss-import, ./postcss-cli-resources.ts, autoprefixer"; + "*.less" -> "less-loader" -> "postcss-loader with postcss-import, ./postcss-cli-resources.ts, autoprefixer"; + "postcss-loader with postcss-import, ./postcss-cli-resources.ts, autoprefixer" -> "raw-loader, ./optimize-css-webpack-plugin.ts" [label="component style?"]; + "raw-loader" -> "./optimize-css-webpack-plugin.ts" + "postcss-loader with postcss-import, ./postcss-cli-resources.ts, autoprefixer" -> "style-loader, ./raw-css-loader.ts, and mini-css-extract-plugin" [label="global style?"]; + "style-loader, ./raw-css-loader.ts, and mini-css-extract-plugin" -> "./optimize-css-webpack-plugin.ts" +} diff --git a/docs/design/build-system.md b/docs/design/build-system.md new file mode 100644 index 000000000000..d4d1d15b33bc --- /dev/null +++ b/docs/design/build-system.md @@ -0,0 +1,194 @@ +# Build System + +Angular CLI includes a first-party build system for Angular applications distributed as `@angular-devkit/build-angular`. +This build system is responsible for creating a standalone single-page application (SPA) from user source files and third-party dependencies. + +`@angular-devkit/build-angular` itself integrates with the rest of Angular CLI by being an [Architect builder](https://angular.dev/tools/cli/cli-builder). +This document describes a top level view of the functionality in `@angular-devkit/build-angular`, referred as just the "build system". +Deprecated or soon to be removed features are not described here. + +In broad strokes the main areas are: + +- loading and processing sources +- code splitting +- production optimizations +- post-processing steps + +Many tools are used in this process, and most of these steps happen within a [Webpack](https://webpack.js.org/) build. +We maintain a number of Webpack-centric plugins in this repository, some of these are public but most are private since they are very specific to our setup. + +## Overview diagram + +Below is a diagram of processing sources go through. +It's not strictly accurate because some options remove certain processing stages while adding others. +This diagram doesn't show these conditionals and only shows the possible processing steps. +Relative paths, such as `./raw-css-loader.ts`, refer to internal plugins, while other names usually refer to public npm packages. + +![Overview diagram](https://g.gravizo.com/source/svg?https://raw.githubusercontent.com/angular/angular-cli/main/docs/design/build-system-overview.dot) + +## Loading and processing sources + +Sources for Angular CLI browser apps are comprised of TypeScript files, style sheets, assets, scripts, and third party dependencies. +A given build will load these sources from disk, process them, and bundle them together. + +### TypeScript and Ahead-Of-Time Compilation + +Angular builds rely heavily on TypeScript-specific functionality for [Ahead-of-Time template compilation](https://angular.dev/tools/cli/aot-compiler) (AOT). +Outside Angular CLI, this is performed by the Angular Compiler (`ngc`), provided by `@angular/compiler-cli`. +To avail of Ahead-of-Time template compilation within a Webpack compilation we use and distribute the `@ngtools/webpack` Webpack plugin. + +Typescript sources are loaded from disk and compiled in-memory into JavaScript files that are stored in a virtual file system and made available to Webpack. +During compilation we also perform a number of code transformations using TypeScript transformers that enable automatic usage of AOT, internationalization features, and server-side rendering. + +AOT compilation requires loading HTML and CSS resources, referenced on Angular Components, as standalone strings with no external dependencies. +However, Webpack compilations operate on the basis of modules and references between them. +It's not possible to get the full compilation result for a given resource before the end of the compilation in webpack. +To obtain the standalone string for a HTML/CSS resource we compile it using a separate Webpack child compilation then extract the results. +These child compilations inherit configuration and access to the same files as the parent compilation, but have their own compilation life cycle and complete independently. + +The build system allows specifying replacements for specific files by replacing what path is loaded from the virtual file system. +This is used for conditional loading of code at build time. + +### Stylesheets + +Two types of stylesheets are used in the build system: global stylesheets and component stylesheets. +Global stylesheets are injected into the `index.html` file, while component stylesheets are loaded directly into compiled Angular components. + +The build system supports plain CSS stylesheets as well as the Sass and LESS CSS pre-processors. +Stylesheet processing functionality is provided by `sass-loader`, `less-loader`, `postcss-loader`, `postcss-import`, augmented in the build system by custom webpack plugins. + +### Assets + +Assets in the build system refer specifically to a list of files or directories that are meant to be copied verbatim as build artifacts. +These files are not processed and commonly include images, favicons, pdfs and other generic file types. +They are loaded into the compilation using `copy-webpack-plugin`. + +### Scripts + +Scripts in the build system refer specifically to JavaScript files that are meant to be loaded directly on `index.html` without being processed. +They are loaded into the compilation using a custom webpack plugin. + +### Third party dependencies + +Third party dependencies are mostly inside `node_modules` and are referenced via imports in source files. +Stylesheet third party dependencies are treated mostly the same as sources. + +JavaScript third party dependencies suffer a more involved process. +They are first resolved to a folder in `node_modules` via [Node Module Resolution](https://nodejs.org/api/modules.html#modules_modules). +A given module might have several different entry points, for instance one for use in NodeJS and another one for using in the browser. +Although `package.json` only officially supports listing one entry point under the `main` key, it's common for npm packages to list [other entry points](https://2ality.com/2017/04/setting-up-multi-platform-packages.html). +Each entry point is listed in under a key in that module's `package.json` whose value is a string containing a relative file path. +We use `es2015 > browser > module > main` a priority list of keys, where the first key matched name determines which entry point to use. +For instance, for a module that has both `browser` and `main` entry points, we pick `browser`. + +Once the actual JavaScript file is determined, it is loaded into the compilation together with it's source map. + +This resolution strategy supports the [Angular Package Format](https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/edit#heading=h.k0mh3o8u5hx). + +## Code splitting + +Code is automatically split into different files (or chunks, for js files) based on a few different triggers. + +The main TypeScript entry point and it's dependencies are bundled into the `main` chunk. +Global styles and scripts get one file per entry, named after themselves. + +JavaScript code imported only via dynamic imports is automatically split into a separate chunk that is loaded asynchronously named after the file containing the dynamic import. +If multiple asynchronous chunks contain a reference to the same module, it is placed in a new asynchronously loaded chunk named after the other chunks that use it. + +There is also a special chunk called `runtime` that contains the module loading logic and is loaded before the others. + +## Optimizations + +The build system contains optimizations aimed at improving the performance (for development builds) or the size of artifacts (for production builds). +These are often mutually exclusive and thus we cannot just default to always using them. + +### Development optimizations + +Development optimizations focus on reducing rebuild time on watched builds. +Although faster is always better, our threshold is to keep rebuilds even for large projects below 2 seconds. + +Computation needed to bundle code grows with its total size because of the cost of string concatenation and source map operations. +Third party dependencies that are initially loaded are split into a synchronously loaded chunk called `vendor`. +Splitting the infrequently changed vendor code from the frequently changed source code thus helps make rebuilds faster. + +When processing stylesheets, Webpack stores the intermediate modules as JavaScript code. +The JavaScript wrapper code makes stylesheets larger and `mini-css-extract-plugin` must be used to obtain the actually stylesheet content into a CSS file. +In development however, we skip the CSS extraction and leave it as JavaScript code for faster rebuild times. + +Watched builds split the processing load of TypeScript compilation between file emission on the main process and type checking on a forked process. +Large projects can also opt-out of AOT compilation for faster rebuilds. + +### Production optimizations + +Angular CLI focuses on enabling tree-shaking (removing unused modules) and dead code elimination (removing unused module code). +These two categories have high potential for size reduction because of network effects: removing code can lead to more code being removed. + +The main tool we use to achieve this goal are the dead code elimination capabilities of [Terser](https://github.com/terser/terser). +We also use Terser's mangling, by which names, but not properties, are renamed to shorter forms. +The main characteristics of Terser to keep in mind is that it operates via static analysis and does not support the indirection introduced by module loading. +Thus the rest of the pipeline is directed towards providing Terser with code that can be removed via static analysis in large single modules scopes. + +To this end we developed [@angular-devkit/build-optimizer](https://github.com/angular/angular-cli/tree/main/packages/angular_devkit/build_optimizer), a post-processing tool for TS code. +Build Optimizer searches for code patterns produced by the TypeScript and Angular compiler that are known to inhibit dead code elimination, and converts them into equivalent structures that enable it instead (the link above contains some examples). +It also adds Terser [annotations](https://github.com/terser/terser#annotations) marking top-level functions as free from side effects for libraries that have the `sideEffects` flag set to false in `package.json`. + +Webpack itself also contains two major features that enable tree-shaking and dead code elimination: [`sideEffects` flag](https://github.com/webpack/webpack/tree/master/examples/side-effects) support and [module concatenation](https://webpack.js.org/plugins/module-concatenation-plugin/). +Having the `sideEffects` flag set to false in `package.json` of a library means that library has no top-level side-effects and only exposes imports, which allows Webpack to rewrite imports to that library directly to the modules used and not including non-imported modules at all. +Module concatenation allows Webpack to collect in a single module the content of several modules, which in turn allows Terser to more easily remove unused code since there is no module loading indirection between those modules. + +One significant pitfall of this optimization strategy is the use of code splitting. +Using code splitting is desirable in order to speed up loading of web apps by deferring code that is not necessary on the initial load. +But since code splitting necessarily makes use of module loading, it is at odds with Terser-based optimizations. + +The use of lazy loading can not only prevent further optimizations, but also regress the currently possible ones by [preventing module concatenation](https://webpack.js.org/plugins/module-concatenation-plugin/#optimization-bailouts). +Modules that were concatenated when lazy modules are not present might not be concatenated anymore after lazy loading is introduced because these modules now need to be accessed from the lazy modules and thus get their own module scope. + +Aside from tree-shaking, scripts and styles (as defined in the sources above) also undergo optimizations via [Terser](https://github.com/terser/terser) and [CleanCSS](https://github.com/jakubpawlowicz/clean-css) respectively. + +## Post-processing steps + +There are some steps that are meant to operate over whole applications and thus happen after the compilation finishes and outputs files. +The steps are described in the order in which they are executed during a build. +The execution order was determined based on the complexity of the step with a primary goal of minimizing the repetition of computationally expensive operations. + +### Differential Loading + +Differential loading is a strategy that allows your web application to support multiple browsers, but only load the necessary code that the browser needs. +When differential loading is enabled, the CLI generates two distinct variants of application bundles. + +- The first contains ES2015 syntax, takes advantage of built-in support in modern browsers, ships fewer polyfills, and results in a smaller total size. +- The second contains code in the older ES5 syntax, along with all necessary polyfills for Angular to function. This results in a larger total size, but supports older browsers. + +This process as designed has the advantage that only one full compilation of the application in ES2015 syntax is required. +This removes a large amount of otherwise unnecessary and duplicate processing such as module resolution and dead code elimination. +It also guarantees that the application is ES5 compliant including third-party code. +The two variants of application bundles are created by the following steps: + +1. A full build of the application is performed using an ES2015 output target. + The application's global stylesheets, scripts, and assets are also processed during this step via the full build. These elements are reused for both of application variants. +2. A copy of the JavaScript output application files are transformed (commonly referred to as down-leveled) to ES5 compatible syntax. + ES2015+ syntax elements such as classes are converted into functionally equivalent ES5 code structures. +3. An additional ES5-only polyfills file is generated that contains the required Angular polyfills for ES5-only browsers. +4. A single index HTML file is created that references both application variants and is designed to only load the appropriate files for each browser. + +To support loading the file sets in the appropriate browsers, the HTML `script` element's `type` and `nomodule` attributes are leveraged. +Browsers will only load a script with a known type. +The ES2015 files are referenced using a type of `module` which is only supported on browsers that support ES2015+ code. +Since browsers that do not support ES2015+ code also do not support the `module` script type, these scripts are ignored for browsers that cannot parse and execute the ES2015 code. +Browsers that support the `module` script type also support the `nomodule` attribute. +This attribute instructs a browser that supports module scripts to ignore the script with the attribute. There is one browser exception in this case: Safari 10.1. +This browser supports module scripts but does not support the `nomodule` attribute. +To support this case, a special polyfill script is included to provide a workaround for the browser. +This arrangement of script elements ensures that ES5-only browsers will only execute the ES5 script files and browsers that support ES2015+ will only execute the ES2015 script files. + +### Localization (i18n) + +The second of these post-processing steps is build-time localization. +The final js bundles are processed using `@angular/localize`, replacing any locale-specific translations. +This sort of localization produces one application for each locale, each in their own folders. + +### Service Worker + +The third and last post-processing step is the creation of a [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API). +A listing of final application files is taken, fingerprinted according to their content, and added to the service worker manifest. +This must be the last step because it needs each application file to not be modified further. diff --git a/docs/design/deployurl-basehref.md b/docs/design/deployurl-basehref.md new file mode 100644 index 000000000000..71a804c073e4 --- /dev/null +++ b/docs/design/deployurl-basehref.md @@ -0,0 +1,18 @@ +| | Deploy URL | Base HREF | +| ------------------------------------------- | :--------: | :-------: | +| Initial scripts (index.html) | ✅ 👍 | ✅ 👍 | +| Initial stylesheets (index.html) | ✅ 👍 | ✅ 👍 | +| Lazy scripts (routes/import()/etc.) | ✅ 👍 | ✅ 👍 | +| Processed CSS resources (images/fonts/etc.) | ✅ 👍 | ✅ 👍 | +| Relative template (HTML) assets | ❌ 👎 | ✅ 👍 | +| Angular Router Default Base (APP_BASE_HREF) | ❌ | ✅ \*1 | +| Single reference in deployed Application | ❌ 👎 | ✅ 👍 | +| Special resource logic within CLI | ✅ 👎 | ❌ 👍 | +| Relative fetch/XMLHttpRequest | ❌ | ✅ | + +✅ - has/affects the item/trait +❌ - does not have/affect the item/trait +👍 - favorable behavior +👎 - unfavorable behavior + +\*1 -- Users with more complicated setups may need to manually configure the `APP_BASE_HREF` token within the application. (e.g., application routing base is `/` but assets/scripts/etc. are at `/assets/`) diff --git a/docs/design/ngConfig.md b/docs/design/ngConfig.md new file mode 100644 index 000000000000..df40002ac3d5 --- /dev/null +++ b/docs/design/ngConfig.md @@ -0,0 +1,141 @@ +# ngConfig - Design + +## Goals + +Currently, a project scaffolded with the CLI has no way of specifying options and configurations affecting their projects. There are ways to affect the build (with the `angular-cli-build.js` file), but the following questions cannot be answered without actual project options: + +- Where in my directory is my karma.conf file? +- What is my firebase database URL? +- Where is my client code? +- How can I use a different lazy-loading boundary prefix (or none at all)? +- Any other backend I want to run prior to `ng serve`? + +# Proposed Solution + +Since the data is static, we only need to keep it in a static store somewhere. + +One solution would be to keep the data in the `package.json`. Unfortunately, the metadata contains too much data and the `package.json` file would become unmanageable. + +Instead of polluting the package file, a `.angular-cli.json` file will be created that contains all the values. Access to that file will be allowed to the user if he knows the structure of the file (unknown keys will be kept but ignored), and it's easy to read and write. + +## Fallback + +There should be two `.angular-cli.json` files; one for the project and a general one. The general one should contain information that can be useful when scaffolding new apps, or information about the user. + +The project `.angular-cli.json` goes into the project root. The global configuration should live at `$HOME/.angular-cli.json`. + +## Structure + +The structure should be defined by a JSON schema (see [here](http://json-schema.org/)). The schema will be used to generate the `d.ts`, but that file will be kept in the file system along with the schema for IDEs. + +Every PR that would change the schema should include the update to the `d.ts`. + +# API + +## CLI + +#### Getting values + +The new command `get` should be used to output values on the terminal. It takes a set of flags and an optional array of [paths](#path); + +- `--glob` or `-g`; the path follows a glob format, where `*` can be replaced by any amount of characters and `?` by a single character. This will output `name=value` for each values matched. + +Otherwise, outputs the value of the path passed in. If multiple paths are passed in, they follow the format of `name=value`. + +#### Setting values + +The new command `set` should be used to set values in the local configuration file. It takes a set of flags and an optional array of `[path](#path)=value`; + +- `--global`; sets the value in the global configuration. +- `--remove`; removes the key (no value should be passed in). + +The schema needs to be taken into account when setting the value of the field; + +- If the field is a number, the string received from the command line is parsed. `NaN` throws an error. +- If the field is an object, an error is thrown. +- If the path is inside an object but the object hasn't been defined yet, sets the object with empty values (use the schema to create a valid object). + +#### Path + +The paths are json formatted path; each `.` separates a map, while `[]` indicates an index in an array. + +An example is the following: + + keyA.keyB.arrayC[3].value + +## Model + +A model should be created that will include loading and saving the configuration, including the global configuration. + +**The model should be part of the project and created on the `project` object.** + +That model can be used internally by the tool to get information. It will include a proxy handler that throws if an operation doesn't respect the schema. It will also set values on globals and locals depending on which branches you access. + +A simple API would return the TypeScript interface: + +```typescript +class Config { + // ... + get local(): ICliConfig { + /* ... */ + } + get global(): ICliConfig { + /* ... */ + } +} +``` + +The `local` and `global` getters return proxies that respect the JSON Schema defined for the Angular config. These proxies allow users to not worry about the existence of values; those values will only be created on disc when they are set. + +Also, `local` will always defer to the same key-path in `global` if a value isn't available. If a value is set and the parent exists in `global`, it should be created to `local` such that it's saved locally to the project. The proxies only care about the end points of `local` and `global`, not the existence of a parent in either. + +For example, assuming the following globals/locals: + +```js +// Global +{ + "key1": { + "key2": { + "value": 0, + "value2": 1 + } + } +} + +// Local +{ + "key1": { + "key2": { + "value2": 2, + "value3": 3 + } + } +} +``` + +The following stands true: + +```typescript +const config = new Config(/* ... */); + +console.log(config.local.key1.key2.value); // 0, even if it doesn't exist. +console.log(config.local.key1.key2.value2); // 2, local overrides. +console.log(config.local.key1.key2.value3); // 3. +console.log(config.local.key1.key2.value4); // Schema's default value. + +console.log(config.global.key1.key2.value); // 0. +console.log(config.global.key1.key2.value2); // 1, only global. +console.log(config.global.key1.key2.value3); // Schema's default value. + +config.local.key1.key2.value = 1; +// Now the value is 1 inside the local. Global stays the same. + +config.local.key1.key2.value3 = 5; +// The global config isn't modified. + +config.global.key1.key2.value4 = 99; +// The local config stays the same. +console.log(config.local.key1.key2.value4); // 99, the global value. + +config.save(); // Commits if there's a change to global and/or local. +``` diff --git a/docs/design/third-party-libraries.md b/docs/design/third-party-libraries.md new file mode 100644 index 000000000000..69ac6a7c8de5 --- /dev/null +++ b/docs/design/third-party-libraries.md @@ -0,0 +1,215 @@ +# Installing third party libraries + +# Abstract + +The current `ng install` process is faulty; third parties have to add a `bundles/` directory +with metadata that we expect. This document explores ways to improve on the process. + +# Requirements + +The following use cases need to be supported by `ng install`: + +1. Support for _any_ thirdparties, ultimately falling back to running `npm install` only and + doing nothing else, if necessary. +1. Not a new package manager. +1. Metadata storage by thirdparties in `package.json`. This must be accessible by plugins for + the build process or even when scaffolding. +1. Proper configuration of SystemJs in the browser. +1. SystemJS configuration managed by the user needs to be kept separated from the autogenerated + part. +1. The build process right now is faulty because the `tmp/` directory used by Broccoli is + inside the project. TypeScript when using `moduleResolution: "node"` can resolve the + `node_modules` directory (since it's in an ancestor folder), which means that TypeScript + compiles properly, but the build does not copy files used by TypeScript to `dist/`. We need + to proper map the imports and move them to `tmp` before compiling, then to `dist/` properly. +1. Potentially hook into scaffolding. +1. Potentially hook into the build. +1. Safe uninstall path. + +# Usages + +Here's a few stories that should work right off: + +```sh +$ ng new my-app && cd my-app/ +$ ng install jquery +``` + +^(this makes jQuery available in the index) + +```sh +$ ng install angularfire2 +> Please specify your Firebase database URL [my-app.firebaseio.com]: _ +``` + +^(makes firebase available and provided to the App) + +```sh +$ ng install angularfire2 --dbUrl=my-firebase-db.example.com +``` + +^(skip prompts for values passed on the command line) + +```sh +$ ng install angularfire2 --quiet +``` + +^(using `--quiet` to skip all prompts and use default values) + +```sh +$ ng install less +``` + +^(now compiles CSS files with less for the appropriate extensions) + +# Proposed Solution + +The `install` task will perform the following subtasks: + +1. **Run `npm install ${libName}`.** On failure, fail the install process. +1. **Run `preinstall` scripts.** If any script fails, run `npm uninstall ${libName}` and fail + the install process. +1. **Copy the `appData` to the `angular.json` file of the generated app, creating it if it + doesn't exist, under the `"modules"` key.** This data will be sent to the App as is. See the + [appData](#appData) section for more information. +1. **Read the `package["angular-cli"].appPrompt` and prompt the user configuration values, + if any.** See the [appData](#appData) section for more information. +1. **Add the providers specified in `package["angular-cli"]["providers"]` to the angular.js + `providers` list.** Rebuild the `providers.js` file. See the [Providers](#providers) + section for more information. +1. **Run `package["angular-cli"].scripts["install"]` if it exists.** If the script fails, + run `npm uninstall ${libName}` and fail the install process. +1. **Detect if a package named `angular/cli-wrapper-${libName}` exist in the angular + organization.** If so, run the steps above as if ng install angular/angular-${libName}. If + this install fails, ignore the failure. + + These packages can be used to wrap libraries that we want to support but can't update + easily, like Jasmine or LESS. + +1. **Install typings.** See the [Typings](#typings) section. +1. **Run `postinstall` scripts.** + +# Proof of Concept + +A proof of concept is being developed. + +# Hooks + +The third party library can implement hooks into the scaffolding, and the build system. The +CLI's tasks will look for the proper hooks prior to running and execute them. + +The order of execution of these hooks is breadth first, going through all node packages and +checking for the `package['angular-cli']['hooks']['${hookName}']`. The hooks are then +`require()`'d as is, from within the app root folder. Within the same level of the dependency +tree, there is no guarantee for the order of execution. + +## Install Hooks + +The only tricky part here is install hooks. The installed package should recursively call +its `(pre|post|)install` hooks only on packages that are newly installed. It should call +`(pre|post|)reinstall` on those who were already installed. A map of the currently installed +packages should be kept before performing `npm install`. + +# appData + +The `angular-cli` key in the generated app should be used for Angular CLI specific data. +This includes the CLI configuration itself, as well as third-parties library configuration. + +Third-parties can store data that will be passed to the app, and can use that data themselves. +That data can be anything. Any keys starting with either `$` or `_` will be ignored and not +passed to the client. It could be used for builds or scaffolding information. + +During installation, there's a step to prompt the user for data. The schema contains a prompt +text and a default value. The default value type is used to convert the string entered by the +user. The key used in `appPrompt` is the key saved in appData. + +Example: + +```typescript +{ // ... + "angular-cli": { + "appPrompt": { + "number": { + "prompt": "Please enter a number:", + "defaultValue": 0 + }, + "url": { + "prompt": "URL of your website:", + "defaultValue": "${homepage}" + } + } + } +} +``` + +The default value is necessary as a `quiet` mode is enforced, using default values to fill +in the data without user interaction. The special strings `${...}` can be used to replace +with special values in the default string values. The values are taken from the `package.json`. + +We use a declarative style to enforce sensible defaults and make sure developers think about +this. _In the case where developers want more complex interaction, they can use the +install/uninstall hooks to prompt users._ But 3rd party libraries developers need to be aware +that those hooks might not prompt users (no STDIN) and throw an error. + +# Providers + +Adding Angular providers to the app should be seamless. The install process will create a +`providers.js` from all the providers contained in all the dependencies. The User can disclude +providers it doesn't want. + +The `providers.js` file will always be overwritten by the `install` / `uninstall` process. It +needs to exist for toolings to be able to understand dependencies. These providers are global +to the application. + +In order to prevent providers from being global, the user can use the `--no-global-providers` +flag during installation, or can change the dependencies by using `ng providers`. As an example: + +```bash +ng new my-todo-app +ng generate component database +ng install --no-global-providers angularfirebase2 +ng providers database angularfirebase2 +``` + +Or, alternatively, the user can add its own providers and dependencies to its components. + +# Dependencies + +Because dependencies are handled by `npm`, we don't have to handle it. + +# Typings + +Typings should be added, but failure to find typings should not be a failure of installation. The +user might still want to install custom typings himself in the worst case. + +The `typings` package can be used to install/verify that typings exist. If the typings do not exist natively, we should tell the user to install the ambient version if he wants to. + +# Index.html + +We do not touch the `index.html` file during the installation task. The default page should +link to a SystemJS configuration file that is auto generated by the CLI and not user +configurable (see SystemJS below). If the user install a third party library, like jQuery, and +wants to use it, they have to import it using `import * as $ from 'vendor/jquery'`. + +The `index.html` also includes a section to configure SystemJS that can be managed by the user. +This is separate from the generated code. + +# SystemJS + +It is important that SystemJS works without any modifications by the user. It is also important +to leave the liberty to the user to change the SystemJS configuration so that it fits their needs. + +We will not use SystemJS bundles in development. This is for better debugging, future proofing +(when moving the build system) and better CDN support, as many of the loaded files will end up +being pulled from a CDN in production. During the `ng build` process for production, the +SystemJS configuration script will be rebuilt to fetch from the CDN. + +# Upgrade Strategy + +The upgrade process simply uses NPM. If new appData is added, it should be added manually using +a migration hook for `postinstall`. + +# Remaining Problems + +1. Installing dependencies of packages need to be further sketched out. +2. Need to add a fully fledged example with Firebase. diff --git a/docs/images/angular-cli-logo.png b/docs/images/angular-cli-logo.png new file mode 100644 index 000000000000..279687174851 Binary files /dev/null and b/docs/images/angular-cli-logo.png differ diff --git a/docs/images/angular-ecosystem-logos.png b/docs/images/angular-ecosystem-logos.png new file mode 100644 index 000000000000..f1f311b3d9d3 Binary files /dev/null and b/docs/images/angular-ecosystem-logos.png differ diff --git a/docs/images/run-configurations.png b/docs/images/run-configurations.png new file mode 100644 index 000000000000..b81207f163a2 Binary files /dev/null and b/docs/images/run-configurations.png differ diff --git a/docs/ng-install.md b/docs/ng-install.md deleted file mode 100644 index 1352791a47f3..000000000000 --- a/docs/ng-install.md +++ /dev/null @@ -1,205 +0,0 @@ -# 3rd party libraries - -### Table of Contents - -* [A. Consumer Guide: Using `ng install` to prepare your application to use a 3rd party library](#a-consumer-guide-installing-a-3rd-party-library) -* [B. Publisher Guide: Preparing your library for use by angular-cli](#b-publisher-guide-preparing-your-library) - -## A. Consumer Guide: Installing a 3rd party library - -Prerequisites: **You will first want to make sure the library adheres to the Publisher Guide** - -How do I find that out? - -1. Look inside the root file of the 3rd party library repo (should be the same name as the repo itself in most cases) -2. If you see `export default { ...some angular2 metadata... }` at the bottom, then the chances are high it's compatible -3. If you don't see it, [click here](#suggest-a-repo-adhere-to-the-publisher-guide) to copy/paste a feature request to post on the library repo - -### Ok I'm ready, let's do this! - -For example purposes, we will use [ng2-cli-test-lib](https://github.com/NathanWalker/ng2-cli-test-lib) here, however you would substitute the name of the library you want to install. - -``` -$ ng install ng2-cli-test-lib -Installing 3rd party package: ng2-cli-test-lib... -Package successfully installed. -[?] Inject the installed package into your app? (Y/n) Y -[?] Customize the injection of ng2-cli-test-lib? (Y/n) Y -[?] What would you like to inject from ng2-cli-test-lib? -1 Directive -2 Pipe -3 Provider -4 styleUrl ------------ -q Quit - -Enter value: 1 -[?] Which Directive would you like to inject? -1 TestDirective -Enter value: 1 - -[?] Where would you like to inject it? -1 ~/project/src/app.ts -2 ~/project/src/app/project.spec.ts -3 ~/project/src/app/project.ts -Enter value: 3 -Injecting Directive (TestDirective) to ~/project/src/app/project.ts -Successfully injected. - -[?] What would you like to inject from ng2-cli-test-lib? -1 Directive -2 Pipe -3 Provider -4 styleUrl ------------ -q Quit - -Enter value: q - -[?] Inject providers into bootstrap script? (Y/n) Y -[?] Path to the file which bootstraps your app? ~/project/src/app.ts -Providers imported in ~/project/src/app.ts -Done. -``` - -Your library is now successfully installed and injected into your project. - -In this example we chose to inject a `Directive`. -Specifically we chose `TestDirective` to be injected into `~/project/src/app/project.ts`. -Upon quitting, we were given the opportunity to inject providers into our bootstrap script. We chose `Y(es)` and specified `~/project/src/app.ts`. Providers were then injected into our bootstrap script. - -However, if you don't want to auto-inject anything, you can just answer `N(o)` to the first question. -Example: - -````shell -$ ng install ng2-cli-test-lib -Installing 3rd party package: ng2-cli-test-lib... -Package successfully installed. -[?] Inject the installed package into your app? (Y/n) n -Done. -```` - -## B. Publisher Guide: Preparing your library - -A few advantages to preparing your library for use by angular-cli: - -* It does not affect how your library would be consumed manually -* Provides a standard format in which your library can be consumed -* Creates a sense of familiarity and ease of use for developers -* Allows for rapid development with your library for projects that are under very tight time constraints -* Provides a way for developers to easily uninstall your library when the need arises - -#### Step 1 - -Create a file in the root of the library project to represent the entry point, usually named to match the repo/library name. - -#### Step 2 - -Add a `default` export at the bottom with the angular2 metadata that your library provides -For example: -[https://github.com/NathanWalker/ng2-cli-test-lib/blob/master/ng2-cli-test-lib.ts](https://github.com/NathanWalker/ng2-cli-test-lib/blob/master/ng2-cli-test-lib.ts) - -#### Step 3 - -At the time of writing, we still need a bundler script to create bundles that will then be included into our app. -Here is an example of a bundler script which uses `systemjs-builder`, which we tested and works well. -````js -var pkg = require('./package.json'); -var path = require('path'); -var Builder = require('systemjs-builder'); -var name = pkg.name; - -var builder = new Builder(); -var config = { - baseURL: '.', - transpiler: 'typescript', - typescriptOptions: { - module: 'cjs' - }, - map: { - typescript: './node_modules/typescript/lib/typescript.js', - angular2: path.resolve('node_modules/angular2'), - rxjs: path.resolve('node_modules/rxjs') - }, - paths: { - '*': '*.js' - }, - meta: { - 'node_modules/angular2/*': { build: false }, - 'node_modules/rxjs/*': { build: false } - }, -}; - -builder.config(config); - -builder -.bundle(name, path.resolve(__dirname, 'bundles/', name + '.js')) -.then(function() { - console.log('Build complete.'); -}) -.catch(function(err) { - console.log('Error', err); -}); -```` - -Just include this script in the root of your app named like `bundler-script.js` and run it before publishing your library to `npm`. -Example of a `prepublish` script in `package.json`: -``` -"scripts": { - "prepublish": "tsc && tsc -d && node bundler-script.js" -} -``` - -Note that when TypeScript version 1.8 is out of `beta`, this script will no longer be needed and this documentation will be updated. - -#### Step 4 - -Bump version, compile and republish. - -#### Step 5 - -There is no step 5. It's that easy. Ok wait a second, what?! -Let's break down the entry file mentioned above. - -``` -// We import these so we can reference them in the 'default' export below -import {TestDirective} from './src/app/directives/test.directive'; -import {TestService} from './src/app/providers/test.provider'; -import {TestService2} from './src/app/providers/test2.provider'; -import {TestPipe} from './src/app/pipes/test.pipe'; -import {TestStyles} from './src/app/test.styles'; - -// We export everything we want developers to be able to use manually -export * from './src/app/directives/test.directive'; -export * from './src/app/providers/test.provider'; -export * from './src/app/providers/test2.provider'; -export * from './src/app/pipes/test.pipe'; -export * from './src/app/test.styles'; - -// This is the magic. -// Provides a standard way to export your library to allow angular-cli to help developers setup your library -// Please note: keys are optional. Your library should provide any metadata you want to be consumable. -export default { - directives: [TestDirective], - pipes: [TestPipe], - providers: [TestService, TestService2], - styles: TestStyles.styles(), - styleUrls: ['src/app/css/test.css'] -} -``` - -This would allow angular-cli to auto-annotate all relevant angular metadata that your library provides if the consumer chose to auto-inject. -You can study this example library which provides this complete example: -https://github.com/NathanWalker/ng2-cli-test-lib - -### Suggest a repo adhere to the Publisher Guide - -1. Create a new issue on the lib repo -2. Copy/paste this subject: `Please add a default export to support angular-cli auto install feature` -3. Copy/paste this message for the body: - -> It would be great if this library provided a `default` export object as described [here in the angular-cli Publisher's Guide](https://github.com/angular/angular-cli/blob/master/docs/ng-install.md#b-publisher-guide-preparing-your-library). Our project would greatly benefit from it and it's very easy to do. Please comment back if that would be possible. If you won't have time in the next couple days, I will try to submit a PR to provide this compatibility soon. Thank you! - -## CREDITS - -Using the `default` export was a brilliant idea from an angular core team member, @robwormald, so huge thanks to him. More background here: https://github.com/angular/angular-cli/issues/96 diff --git a/docs/process/bazel.md b/docs/process/bazel.md new file mode 100644 index 000000000000..86542047745e --- /dev/null +++ b/docs/process/bazel.md @@ -0,0 +1,71 @@ +## Yarn Workspaces + +The package architecture of `angular-cli` repository is originally setup using +yarn [workspaces](https://yarnpkg.com/lang/en/docs/workspaces/). This means the +dependencies of various `package.json` in the repository are linked and +installed together. + +## Bazel + +Since then, Bazel was introduced to manage some of the build dependencies in +`angular-cli` repo. However, Bazel does **not** yet support yarn workspaces, +since it requires laying out more than one `node_modules` directory. In this +mixed mode, developers ought to take extra care to synchronize the dependencies. + +Since the `yarn_install` rule that installs all NPM dependencies in this +repository only reads from the **root** `package.json`, every Bazel target that +depends on packages downloaded from the NPM registry will need to have the +dependency declared in the **root** `package.json`. + +In addition, if the dependency is also needed at runtime (non-dev dependencies), +the dependency on the individual package's `package.json` has to be updated as +well. This is to ensure that when users download a published version from NPM, +they will be able to install all dependencies correctly without Bazel. It is the +responsibility of the developer to keep both `package.json` in sync. + +## Windows support + +In general, any sort of node file lookup on Bazel should be subject to `require.resolve`. +This is how rules_nodejs resolves paths using the Bazel runfiles mechanism, where a given +Bazel target only has access to outputs from its dependencies. + +In practice, this does not make a lot of difference on Linux. +A symlink forest is laid down where the target is going to actually run, and mostly the +files are resolved correctly whether you use `require.resolve` or not because the files are there. + +On Windows though, that's a stricter. Bazel does not lay down a symlink forest on +windows by default. If you don't use `require.resolve`, it's still possible to correctly +resolve some files, like outputs from other rules. But other files, like node modules +dependencies and data files, need to be looked up in the runfiles. + +Since the requirement is quite lax on Linux but quite strict on windows, what ends up +happening is that lack of `require.resolve` calls go unnoticed until someone tries to run +things on Windows, at which point it breaks. + +## Debugging jasmine_node_test + +On Linux, Bazel tests will run under a sandbox for isolation. +You can turn off this sandbox by adding the [`local = True`](https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests) attribute to the rule. +You can also force local execution by passing `--test_output=streamed`. + +Then you will find the intermediate test files in `bazel-out/k8-fastbuild/bin`, followed by the test target path. + +Tests that are sharded, via the `shard_count` attribute, can fail if you reduce the number of tests or focus only a few. +This will cause some shards to execute 0 tests, which makes the exit with an error code. + +Tests that are marked as flaky, via the `flaky` attribute, will repeat when they fail. +This will cause any focused test to be repeated multiple time, since the presence of focused tests +causes jasmine to exit with a non-zero exit code. + +While testing, you can remove the `shard_count` attribute to prevent sharding and the `flaky` +attribute to prevent repetition. +Setting `--test_output=streamed` will disable sharding and `--flaky_test_attempts=1` will disable +the reruns of tests that have been marked as `flaky`. + +The `.bazelrc` includes a config for running tests with remote debugging enabled: + +```sh +pnpm bazel test --config=debug //packages/angular/cli:angular-cli_test +# Also disable reruns of failing tests that were marked as flaky: +pnpm bazel test --config=debug --config=no-sharding //packages/angular/cli:angular-cli_test +``` diff --git a/docs/process/release.md b/docs/process/release.md new file mode 100644 index 000000000000..5490e48e8279 --- /dev/null +++ b/docs/process/release.md @@ -0,0 +1,179 @@ +# Setting Up Local Repository + +1. Clone the Angular-CLI repo. A local copy works just fine. +1. Create an upstream remote: + +```bash +$ git remote add upstream https://github.com/angular/angular-cli.git +``` + +# Caretaker + +The caretaker should triage issues, merge PR, and sheppard the release. + +Caretaker rotation can be found +[here](https://rotations.corp.google.com/rotation/5117919353110528) and individual shifts can +be modified as necessary to accommodate caretaker's schedules. This automatically syncs to a +Google Calendar +[here](https://calendar.google.com/calendar/u/0/embed?src=c_6s96kkvd7nhink3e2gnkvfrt1g@group.calendar.google.com). +Click the "+" button in the bottom right to add it to your calendar to see shifts alongside the +rest of your schedule. + +The primary caretaker is responsible for both merging PRs and performing the weekly release. +The secondary caretaker does not have any _direct_ responsibilities, but they may need to take +over the primary's responsibilities if the primary is unavailable for an extended time (a day +or more) or in the event of an emergency. + +At the end of each caretaker's rotation, the primary should perform a handoff in which they +provide information to the next caretaker about the current state of the repository and update +the access group to now include the next caretakers. To perform this update to the access group, +the caretaker can run: + +```bash +$ pnpm ng-dev caretaker handoff +``` + +## Merging PRs + +The list of PRs which are currently ready to merge (approved with passing status checks) can +be found with [this search](https://github.com/angular/angular-cli/pulls?q=is%3Apr+is%3Aopen+label%3A%22action%3A+merge%22+-is%3Adraft). +This list should be checked daily and any ready PRs should be merged. For each PR, check the +`target` label to understand where it should be merged to. You can find which branches a specific +PR will be merged into with the `pnpm ng-dev pr check-target-branches ` command. + +When ready to merge a PR, run the following command: + +```bash +pnpm ng-dev pr merge +``` + +### Maintaining LTS branches + +Releases that are under Long Term Support (LTS) are listed on [angular.dev](https://angular.dev/reference/releases#support-policy-and-schedule). + +Since there could be more than one LTS branch at any one time, PR authors who want to +merge commits into LTS branches must open a pull request against the specific base branch they'd like to target. + +In general, cherry picks for LTS should only be done if it meets one of the criteria below: + +1. It addresses a critical security vulnerability. +2. It fixes a breaking change in the external environment. + For example, this could happen if one of the dependencies is deleted from NPM. +3. It fixes a legitimate failure on CI for a particular LTS branch. + +# Release + +Releasing is performed using Angular's unified release tooling. Each week, two releases are expected, `latest` and `next` on npm. + +**DURING a minor OR major CLI release:** + +Once FW releases the actual minor/major release (for example: `13.0.0` or `13.1.0`), update dependencies with the following: + +1. Update [`constants.bzl`](../../constants.bzl) so `@angular/core` and `ng-packagr` are using the release version (drop `-rc.*`). +2. Update all `package.json` dependencies on framework packages to to the release version (drop `-rc.*`). + - Components packages release _after_ CLI, so those should be left as `-rc.*`. +3. `pnpm install` to update the `pnpm-lock.yaml` file. + +Create a PR with the above changes ([example](https://github.com/angular/angular-cli/pull/31872)) and merge it directly to the RC +branch. This PR must land _after_ FW releases (or else CI will fail) but _before_ the CLI release PR. Releases are built before +the PR is sent for review, so any changes after that point won't be included in the release and this cannot be combined with the +release PR. + +**AFTER a minor OR major CLI release:** + +`constants.bzl` also needs to be updated to use `-next.0` after a major or minor release. However this needs to happen _after_ FW +publishes the initial `-next.0` release, which will happen 1 week after the major or minor release. + +## Releasing the CLI + +Typical patch and next releases do not require FW to release in advance, as CLI does not pin the FW +dependency. + +After confirming that the above steps have been done or are not necessary, run the following and +navigate the prompts: + +```sh +pnpm ng-dev release publish +``` + +Releases should be done in "reverse semver order", meaning they should follow: + +Oldest LTS -> Newest LTS -> Patch -> RC -> Next + +This can skip any versions which don't need releases, so most weeks are just "Patch -> Next". + +## Releasing a new package + +Wombat has some special access requirements which need to be configured to publish a new NPM package. + +See [this Wombat doc](http://g3doc/company/teams/cloud-client-libraries/team/automation/docs/npm-publish-service#existing-package) +and [this postmortem](http://docs/document/d/1emx2mhvF5xMzNUlDrVRYKI_u4iUOnVrg3rV6c5jk2is?resourcekey=0-qpsFbBfwioYT4f6kyUm8ZA&tab=t.0) +for more info. + +Angular is _not_ an organization on NPM, therefore each package is published +independently and Wombat access needs to be managed individually. This also means +we can't rely on Wombat already having access to a new package. + +In order to configure a brand new NPM package, it first needs to be published +manually so we can add Wombat access to it. Note that this step can and should be +done prior to weekly releases. The sooner this can be done, the less likely it +will block the next weekly release. + +1. Check out the `main` branch, which should always have a `-next` version. + - This avoids having the initial publish actually be used in production. +1. Trigger a release build locally. + ```shell + nvm install + pnpm install --frozen-lockfile + pnpm ng-dev release build + ``` +1. Log in to NPM as `angular`. + + ```shell + npm login + ``` + + - See these two Valentine entries for authentication details: + - https://valentine.corp.google.com/#/show/1460636514618735 + - https://valentine.corp.google.com/#/show/1531867371192103 + +1. Publish the release. + ```shell + (cd dist/releases/my-scope/my-pkg/ && npm publish --access public) + ``` +1. Add Wombat to the package. + ```shell + npm owner add google-wombot @my-scope/my-pkg + ``` +1. Don't forget to logout. + ```shell + npm logout + ``` +1. File a bug like [b/336626936](http://b/336626936) to ask Wombat maintainers to + accept the invite for the new package. + +Once Wombat accepts the invite, regular automated releases should work as expected. + +## Updating Browser Support + +Angular's browser support is defined by a [Baseline](https://web.dev/baseline) +"widely available" date. Before a new major version is released, this should be +updated to approximately the current date. + +A few weeks before a major (around feature freeze): + +1. Update `BASELINE_DATE` in + [`/constants.bzl`](/constants.bzl) to the end of the most recent month. + - For example, if it is currently May 12th, set `baselineThreshold` to April + 30th. + - Picking a date at the end of a month makes it easier to cross-reference + Angular's support with other tools (like MDN) which state Baseline support + using month specificity. + - Commit and merge the change, no other alterations or automation is + necessary in the CLI repo. +2. Update the date in the `ng-packagr` repo. + [`/.stylesheet-processor.ts`](https://github.com/ng-packagr/ng-packagr/blob/main/src/lib/styles/stylesheet-processor.ts#L25). +3. Update + [`angular.dev` documentation](https://github.com/angular/angular/tree/main/adev/src/content/reference/versions.md#browser-support) + to specify the date used and link to [browsersl.ist](https://browsersl.ist) + with the generated configuration. diff --git a/docs/specifications/schematic-collections-config.md b/docs/specifications/schematic-collections-config.md new file mode 100644 index 000000000000..5d52fb394d16 --- /dev/null +++ b/docs/specifications/schematic-collections-config.md @@ -0,0 +1,35 @@ +# Schematics Collections (`schematicCollections`) + +The `schematicCollections` can be placed under the `cli` option in the global `.angular.json` configuration, at the root or at project level in `angular.json` . + +```jsonc +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "schematicCollections": ["@schematics/angular", "@angular/material"] + } + // ... +} +``` + +## Rationale + +When this option is not configured and a user would like to run a schematic which is not part of `@schematics/angular`, +the collection name needs to be provided to `ng generate` command in the form of `[collection-name:schematic-name]`. This make the `ng generate` command too verbose for repeated usages. + +This is where the `schematicCollections` option can be useful. When adding `@angular/material` to the list of `schematicCollections`, the generate command will try to locate the schematic in the specified collections. + +``` +ng generate navigation +``` + +is equivalent to: + +``` +ng generate @angular/material:navigation +``` + +## Conflicting schematic names + +When multiple collections have a schematic with the same name. Both `ng generate` and `ng new` will run the first schematic matched based on the ordering (as specified) of `schematicCollections`. diff --git a/docs/specifications/schematic-prompts.md b/docs/specifications/schematic-prompts.md new file mode 100644 index 000000000000..b197cb4f5e72 --- /dev/null +++ b/docs/specifications/schematic-prompts.md @@ -0,0 +1,123 @@ +# Schematic Prompts + +Schematic prompts provide the ability to introduce user interaction into the schematic execution. The schematic runtime supports the ability to allow schematic options to be configured to display a customizable question to the user and then use the response as the value for the option. These prompts are displayed before the execution of the schematic. This allows users direct the operation of the schematic without requiring indepth knowledge of the full spectrum of options available to the user. + +To enable this capability, the JSON Schema used to define the schematic's options supports extensions to allow the declarative definition of the prompts and their respective behavior. No additional logic or changes are required to the JavaScript for a schematic to support the prompts. + +## Basic Usage + +To illustrate the addition of a prompt to an existing schematic the following example JSON schema for a hypothetical _hello world_ schematic will be used. + +```json +{ + "properties": { + "name": { + "type": "string", + "minLength": 1, + "default": "world" + }, + "useColor": { + "type": "boolean" + } + } +} +``` + +Suppose it would be preferred if the user was asked for their name. This can be accomplished by augmenting the `name` property definition with an `x-prompt` field. + +```json +"x-prompt": "What is your name?" +``` + +In most cases, only the text of the prompt is required. To minimize the amount of necessary configuration, the above _shorthand_ form is supported and will typically be all that is required. Full details regarding the _longhand_ form can be found in the **Configuration Reference** section. + +Adding a prompt to allow the user to decided whether the schematic will use color when executing its hello action is also very similar. The schema with both prompts would be as follows: + +```json +{ + "properties": { + "name": { + "type": "string", + "minLength": 1, + "default": "world", + "x-prompt": "What is your name?" + }, + "useColor": { + "type": "boolean", + "x-prompt": "Would you like the response in color?" + } + } +} +``` + +Prompts have several different types which provide the ability to display an input method that best represents the schematic option's potential values. + +- `confirmation` - A **yes** or **no** question; ideal for boolean options +- `input` - textual input; ideal for string or number options +- `list` - a predefined set of items which may be selected + +When using the _shorthand_ form, the most appropriate type will automatically be selected based on the property's schema. In the example, the `name` prompt will use an `input` type because it is a `string` property. The `useColor` prompt will use a `confirmation` type because it is a boolean property with `yes` corresponding to `true` and `no` corresponding to `false`. + +It is also important that the response from the user conforms to the constraints of the property. By specifying constraints using the JSON schema, the prompt runtime will automatically validate the response provided by the user. If the value is not acceptable, the user will be asked to enter a new value. This ensures that any values passed to the schematic will meet the expectations of the schematic's implementation and removes the need to add additional checks within the schematic's code. + +## Configuration Reference + +The `x-prompt` field supports two alternatives to enable a prompt for a schematic option. A shorthand form when additional customization is not required and a longhand form providing the ability for more control over the prompt. All user responses are validated against the property's schema. For example, string type properties can use a minimum length or regular expression constraint to control the allowed values. In the event the response fails validation, the user will be asked to enter a new value. + +### Longhand Form + +In the this form, the `x-prompt` field is an object with subfields that can be used to customize the behavior of the prompt. Note that some fields only apply to specific prompt types. + +| Field | Data Value | Default | +| --------- | ----------------------------------------- | --------------------------------- | +| `type` | `confirmation`, `input`, `list` | see shorthand section for details | +| `message` | string | N/A (required) | +| `items` | string and/or `label`/`value` object pair | only valid with type `list` | + +### Shorthand Form + +`x-prompt` [type: string] --> Question to display to the user. + +For this usage, the type of the prompt is determined by the type of the containing property. + +| Property Schema | Prompt Type | Notes | +| ------------------- | :------------: | :---------------------------------: | +| `"type": "boolean"` | `confirmation` | | +| `"type": "string"` | `input` | | +| `"type": "number"` | `input` | only valid numbers accepted | +| `"type": "integer"` | `input` | only valid numbers accepted | +| `"enum": [...]` | `list` | enum members become list selections | + +### `x-prompt` Schema + +```json +{ + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "type": { "type": "string" }, + "message": { "type": "string" }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "label": { "type": "string" }, + "value": {} + }, + "required": ["value"] + } + ] + } + } + }, + "required": ["message"] + } + ] +} +``` diff --git a/docs/specifications/universal-gotchas.md b/docs/specifications/universal-gotchas.md new file mode 100644 index 000000000000..cb7f85448547 --- /dev/null +++ b/docs/specifications/universal-gotchas.md @@ -0,0 +1,231 @@ +# Important Considerations when Using Angular Universal + +## Introduction + +Although the goal of the Universal project is the ability to seamlessly render an Angular +application on the server, there are some inconsistencies that you should consider. First, +there is the obvious discrepancy between the server and browser environments. When rendering +on the server, your application is in an ephemeral or "snapshot" state. The application is +fully rendered once, with the resulting HTML returned, and the remaining application state +destroyed until the next render. Next, the server environment inherently does not have the +same capabilities as the browser (and has some that likewise the browser does not). For +instance, the server does not have any concept of cookies. You can polyfill this and other +functionality, but there is no perfect solution for this. In later sections, we'll walk +through potential mitigations to reduce the scope of errors when rendering on the server. + +Please also note the goal of SSR: improved initial render time for your application. This +means that anything that has the potential to reduce the speed of your application in this +initial render should be avoided or sufficiently guarded against. Again, we'll review how +to accomplish this in later sections. + +## "window is not defined" + +One of the most common issues when using Angular Universal is the lack of browser global +variables in the server environment. This is because the Universal project uses +[domino](https://github.com/fgnass/domino) as the server DOM rendering engine. As a result, +there is certain functionality that won't be present or supported on the server. This +includes the `window` and `document` global objects, cookies, certain HTML elements (like canvas), +and several others. There is no exhaustive list, so please be aware of the fact that if you +see an error like this, where a previously-accessible global is not defined, it's likely because +that global is not available through domino. + +> Fun fact: Domino stands for "DOM in Node" + +### How to fix? + +#### Strategy 1: Injection + +Frequently, the needed global is available through the Angular platform via Dependency Injection (DI). +For instance, the global `document` is available through the `DOCUMENT` token. Additionally, a _very_ +primitive version of both `window` and `location` exist through the `DOCUMENT` object. For example: + +```ts +// example.service.ts +import { Injectable, Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; + +@Injectable() +export class ExampleService { + constructor(@Inject(DOCUMENT) private _doc: Document) {} + + getWindow(): Window | null { + return this._doc.defaultView; + } + + getLocation(): Location { + return this._doc.location; + } + + createElement(tag: string): HTMLElement { + return this._doc.createElement(tag); + } +} +``` + +Please be judicious about using these references, and lower your expectations about their capabilities. `localStorage` +is one frequently-requested API that won't work how you want it to out of the box. If you need to write your own library +components, please consider using this method to provide similar functionality on the server (this is what Angular CDK +and Material do). + +#### Strategy 2: Guards + +If you can't inject the proper global value you need from the Angular platform, you can "guard" against +invocation of browser code, so long as you don't need to access that code on the server. For instance, +often invocations of the global `window` element are to get window size, or some other visual aspect. +However, on the server, there is no concept of "screen", and so this functionality is rarely needed. + +You may read online and elsewhere that the recommended approach is to use `isPlatformBrowser` or +`isPlatformServer`. This guidance is **incorrect**. This is because you wind up creating platform-specific +code branches in your application code. This not only increases the size of your application unnecessarily, +but it also adds complexity that then has to be maintained. By separating code into separate platform-specific +modules and implementations, your base code can remain about business logic, and platform-specific exceptions +are handled as they should be: on a case-by-case abstraction basis. This can be accomplished using Angular's Dependency +Injection (DI) in order to remove the offending code and drop in a replacement at runtime. Here's an example: + +```ts +// window-service.ts +import { Injectable } from '@angular/core'; + +@Injectable() +export class WindowService { + getWidth(): number { + return window.innerWidth; + } +} +``` + +```ts +// server-window.service.ts +import { Injectable } from '@angular/core'; +import { WindowService } from './window.service'; + +@Injectable() +export class ServerWindowService extends WindowService { + getWidth(): number { + return 0; + } +} +``` + +```ts +// app-server.module.ts +import {NgModule} from '@angular/core'; +import {WindowService} from './window.service'; +import {ServerWindowService} from './server-window.service'; + +@NgModule({ + providers: [{ + provide: WindowService, + useClass: ServerWindowService, + }] +}) +``` + +If you have a component provided by a third-party that is not Universal-compatible out of the box, +you can create two separate modules for browser and server (the server module you should already have), +in addition to your base app module. The base app module will contain all of your platform-agnostic code, +the browser module will contain all of your browser-specific/server-incompatible code, and vice-versa for +your server module. In order to avoid editing too much template code, you can create a no-op component +to drop in for the library component. Here's an example: + +```ts +// example.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'example-component', + template: ``, // this is provided by a third-party lib + // that causes issues rendering on Universal +}) +export class ExampleComponent {} +``` + +```ts +// app.module.ts +import {NgModule} from '@angular/core'; +import {ExampleComponent} from './example.component'; + +@NgModule({ + declarations: [ExampleComponent], +}) +``` + +```ts +// browser-app.module.ts +import {NgModule} from '@angular/core'; +import {LibraryModule} from 'some-lib'; +import {AppModule} from './app.module'; + +@NgModule({ + imports: [AppModule, LibraryModule], +}) +``` + +```ts +// library-shim.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'library-component', + template: '', +}) +export class LibraryShimComponent {} +``` + +```ts +// server.app.module.ts +import { NgModule } from '@angular/core'; +import { LibraryShimComponent } from './library-shim.component'; +import { AppModule } from './app.module'; + +@NgModule({ + imports: [AppModule], + declarations: [LibraryShimComponent], +}) +export class ServerAppModule {} +``` + +#### Strategy 3: Shims + +If all else fails, and you simply must have access to some sort of browser functionality, you can patch +the global scope of the server environment to include the globals you need. For instance: + +```ts +// server.ts +global['window'] = { + // properties you need implemented here... +}; +``` + +This can be applied to any undefined element. Please be careful when you do this, as playing with the global +scope is generally considered an anti-pattern. + +> Fun fact: a shim is a patch for functionality that will never be supported on a given platform. A +> polyfill is a patch for functionality that is planned to be supported, or is supported on newer versions + +## Application is slow, or worse, won't render + +The Angular Universal rendering process is straightforward, but just as simply can be blocked or slowed down +by well-meaning or innocent-looking code. First, some background on the rendering process. When a render +request is made for platform-server (the Angular Universal platform), a single route navigation is executed. +When that navigation completes, meaning that all Zone.js macrotasks are completed, the DOM in whatever state +it's in at that time is returned to the user. + +> A Zone.js macrotask is just a JavaScript macrotask that executes in/is patched by Zone.js + +This means that if there is a process, like a microtask, that takes up ticks to complete, or a long-standing +HTTP request, the rendering process will not complete, or will take longer. Macrotasks include calls to globals +like `setTimeout` and `setInterval`, and `Observables`. Calling these without cancelling them, or letting them run +longer than needed on the server could result in suboptimal rendering. + +> It may be worth brushing up on the JavaScript event loop and learning the difference between microtasks +> and macrotasks, if you don't know it already. [Here's](https://javascript.info/event-loop) a good reference. + +## My HTTP, Firebase, WebSocket, etc. won't finish before render! + +Similarly to the above section on waiting for macrotasks to complete, the flip-side is that the platform will +not wait for microtasks to complete before finishing the render. In Angular Universal, we have patched the +Angular HTTP client to turn it into a macrotask, to ensure that any needed HTTP requests complete for a given +render. However, this type of patch may not be appropriate for all microtasks, and so it is recommended you use +your best judgment on how to proceed. You can look at the code reference for how Universal wraps a task to turn +it into a macrotask, or you can simply opt to change the server behavior of the given tasks. diff --git a/docs/specifications/update.md b/docs/specifications/update.md new file mode 100644 index 000000000000..e8ec31034b85 --- /dev/null +++ b/docs/specifications/update.md @@ -0,0 +1,189 @@ +# Update Command + +`ng update` is a new command in the CLI to update one or multiple packages, its peer dependencies, and the peer dependencies that depends on it. + +If there are inconsistencies, for example if peer dependencies cannot be matches by a simple semver range, the tool will error out (and nothing will be committed on the filesystem). + +## Command Line Usage + +```bash +ng update [options] +``` + +You can specify more than one package. Each package follows the convention of `[@scope/]packageName[@version-range-or-dist-tag]`. Packages not found in your dependencies will trigger an error. Any package that has a higher version in your `package.json` will trigger an error. + +| Flag | Argument | Description | +| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--force` | `boolean` | If true, skip the verification step and perform the update even if some peer dependencies would be invalidated. Peer dependencies errors will still be shown as warning. Defaults to false. | +| `--next` | `boolean` | If true, allows version discovery to include Beta and RC. Defaults to false. | +| `--migrate-only` | `boolean` | If true, don't change the `package.json` file, only apply migration scripts. | +| `--from` | `version` | Apply migrations from a certain version number. | +| `--to` | `version` | Apply migrations up to a certain version number (inclusive). By default will update to the installed version. | + +## Details + +The schematic performs the following steps, in order: + +1. Get all installed package names and versions from the `package.json` into `dependencyMap: Map`. +1. From that map, fetch all `package.json` from the NPM repository, which contains all versions, and gather them in a `Map`. +1. At the same time, update the `Map<>` with the version of the package which is believed to be installed (largest version number matching the version range). +1. **WARNING**: this might not be the exact installed versions, unfortunately. We should have a proper `package-lock.json` loader, and support `yarn.lock` as well, but these are stretch goals (and where do we stop). +1. For each packages mentioned on the command line, update to the target version (by default largest non-beta non-rc version): + +```python +# ARGV The packages being requested by the user. +# NPM A map of package name to a map of version to PackageJson structure. +# V A map of package name to available versions. +# PKG A map of package name to PackageJson structure, for the installed versions. +# next A flag for the "--next" command line argument. + +# First add all updating packages' peer dependencies. This should be recursive but simplified +# here for readability. +ARGV += [ NPM[p][max([ v for v in V[p] if (not is_beta(v) or next) ])].peerDependencies + for p in ARGV ] + +for p in ARGV: + x = max([ v for v in V[p] if (not is_beta(v) or next) ]) + + for other in set(PKG.keys()) - set([ p ]): + # Verify all packages' peer dependencies. + if has(other.peerDependencies, p) and !compatible(x, other.peerDependencies[p]): + showError('Cannot update dependency "%s": "%s" is incompatible with the updated dependency' % (x, other)) + + if any( has(other.peerDependencies, peer) and !compatible(x, other.peerDependencies[peer]) + for peer in PKG[p].peerDependencies.keys() ): + showError('Cannot update dependency "%s": "%s" depends on an incompatible peer dependency' % (x, other)) + + update_package_json(p, x) +``` + +## Library Developers + +Libraries are responsible for defining their own update schematics. The `ng update` tool will update the package.json, and if it detects the `"ng-update"` key in package.json of the library, will run the update schematic on it (with version information metadata). + +If a library does not define the `"ng-update"` key in their package.json, they are considered not supporting the update workflow and `ng update` is basically equivalent to `npm install`. + +### Migration + +In order to implement migrations in a library, the author must add the `ng-update` key to its `package.json`. This key contains the following fields: + +| Field Name | Type | Description | +| ------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `requirements` | `{ [packageName: string]: VersionRange }` | A map of package names to version to check for minimal requirement. If one of the libraries listed here does not match the version range specified in `requirements`, an error will be shown to the user to manually update those libraries. For example, `@angular/core` does not support updates from versions earlier than 5, so this field would be `{ '@angular/core': '>= 5' }`. | +| `migrations` | `string` | A relative path (or resolved using Node module resolution) to a Schematics collection definition. | +| `packageGroup` | `string[]` | A list of npm packages that are to be grouped together. When running the update schematic it will automatically include all packages as part of the packageGroup in the update (if the user also installed them). | +| `packageGroupName` | `string` | The name of the packageGroup to use. By default, uses the first package in the packageGroup. The packageGroupName needs to be part of the packageGroup and should be a valid package name. | + +#### Example given: + +Library my-lib wants to have 2 steps to update from version 4 -> 4.5 and 4.5 to 5. It would add this information in its `package.json`: + +```json +{ + "ng-update": { + "requirements": { + "my-lib": "^5" + }, + "migrations": "./migrations/migration-collection.json" + } +} +``` + +And create a migration collection (same schema as the Schematics collection): + +```json +{ + "schematics": { + "migration-01": { + "version": "6", + "factory": "./update-6" + }, + "migration-02": { + "version": "6.2", + "factory": "./update-6_2" + }, + "migration-03": { + "version": "6.3", + "factory": "./update-6_3" + }, + "migration-04": { + "version": "7", + "factory": "./update-7" + }, + "migration-05": { + "version": "8", + "factory": "./update-8" + } + } +} +``` + +The update tool would then read the current version of library installed, check against all `version` fields and run the schematics, until it reaches the version required by the user (inclusively). If such a collection is used to update from version 5 to version 7, the `01`, `02`, `03,` and `04` functions would be called. If the current version is 7 and a `--refactor-only` flag is passed, it would run the migration `04` only. More arguments are needed to know from which version you are updating. + +Running `ng update @angular/core` would be the same as `ng generate @angular/core/migrations:migration-01`. + +## Use cases + +### Help + +`ng update`, shows what updates would be applied; + +```sh +$ ng update +We analyzed your package.json, there's some packages to update: + +Name Version Command to update +---------------------------------------------------------------------------- +@angular/cli 1.7.0 > 6.0.0 ng update @angular/cli +@angular/core 5.4.3 > 6.0.1 ng update @angular/core +@angular/material 5.2.1 > 6.0.0 ng update @angular/material +@angular/router 5.4.3 > 6.0.1 ng update @angular/core + +There might be additional packages that are outdated. +``` + +### Simple Multi-steps + +I have a dependency on Angular, Material and CLI. I want to update the CLI, then Angular, then Material in separate steps. + +#### Details + +1. `ng update @angular/cli`. + Updates the CLI and packages that have a peer dependencies on the CLI (none), running refactoring tools from CLI 1 to 6. +1. `ng update @angular/core`. + Updates the Core package and all packages that have a peer dependency on it. This can get tricky if `@angular/material` get caught in the update because the version installed does not directly allow the new version of `@angular/core`. In this case + +### Complex Case + +package.json: + +```json +{ + "dependencies": { + "@angular/material": "5.0.0", + "@angular/core": "5.5.5" + } +} +``` + +Commands: + +```bash +ng update @angular/core +``` + +- updates `@angular/core` to the `latest` dist-tag (6.0.0) +- sees that `@angular/material` is not compatible with 6.0.0; **error out.** + +```bash +ng update @angular/material +``` + +- update `@angular/material` to latest version, that should be compatible with the current `@angular/core`. +- if that version is not compatible with you +- tell the user about a higher version that requires an update to `@angular/core`. + +## Notes + +1. if someone is on CLI 1.5, the command is not supported. The user needs to update to `@angular/cli@latest`, then `ng update @angular/cli`. Post install hook will check versions of cli configuration and show a message to run the `ng update` command. +1. NPM proxies or cache are not supported by the first version of this command. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000000..192ba19f2007 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,223 @@ +/** + * @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.dev/license + */ + +import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'; +import { FlatCompat } from '@eslint/eslintrc'; +import js from '@eslint/js'; +import stylistic from '@stylistic/eslint-plugin'; +import typescriptEslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import header from 'eslint-plugin-header'; +import _import from 'eslint-plugin-import'; +import globals from 'globals'; + +const compat = new FlatCompat({ + baseDirectory: import.meta.dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +// See: https://github.com/Stuk/eslint-plugin-header/issues/57 +header.rules.header.meta.schema = false; + +export default [ + { + ignores: [ + '**/bazel-out', + '**/dist-schema', + 'goldens/public-api', + 'modules/testing/builder/projects', + 'packages/angular_devkit/build_angular/src/babel-bazel.d.ts', + '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', + ], + }, + ...fixupConfigRules( + compat.extends( + 'eslint:recommended', + 'plugin:import/typescript', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'prettier', + ), + ), + { + plugins: { + '@stylistic': stylistic, + '@typescript-eslint': fixupPluginRules(typescriptEslint), + import: fixupPluginRules(_import), + header, + }, + + languageOptions: { + globals: { + ...globals.node, + }, + + parser: tsParser, + ecmaVersion: 5, + sourceType: 'module', + + parserOptions: { + project: 'tsconfig.json', + }, + }, + + linterOptions: { + // TODO: This defaults to "warn" in eslint9 and might be worth turning on. + reportUnusedDisableDirectives: 'off', + }, + + rules: { + '@stylistic/lines-around-comment': [ + 'error', + { + allowArrayStart: true, + allowBlockStart: true, + allowClassStart: true, + allowEnumStart: true, + allowInterfaceStart: true, + allowModuleStart: true, + allowObjectStart: true, + allowTypeStart: true, + beforeBlockComment: true, + ignorePattern: '@license', + }, + ], + + '@stylistic/spaced-comment': ['error', 'always'], + '@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.dev/license', + ' ', + ], + 2, + ], + + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-absolute-path': 'error', + 'import/no-duplicates': 'error', + '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: ['/'], + }, + ], + + '@typescript-eslint/await-thenable': 'off', + '@typescript-eslint/no-implied-eval': '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/require-await': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/only-throw-error': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + }, + }, + { + files: ['!packages/**', '**/*_spec.ts'], + + rules: { + 'max-lines-per-function': 'off', + 'no-case-declarations': 'off', + 'no-console': 'off', + }, + }, +]; diff --git a/goldens/BUILD.bazel b/goldens/BUILD.bazel new file mode 100644 index 000000000000..6dbbdd28f25b --- /dev/null +++ b/goldens/BUILD.bazel @@ -0,0 +1,10 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") + +package(default_visibility = ["//visibility:public"]) + +copy_to_bin( + name = "public-api", + srcs = glob([ + "public-api/**/*.md", + ]), +) diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json new file mode 100644 index 000000000000..96a53f7a1040 --- /dev/null +++ b/goldens/circular-deps/packages.json @@ -0,0 +1,42 @@ +[ + [ + "packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts", + "packages/angular_devkit/build_angular/src/builders/dev-server/options.ts" + ], + [ + "packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts", + "packages/angular/build/src/tools/esbuild/bundler-context.ts", + "packages/angular/build/src/tools/esbuild/utils.ts", + "packages/angular/build/src/tools/esbuild/bundler-execution-result.ts" + ], + [ + "packages/angular/build/src/tools/esbuild/bundler-context.ts", + "packages/angular/build/src/tools/esbuild/utils.ts" + ], + [ + "packages/angular/build/src/tools/esbuild/bundler-context.ts", + "packages/angular/build/src/tools/esbuild/utils.ts", + "packages/angular/build/src/tools/esbuild/bundler-execution-result.ts" + ], + [ + "packages/angular/build/src/tools/esbuild/bundler-context.ts", + "packages/angular/build/src/tools/esbuild/utils.ts", + "packages/angular/build/src/utils/server-rendering/manifest.ts" + ], + [ + "packages/angular/build/src/tools/esbuild/bundler-execution-result.ts", + "packages/angular/build/src/tools/esbuild/utils.ts" + ], + [ + "packages/angular/build/src/tools/esbuild/utils.ts", + "packages/angular/build/src/utils/server-rendering/manifest.ts" + ], + [ + "packages/angular/cli/src/analytics/analytics-collector.ts", + "packages/angular/cli/src/command-builder/command-module.ts" + ], + [ + "packages/angular/cli/src/analytics/analytics.ts", + "packages/angular/cli/src/command-builder/command-module.ts" + ] +] diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md new file mode 100644 index 000000000000..a51449319e47 --- /dev/null +++ b/goldens/public-api/angular/build/index.api.md @@ -0,0 +1,250 @@ +## API Report File for "@angular/build" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BuilderContext } from '@angular-devkit/architect'; +import { BuilderOutput } from '@angular-devkit/architect'; +import type { ConfigOptions } from 'karma'; +import type http from 'node:http'; +import { OutputFile } from 'esbuild'; +import type { Plugin as Plugin_2 } from 'esbuild'; + +// @public (undocumented) +export interface ApplicationBuilderExtensions { + // (undocumented) + codePlugins?: Plugin_2[]; + // (undocumented) + indexHtmlTransformer?: IndexHtmlTransform; +} + +// @public +export type ApplicationBuilderOptions = { + allowedCommonJsDependencies?: string[]; + aot?: boolean; + appShell?: boolean; + assets?: AssetPattern[]; + baseHref?: string; + browser?: string; + budgets?: Budget[]; + clearScreen?: boolean; + conditions?: string[]; + crossOrigin?: CrossOrigin; + define?: { + [key: string]: string; + }; + deleteOutputPath?: boolean; + deployUrl?: string; + externalDependencies?: string[]; + extractLicenses?: boolean; + fileReplacements?: FileReplacement[]; + i18nDuplicateTranslation?: I18NTranslation; + i18nMissingTranslation?: I18NTranslation; + index?: IndexUnion; + inlineStyleLanguage?: InlineStyleLanguage; + loader?: { + [key: string]: any; + }; + localize?: Localize; + namedChunks?: boolean; + optimization?: OptimizationUnion; + outputHashing?: OutputHashing; + outputMode?: OutputMode; + outputPath?: OutputPathUnion; + poll?: number; + polyfills?: string[]; + prerender?: PrerenderUnion; + preserveSymlinks?: boolean; + progress?: boolean; + scripts?: ScriptElement[]; + security?: Security; + server?: Serv; + serviceWorker?: Serv; + sourceMap?: SourceMapUnion; + ssr?: SsrUnion; + statsJson?: boolean; + stylePreprocessorOptions?: StylePreprocessorOptions; + styles?: StyleElement[]; + subresourceIntegrity?: boolean; + tsConfig: string; + verbose?: boolean; + watch?: boolean; + webWorkerTsConfig?: string; +}; + +// @public +export function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable; + +// @public (undocumented) +export interface BuildOutputAsset { + // (undocumented) + destination: string; + // (undocumented) + source: string; +} + +// @public (undocumented) +export interface BuildOutputFile extends OutputFile { + // (undocumented) + clone: () => BuildOutputFile; + // (undocumented) + readonly size: number; + // (undocumented) + type: BuildOutputFileType; +} + +// @public (undocumented) +export enum BuildOutputFileType { + // (undocumented) + Browser = 0, + // (undocumented) + Media = 1, + // (undocumented) + Root = 4, + // (undocumented) + ServerApplication = 2, + // (undocumented) + ServerRoot = 3 +} + +// @public +export type DevServerBuilderOptions = { + allowedHosts?: AllowedHosts; + buildTarget: string; + define?: { + [key: string]: string; + }; + headers?: { + [key: string]: string; + }; + hmr?: boolean; + host?: string; + inspect?: Inspect; + liveReload?: boolean; + open?: boolean; + poll?: number; + port?: number; + prebundle?: PrebundleUnion; + proxyConfig?: string; + servePath?: string; + ssl?: boolean; + sslCert?: string; + sslKey?: string; + verbose?: boolean; + watch?: boolean; +}; + +// @public +export interface DevServerBuilderOutput extends BuilderOutput { + // (undocumented) + address?: string; + // (undocumented) + baseUrl: string; + // (undocumented) + port?: number; +} + +// @public +export function executeDevServerBuilder(options: DevServerBuilderOptions, context: BuilderContext, extensions?: { + buildPlugins?: Plugin_2[]; + middleware?: ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[]; + indexHtmlTransformer?: IndexHtmlTransform; +}): AsyncIterable; + +// @public +export function executeExtractI18nBuilder(options: ExtractI18nBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): Promise; + +// @public +export function executeKarmaBuilder(options: KarmaBuilderOptions, context: BuilderContext, transforms?: KarmaBuilderTransformsOptions): AsyncIterable; + +// @public +export function executeNgPackagrBuilder(options: NgPackagrBuilderOptions, context: BuilderContext): AsyncIterableIterator; + +// @public +export function executeUnitTestBuilder(options: UnitTestBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable; + +// @public +export type ExtractI18nBuilderOptions = { + buildTarget?: string; + format?: Format; + i18nDuplicateTranslation?: I18NDuplicateTranslation; + outFile?: string; + outputPath?: string; + progress?: boolean; +}; + +// @public +export type KarmaBuilderOptions = { + aot?: boolean; + assets?: AssetPattern_2[]; + browsers?: Browsers; + codeCoverage?: boolean; + codeCoverageExclude?: string[]; + define?: { + [key: string]: string; + }; + exclude?: string[]; + externalDependencies?: string[]; + fileReplacements?: FileReplacement_2[]; + include?: string[]; + inlineStyleLanguage?: InlineStyleLanguage_2; + karmaConfig?: string; + loader?: { + [key: string]: any; + }; + main?: string; + poll?: number; + polyfills?: string[]; + preserveSymlinks?: boolean; + progress?: boolean; + reporters?: string[]; + scripts?: ScriptElement_2[]; + sourceMap?: SourceMapUnion_2; + stylePreprocessorOptions?: StylePreprocessorOptions_2; + styles?: StyleElement_2[]; + tsConfig: string; + watch?: boolean; + webWorkerTsConfig?: string; +}; + +// @public +export type NgPackagrBuilderOptions = { + poll?: number; + project?: string; + tsConfig?: string; + watch?: boolean; +}; + +// @public +export type UnitTestBuilderOptions = { + browserViewport?: string; + browsers?: string[]; + buildTarget?: string; + coverage?: boolean; + coverageExclude?: string[]; + coverageInclude?: string[]; + coverageReporters?: SchemaCoverageReporter[]; + coverageThresholds?: CoverageThresholds; + coverageWatermarks?: CoverageWatermarks; + debug?: boolean; + dumpVirtualFiles?: boolean; + exclude?: string[]; + filter?: string; + include?: string[]; + listTests?: boolean; + outputFile?: string; + progress?: boolean; + providersFile?: string; + reporters?: SchemaReporter[]; + runner?: Runner; + runnerConfig?: RunnerConfig; + setupFiles?: string[]; + tsConfig?: string; + ui?: boolean; + watch?: boolean; +}; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular/ssr/index.api.md b/goldens/public-api/angular/ssr/index.api.md new file mode 100644 index 000000000000..81764fcc1f62 --- /dev/null +++ b/goldens/public-api/angular/ssr/index.api.md @@ -0,0 +1,82 @@ +## API Report File for "@angular/ssr" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { DefaultExport } from '@angular/router'; +import { EnvironmentProviders } from '@angular/core'; +import { Provider } from '@angular/core'; +import { Type } from '@angular/core'; + +// @public +export class AngularAppEngine { + handle(request: Request, requestContext?: unknown): Promise; + static ɵallowStaticRouteRender: boolean; + static ɵhooks: Hooks; +} + +// @public +export function createRequestHandler(handler: RequestHandlerFunction): RequestHandlerFunction; + +// @public +export enum PrerenderFallback { + Client = 1, + None = 2, + Server = 0 +} + +// @public +export function provideServerRendering(...features: ServerRenderingFeature[]): EnvironmentProviders; + +// @public +export enum RenderMode { + Client = 1, + Prerender = 2, + Server = 0 +} + +// @public +export type RequestHandlerFunction = (request: Request) => Promise | null | Response; + +// @public +export type ServerRoute = ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer; + +// @public +export interface ServerRouteClient extends ServerRouteCommon { + renderMode: RenderMode.Client; +} + +// @public +export interface ServerRouteCommon { + headers?: Record; + path: string; + status?: number; +} + +// @public +export interface ServerRoutePrerender extends Omit { + fallback?: never; + renderMode: RenderMode.Prerender; +} + +// @public +export interface ServerRoutePrerenderWithParams extends Omit { + fallback?: PrerenderFallback; + getPrerenderParams: () => Promise[]>; +} + +// @public +export interface ServerRouteServer extends ServerRouteCommon { + renderMode: RenderMode.Server; +} + +// @public +export function withAppShell(component: Type | (() => Promise | DefaultExport>>)): ServerRenderingFeature; + +// @public +export function withRoutes(routes: ServerRoute[]): ServerRenderingFeature; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular/ssr/node/index.api.md b/goldens/public-api/angular/ssr/node/index.api.md new file mode 100644 index 000000000000..eccb6396938e --- /dev/null +++ b/goldens/public-api/angular/ssr/node/index.api.md @@ -0,0 +1,66 @@ +## API Report File for "@angular/ssr_node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { ApplicationRef } from '@angular/core'; +import { BootstrapContext } from '@angular/platform-browser'; +import { Http2ServerRequest } from 'node:http2'; +import { Http2ServerResponse } from 'node:http2'; +import { IncomingMessage } from 'node:http'; +import { ServerResponse } from 'node:http'; +import { StaticProvider } from '@angular/core'; +import { Type } from '@angular/core'; + +// @public +export class AngularNodeAppEngine { + constructor(); + handle(request: IncomingMessage | Http2ServerRequest, requestContext?: unknown): Promise; +} + +// @public +export class CommonEngine { + constructor(options?: CommonEngineOptions | undefined); + render(opts: CommonEngineRenderOptions): Promise; +} + +// @public (undocumented) +export interface CommonEngineOptions { + bootstrap?: Type<{}> | ((context: BootstrapContext) => Promise); + enablePerformanceProfiler?: boolean; + providers?: StaticProvider[]; +} + +// @public (undocumented) +export interface CommonEngineRenderOptions { + bootstrap?: Type<{}> | ((context: BootstrapContext) => Promise); + // (undocumented) + document?: string; + // (undocumented) + documentFilePath?: string; + inlineCriticalCss?: boolean; + providers?: StaticProvider[]; + publicPath?: string; + // (undocumented) + url?: string; +} + +// @public +export function createNodeRequestHandler(handler: T): T; + +// @public +export function createWebRequestFromNodeRequest(nodeRequest: IncomingMessage | Http2ServerRequest): Request; + +// @public +export function isMainModule(url: string): boolean; + +// @public +export type NodeRequestHandlerFunction = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => Promise | void; + +// @public +export function writeResponseToNodeResponse(source: Response, destination: ServerResponse | Http2ServerResponse): Promise; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/architect/index.api.md b/goldens/public-api/angular_devkit/architect/index.api.md new file mode 100644 index 000000000000..0ae8751719b5 --- /dev/null +++ b/goldens/public-api/angular_devkit/architect/index.api.md @@ -0,0 +1,553 @@ +## API Report File for "@angular-devkit/architect" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BaseException } from '@angular-devkit/core'; +import { json } from '@angular-devkit/core'; +import { JsonObject } from '@angular-devkit/core'; +import { JsonValue } from '@angular-devkit/core'; +import { logging } from '@angular-devkit/core'; +import { Observable } from 'rxjs'; +import { ObservableInput } from 'rxjs'; +import { Observer } from 'rxjs'; +import { schema } from '@angular-devkit/core'; + +// @public (undocumented) +export class Architect { + constructor(_host: ArchitectHost, registry?: json.schema.SchemaRegistry, additionalJobRegistry?: Registry); + // (undocumented) + has(name: JobName): Observable; + // (undocumented) + scheduleBuilder(name: string, options: json.JsonObject, scheduleOptions?: ScheduleOptions): Promise; + // (undocumented) + scheduleTarget(target: Target, overrides?: json.JsonObject, scheduleOptions?: ScheduleOptions): Promise; +} + +// @public +export interface Builder { + // (undocumented) + [BuilderSymbol]: true; + // (undocumented) + [BuilderVersionSymbol]: string; + // (undocumented) + __OptionT: OptionT; + // (undocumented) + handler: JobHandler; +} + +// @public +export interface BuilderContext { + addTeardown(teardown: () => Promise | void): void; + builder: BuilderInfo; + currentDirectory: string; + getBuilderNameForTarget(target: Target): Promise; + // (undocumented) + getProjectMetadata(projectName: string): Promise; + // (undocumented) + getProjectMetadata(target: Target): Promise; + getTargetOptions(target: Target): Promise; + id: number; + logger: logging.LoggerApi; + reportProgress(current: number, total?: number, status?: string): void; + reportRunning(): void; + reportStatus(status: string): void; + scheduleBuilder(builderName: string, options?: json.JsonObject, scheduleOptions?: ScheduleOptions_2): Promise; + scheduleTarget(target: Target, overrides?: json.JsonObject, scheduleOptions?: ScheduleOptions_2): Promise; + target?: Target; + validateOptions(options: json.JsonObject, builderName: string): Promise; + workspaceRoot: string; +} + +// @public +export interface BuilderHandlerFn { + (input: A, context: BuilderContext): BuilderOutputLike; +} + +// @public +export type BuilderInfo = json.JsonObject & { + builderName: string; + description: string; + optionSchema: json.schema.JsonSchema; +}; + +// @public +export type BuilderInput = json.JsonObject & Schema; + +// @public (undocumented) +export type BuilderOutput = json.JsonObject & Schema_2; + +// @public +export type BuilderOutputLike = ObservableInput | BuilderOutput; + +// @public (undocumented) +export type BuilderProgress = json.JsonObject & Schema_3 & TypedBuilderProgress; + +// @public +export type BuilderProgressReport = BuilderProgress & { + target?: Target; + builder: BuilderInfo; +}; + +// @public (undocumented) +export enum BuilderProgressState { + // (undocumented) + Error = "error", + // (undocumented) + Running = "running", + // (undocumented) + Stopped = "stopped", + // (undocumented) + Waiting = "waiting" +} + +// @public (undocumented) +export type BuilderRegistry = Registry; + +// @public +export interface BuilderRun { + id: number; + info: BuilderInfo; + lastOutput: Promise; + output: Observable; + progress: Observable; + result: Promise; + stop(): Promise; +} + +// @public (undocumented) +class ChannelAlreadyExistException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export function createBuilder(fn: BuilderHandlerFn): Builder; + +// @public +function createDispatcher(options?: Partial>): JobDispatcher; + +// @public +function createJobFactory(loader: () => Promise>, options?: Partial): JobHandler; + +// @public +function createJobHandler(fn: SimpleJobHandlerFn, options?: Partial): JobHandler; + +// @public +function createLoggerJob(job: JobHandler, logger: logging.LoggerApi): JobHandler; + +// @public +class FallbackRegistry implements Registry { + constructor(_fallbacks?: Registry[]); + // (undocumented) + addFallback(registry: Registry): void; + // (undocumented) + protected _fallbacks: Registry[]; + // (undocumented) + get(name: JobName): Observable | null>; +} + +// @public (undocumented) +export function fromAsyncIterable(iterable: AsyncIterable): Observable; + +// @public (undocumented) +export function isBuilderOutput(obj: any): obj is BuilderOutput; + +// @public (undocumented) +function isJobHandler(value: unknown): value is JobHandler; + +// @public +interface Job { + readonly argument: ArgumentT; + readonly description: Observable; + getChannel(name: string, schema?: schema.JsonSchema): Observable; + readonly inboundBus: Observer>; + readonly input: Observer; + readonly outboundBus: Observable>; + readonly output: Observable; + ping(): Observable; + readonly state: JobState; + stop(): void; +} + +// @public (undocumented) +class JobArgumentSchemaValidationError extends schema.SchemaValidationException { + constructor(errors?: schema.SchemaValidatorError[]); +} + +// @public +interface JobDescription extends JsonObject { + // (undocumented) + readonly argument: DeepReadonly; + // (undocumented) + readonly input: DeepReadonly; + // (undocumented) + readonly name: JobName; + // (undocumented) + readonly output: DeepReadonly; +} + +// @public +interface JobDispatcher extends JobHandler { + addConditionalJob(predicate: (args: A) => boolean, name: string): void; + setDefaultJob(name: JobName | null | JobHandler): void; +} + +// @public (undocumented) +class JobDoesNotExistException extends BaseException { + constructor(name: JobName); +} + +// @public +interface JobHandler { + // (undocumented) + (argument: ArgT, context: JobHandlerContext): Observable>; + // (undocumented) + jobDescription: Partial; +} + +// @public +interface JobHandlerContext { + // (undocumented) + readonly dependencies: Job[]; + // (undocumented) + readonly description: JobDescription; + // (undocumented) + readonly inboundBus: Observable>; + // (undocumented) + readonly scheduler: Scheduler; +} + +// @public (undocumented) +type JobInboundMessage = JobInboundMessagePing | JobInboundMessageStop | JobInboundMessageInput; + +// @public +interface JobInboundMessageBase extends JsonObject { + readonly kind: JobInboundMessageKind; +} + +// @public +interface JobInboundMessageInput extends JobInboundMessageBase { + // (undocumented) + readonly kind: JobInboundMessageKind.Input; + readonly value: InputT; +} + +// @public +enum JobInboundMessageKind { + // (undocumented) + Input = "in", + // (undocumented) + Ping = "ip", + // (undocumented) + Stop = "is" +} + +// @public +interface JobInboundMessagePing extends JobInboundMessageBase { + readonly id: number; + // (undocumented) + readonly kind: JobInboundMessageKind.Ping; +} + +// @public (undocumented) +class JobInboundMessageSchemaValidationError extends schema.SchemaValidationException { + constructor(errors?: schema.SchemaValidatorError[]); +} + +// @public +interface JobInboundMessageStop extends JobInboundMessageBase { + // (undocumented) + readonly kind: JobInboundMessageKind.Stop; +} + +// @public +type JobName = string; + +// @public (undocumented) +class JobNameAlreadyRegisteredException extends BaseException { + constructor(name: JobName); +} + +// @public +type JobOutboundMessage = JobOutboundMessageOnReady | JobOutboundMessageStart | JobOutboundMessageOutput | JobOutboundMessageChannelCreate | JobOutboundMessageChannelMessage | JobOutboundMessageChannelError | JobOutboundMessageChannelComplete | JobOutboundMessageEnd | JobOutboundMessagePong; + +// @public +interface JobOutboundMessageBase { + readonly description: JobDescription; + readonly kind: JobOutboundMessageKind; +} + +// @public +interface JobOutboundMessageChannelBase extends JobOutboundMessageBase { + readonly name: string; +} + +// @public +interface JobOutboundMessageChannelComplete extends JobOutboundMessageChannelBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.ChannelComplete; +} + +// @public +interface JobOutboundMessageChannelCreate extends JobOutboundMessageChannelBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.ChannelCreate; +} + +// @public +interface JobOutboundMessageChannelError extends JobOutboundMessageChannelBase { + readonly error: JsonValue; + // (undocumented) + readonly kind: JobOutboundMessageKind.ChannelError; +} + +// @public +interface JobOutboundMessageChannelMessage extends JobOutboundMessageChannelBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.ChannelMessage; + readonly message: JsonValue; +} + +// @public +interface JobOutboundMessageEnd extends JobOutboundMessageBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.End; +} + +// @public +enum JobOutboundMessageKind { + // (undocumented) + ChannelComplete = "cc", + // (undocumented) + ChannelCreate = "cn", + // (undocumented) + ChannelError = "ce", + // (undocumented) + ChannelMessage = "cm", + // (undocumented) + End = "e", + // (undocumented) + OnReady = "c", + // (undocumented) + Output = "o", + // (undocumented) + Pong = "p", + // (undocumented) + Start = "s" +} + +// @public +interface JobOutboundMessageOnReady extends JobOutboundMessageBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.OnReady; +} + +// @public +interface JobOutboundMessageOutput extends JobOutboundMessageBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.Output; + readonly value: OutputT; +} + +// @public +interface JobOutboundMessagePong extends JobOutboundMessageBase { + readonly id: number; + // (undocumented) + readonly kind: JobOutboundMessageKind.Pong; +} + +// @public +interface JobOutboundMessageStart extends JobOutboundMessageBase { + // (undocumented) + readonly kind: JobOutboundMessageKind.Start; +} + +// @public (undocumented) +class JobOutputSchemaValidationError extends schema.SchemaValidationException { + constructor(errors?: schema.SchemaValidatorError[]); +} + +declare namespace jobs { + export { + strategy, + isJobHandler, + JobName, + JobHandler, + JobHandlerContext, + JobDescription, + JobInboundMessageKind, + JobInboundMessageBase, + JobInboundMessagePing, + JobInboundMessageStop, + JobInboundMessageInput, + JobInboundMessage, + JobOutboundMessageKind, + JobOutboundMessageBase, + JobOutboundMessageOnReady, + JobOutboundMessageStart, + JobOutboundMessageOutput, + JobOutboundMessageChannelBase, + JobOutboundMessageChannelMessage, + JobOutboundMessageChannelError, + JobOutboundMessageChannelCreate, + JobOutboundMessageChannelComplete, + JobOutboundMessageEnd, + JobOutboundMessagePong, + JobOutboundMessage, + JobState, + Job, + ScheduleJobOptions, + Registry, + Scheduler, + createJobHandler, + createJobFactory, + createLoggerJob, + ChannelAlreadyExistException, + SimpleJobHandlerContext, + SimpleJobHandlerFn, + JobNameAlreadyRegisteredException, + JobDoesNotExistException, + createDispatcher, + JobDispatcher, + FallbackRegistry, + RegisterJobOptions, + SimpleJobRegistry, + JobArgumentSchemaValidationError, + JobInboundMessageSchemaValidationError, + JobOutputSchemaValidationError, + SimpleScheduler + } +} +export { jobs } + +// @public +enum JobState { + Ended = "ended", + Errored = "errored", + Queued = "queued", + Ready = "ready", + Started = "started" +} + +// @public (undocumented) +type JobStrategy = (handler: JobHandler, options?: Partial>) => JobHandler; + +// @public +function memoize(replayMessages?: boolean): JobStrategy; + +// @public +interface RegisterJobOptions extends Partial { +} + +// @public (undocumented) +interface Registry { + get(name: JobName): Observable | null>; +} + +// @public +function reuse(replayMessages?: boolean): JobStrategy; + +// @public +interface ScheduleJobOptions { + dependencies?: Job | Job[]; +} + +// @public (undocumented) +export interface ScheduleOptions { + // (undocumented) + logger?: logging.Logger; +} + +// @public +interface Scheduler { + getDescription(name: JobName): Observable; + has(name: JobName): Observable; + pause(): () => void; + schedule(name: JobName, argument: A, options?: ScheduleJobOptions): Job; +} + +// @public +export function scheduleTargetAndForget(context: BuilderContext, target: Target, overrides?: json.JsonObject, scheduleOptions?: ScheduleOptions_2): Observable; + +// @public +function serialize(): JobStrategy; + +// @public +interface SimpleJobHandlerContext extends JobHandlerContext { + // (undocumented) + addTeardown(teardown: () => Promise | void): void; + // (undocumented) + createChannel: (name: string) => Observer; + // (undocumented) + input: Observable; +} + +// @public +type SimpleJobHandlerFn = (input: A, context: SimpleJobHandlerContext) => O | Promise | Observable; + +// @public +class SimpleJobRegistry implements Registry { + // (undocumented) + get(name: JobName): Observable | null>; + getJobNames(): JobName[]; + register(name: JobName, handler: JobHandler, options?: RegisterJobOptions): void; + register(handler: JobHandler, options?: RegisterJobOptions & { + name: string; + }): void; + // (undocumented) + protected _register(name: JobName, handler: JobHandler, options: RegisterJobOptions): void; +} + +// @public +class SimpleScheduler implements Scheduler { + constructor(_jobRegistry: Registry, _schemaRegistry?: schema.SchemaRegistry); + getDescription(name: JobName): Observable; + has(name: JobName): Observable; + // (undocumented) + protected _jobRegistry: Registry; + pause(): () => void; + schedule(name: JobName, argument: A, options?: ScheduleJobOptions): Job; + // (undocumented) + protected _scheduleJob(name: JobName, argument: A, options: ScheduleJobOptions, waitable: Observable): Job; + // (undocumented) + protected _schemaRegistry: schema.SchemaRegistry; +} + +declare namespace strategy { + export { + serialize, + reuse, + memoize, + JobStrategy + } +} + +// @public (undocumented) +export type Target = json.JsonObject & Target_2; + +// @public +export function targetFromTargetString(specifier: string, abbreviatedProjectName?: string, abbreviatedTargetName?: string): Target; + +// @public +export function targetStringFromTarget({ project, target, configuration }: Target): string; + +// @public +export type TypedBuilderProgress = { + state: BuilderProgressState.Stopped; +} | { + state: BuilderProgressState.Error; + error: json.JsonValue; +} | { + state: BuilderProgressState.Waiting; + status?: string; +} | { + state: BuilderProgressState.Running; + status?: string; + current: number; + total?: number; +}; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/build_angular/index.api.md b/goldens/public-api/angular_devkit/build_angular/index.api.md new file mode 100644 index 000000000000..cb46b4458351 --- /dev/null +++ b/goldens/public-api/angular_devkit/build_angular/index.api.md @@ -0,0 +1,372 @@ +## API Report File for "@angular-devkit/build-angular" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { ApplicationBuilderOptions } from '@angular/build'; +import { buildApplication } from '@angular/build'; +import { BuilderContext } from '@angular-devkit/architect'; +import { BuilderOutput } from '@angular-devkit/architect'; +import type { ConfigOptions } from 'karma'; +import { Configuration } from 'webpack'; +import { DevServerBuilderOutput } from '@angular/build'; +import type http from 'node:http'; +import { IndexHtmlTransform } from '@angular/build/private'; +import { Observable } from 'rxjs'; +import type { Plugin as Plugin_2 } from 'esbuild'; +import webpack from 'webpack'; +import { WebpackLoggingCallback } from '@angular-devkit/build-webpack'; + +export { ApplicationBuilderOptions } + +// @public (undocumented) +export type AssetPattern = AssetPatternObject | string; + +// @public (undocumented) +export type AssetPatternObject = { + followSymlinks?: boolean; + glob: string; + ignore?: string[]; + input: string; + output?: string; +}; + +// @public +export type BrowserBuilderOptions = { + allowedCommonJsDependencies?: string[]; + aot?: boolean; + assets?: AssetPattern[]; + baseHref?: string; + budgets?: Budget[]; + buildOptimizer?: boolean; + commonChunk?: boolean; + crossOrigin?: CrossOrigin; + deleteOutputPath?: boolean; + deployUrl?: string; + extractLicenses?: boolean; + fileReplacements?: FileReplacement[]; + i18nDuplicateTranslation?: I18NTranslation; + i18nMissingTranslation?: I18NTranslation; + index: IndexUnion; + inlineStyleLanguage?: InlineStyleLanguage; + localize?: Localize; + main: string; + namedChunks?: boolean; + ngswConfigPath?: string; + optimization?: OptimizationUnion; + outputHashing?: OutputHashing; + outputPath: string; + poll?: number; + polyfills?: Polyfills; + preserveSymlinks?: boolean; + progress?: boolean; + resourcesOutputPath?: string; + scripts?: ScriptElement[]; + serviceWorker?: boolean; + sourceMap?: SourceMapUnion; + statsJson?: boolean; + stylePreprocessorOptions?: StylePreprocessorOptions; + styles?: StyleElement[]; + subresourceIntegrity?: boolean; + tsConfig: string; + vendorChunk?: boolean; + verbose?: boolean; + watch?: boolean; + webWorkerTsConfig?: string; +}; + +// @public +export type BrowserBuilderOutput = BuilderOutput & { + stats: BuildEventStats; + baseOutputPath: string; + outputs: { + locale?: string; + path: string; + baseHref?: string; + }[]; +}; + +// @public (undocumented) +export type Budget = { + baseline?: string; + error?: string; + maximumError?: string; + maximumWarning?: string; + minimumError?: string; + minimumWarning?: string; + name?: string; + type: Type; + warning?: string; +}; + +export { buildApplication } + +// @public +export enum CrossOrigin { + // (undocumented) + Anonymous = "anonymous", + // (undocumented) + None = "none", + // (undocumented) + UseCredentials = "use-credentials" +} + +// @public +export type DevServerBuilderOptions = { + allowedHosts?: string[]; + buildTarget: string; + disableHostCheck?: boolean; + forceEsbuild?: boolean; + headers?: { + [key: string]: string; + }; + hmr?: boolean; + host?: string; + inspect?: Inspect; + liveReload?: boolean; + open?: boolean; + poll?: number; + port?: number; + prebundle?: PrebundleUnion; + proxyConfig?: string; + publicHost?: string; + servePath?: string; + ssl?: boolean; + sslCert?: string; + sslKey?: string; + verbose?: boolean; + watch?: boolean; +}; + +export { DevServerBuilderOutput } + +// @public +export function executeBrowserBuilder(options: BrowserBuilderOptions, context: BuilderContext, transforms?: { + webpackConfiguration?: ExecutionTransformer; + logging?: WebpackLoggingCallback; + indexHtml?: IndexHtmlTransform; +}): Observable; + +// @public +export function executeDevServerBuilder(options: DevServerBuilderOptions, context: BuilderContext, transforms?: { + webpackConfiguration?: ExecutionTransformer; + logging?: WebpackLoggingCallback; + indexHtml?: IndexHtmlTransform; +}, extensions?: { + buildPlugins?: Plugin_2[]; + middleware?: ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[]; + builderSelector?: (info: BuilderSelectorInfo, logger: BuilderContext['logger']) => string; +}): Observable; + +// @public +export function executeExtractI18nBuilder(options: ExtractI18nBuilderOptions, context: BuilderContext, transforms?: { + webpackConfiguration?: ExecutionTransformer; +}): Promise; + +// @public +export function executeKarmaBuilder(options: KarmaBuilderOptions, context: BuilderContext, transforms?: { + webpackConfiguration?: ExecutionTransformer; + karmaOptions?: (options: KarmaConfigOptions) => KarmaConfigOptions; +}): Observable; + +// @public +export function executeNgPackagrBuilder(options: NgPackagrBuilderOptions, context: BuilderContext): Observable; + +// @public +export function executeProtractorBuilder(options: ProtractorBuilderOptions, context: BuilderContext): Promise; + +// @public +export function executeServerBuilder(options: ServerBuilderOptions, context: BuilderContext, transforms?: { + webpackConfiguration?: ExecutionTransformer; +}): Observable; + +// @public (undocumented) +export function executeSSRDevServerBuilder(options: SSRDevServerBuilderOptions, context: BuilderContext): Observable; + +// @public +export type ExecutionTransformer = (input: T) => T | Promise; + +// @public +export type ExtractI18nBuilderOptions = { + buildTarget?: string; + format?: Format; + i18nDuplicateTranslation?: I18NDuplicateTranslation; + outFile?: string; + outputPath?: string; + progress?: boolean; +}; + +// @public (undocumented) +export type FileReplacement = { + replace?: string; + replaceWith?: string; + src?: string; + with?: string; +}; + +// @public +export type KarmaBuilderOptions = { + aot?: boolean; + assets?: AssetPattern_2[]; + browsers?: Browsers; + builderMode?: BuilderMode; + codeCoverage?: boolean; + codeCoverageExclude?: string[]; + exclude?: string[]; + fileReplacements?: FileReplacement_2[]; + include?: string[]; + inlineStyleLanguage?: InlineStyleLanguage_2; + karmaConfig?: string; + main?: string; + poll?: number; + polyfills?: Polyfills_2; + preserveSymlinks?: boolean; + progress?: boolean; + reporters?: string[]; + scripts?: ScriptElement_2[]; + sourceMap?: SourceMapUnion_2; + stylePreprocessorOptions?: StylePreprocessorOptions_2; + styles?: StyleElement_2[]; + tsConfig: string; + watch?: boolean; + webWorkerTsConfig?: string; +}; + +// @public (undocumented) +export type KarmaConfigOptions = ConfigOptions & { + buildWebpack?: unknown; + configFile?: string; +}; + +// @public +export type NgPackagrBuilderOptions = { + poll?: number; + project: string; + tsConfig?: string; + watch?: boolean; +}; + +// @public (undocumented) +export type OptimizationObject = { + fonts?: FontsUnion; + scripts?: boolean; + styles?: StylesUnion; +}; + +// @public +export type OptimizationUnion = boolean | OptimizationObject; + +// @public +export enum OutputHashing { + // (undocumented) + All = "all", + // (undocumented) + Bundles = "bundles", + // (undocumented) + Media = "media", + // (undocumented) + None = "none" +} + +// @public +export type ProtractorBuilderOptions = { + baseUrl?: string; + devServerTarget?: string; + grep?: string; + host?: string; + invertGrep?: boolean; + port?: number; + protractorConfig: string; + specs?: string[]; + suite?: string; + webdriverUpdate?: boolean; +}; + +// @public (undocumented) +export type ServerBuilderOptions = { + assets?: AssetPattern_3[]; + buildOptimizer?: boolean; + deleteOutputPath?: boolean; + deployUrl?: string; + externalDependencies?: string[]; + extractLicenses?: boolean; + fileReplacements?: FileReplacement_3[]; + i18nDuplicateTranslation?: I18NTranslation_2; + i18nMissingTranslation?: I18NTranslation_2; + inlineStyleLanguage?: InlineStyleLanguage_3; + localize?: Localize_2; + main: string; + namedChunks?: boolean; + optimization?: OptimizationUnion_2; + outputHashing?: OutputHashing_2; + outputPath: string; + poll?: number; + preserveSymlinks?: boolean; + progress?: boolean; + resourcesOutputPath?: string; + sourceMap?: SourceMapUnion_3; + statsJson?: boolean; + stylePreprocessorOptions?: StylePreprocessorOptions_3; + tsConfig: string; + vendorChunk?: boolean; + verbose?: boolean; + watch?: boolean; +}; + +// @public +export type ServerBuilderOutput = BuilderOutput & { + baseOutputPath: string; + outputPath: string; + outputs: { + locale?: string; + path: string; + }[]; +}; + +// @public (undocumented) +export type SourceMapObject = { + hidden?: boolean; + scripts?: boolean; + styles?: boolean; + vendor?: boolean; +}; + +// @public +export type SourceMapUnion = boolean | SourceMapObject; + +// @public (undocumented) +export type SSRDevServerBuilderOptions = Schema; + +// @public (undocumented) +export type SSRDevServerBuilderOutput = BuilderOutput & { + baseUrl?: string; + port?: string; +}; + +// @public +export type StylePreprocessorOptions = { + includePaths?: string[]; +}; + +// @public +export enum Type { + // (undocumented) + All = "all", + // (undocumented) + AllScript = "allScript", + // (undocumented) + Any = "any", + // (undocumented) + AnyComponentStyle = "anyComponentStyle", + // (undocumented) + AnyScript = "anyScript", + // (undocumented) + Bundle = "bundle", + // (undocumented) + Initial = "initial" +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/build_webpack/index.api.md b/goldens/public-api/angular_devkit/build_webpack/index.api.md new file mode 100644 index 000000000000..0d60187627d5 --- /dev/null +++ b/goldens/public-api/angular_devkit/build_webpack/index.api.md @@ -0,0 +1,79 @@ +## API Report File for "@angular-devkit/build-webpack" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BuilderContext } from '@angular-devkit/architect'; +import { BuilderOutput } from '@angular-devkit/architect'; +import { Observable } from 'rxjs'; +import type webpack from 'webpack'; +import type WebpackDevServer from 'webpack-dev-server'; + +// @public (undocumented) +export type BuildResult = BuilderOutput & { + emittedFiles?: EmittedFiles[]; + webpackStats?: webpack.StatsCompilation; + outputPath: string; +}; + +// @public (undocumented) +export type DevServerBuildOutput = BuildResult & { + port: number; + family: string; + address: string; +}; + +// @public (undocumented) +export interface EmittedFiles { + // (undocumented) + asset?: boolean; + // (undocumented) + extension: string; + // (undocumented) + file: string; + // (undocumented) + id?: string; + // (undocumented) + initial: boolean; + // (undocumented) + name?: string; +} + +// @public (undocumented) +export function runWebpack(config: webpack.Configuration, context: BuilderContext, options?: { + logging?: WebpackLoggingCallback; + webpackFactory?: WebpackFactory; + shouldProvideStats?: boolean; +}): Observable; + +// @public (undocumented) +export function runWebpackDevServer(config: webpack.Configuration, context: BuilderContext, options?: { + shouldProvideStats?: boolean; + devServerConfig?: WebpackDevServer.Configuration; + logging?: WebpackLoggingCallback; + webpackFactory?: WebpackFactory; + webpackDevServerFactory?: WebpackDevServerFactory; +}): Observable; + +// @public (undocumented) +export type WebpackBuilderSchema = Schema; + +// @public (undocumented) +export type WebpackDevServerFactory = typeof WebpackDevServer; + +// @public (undocumented) +export interface WebpackFactory { + // (undocumented) + (config: webpack.Configuration): Observable | webpack.Compiler | null; +} + +// @public (undocumented) +export interface WebpackLoggingCallback { + // (undocumented) + (stats: webpack.Stats, config: webpack.Configuration): void; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/core/index.api.md b/goldens/public-api/angular_devkit/core/index.api.md new file mode 100644 index 000000000000..748faf2bbeb4 --- /dev/null +++ b/goldens/public-api/angular_devkit/core/index.api.md @@ -0,0 +1,1400 @@ +## API Report File for "@angular-devkit/core" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { ErrorObject } from 'ajv'; +import { Format } from 'ajv'; +import { Observable } from 'rxjs'; +import { ObservableInput } from 'rxjs'; +import { Operator } from 'rxjs'; +import { PartialObserver } from 'rxjs'; +import { Position } from 'source-map'; +import { Subject } from 'rxjs'; +import { Subscription } from 'rxjs'; +import { ValidateFunction } from 'ajv'; + +// @public (undocumented) +function addUndefinedDefaults(value: JsonValue, _pointer: JsonPointer, schema?: JsonSchema): JsonValue; + +// @public (undocumented) +function addUndefinedObjectDefaults(value: JsonValue, _pointer: JsonPointer, schema?: JsonSchema): JsonValue; + +// @public +class AliasHost extends ResolverHost { + // (undocumented) + get aliases(): Map; + // (undocumented) + protected _aliases: Map; + // (undocumented) + protected _resolve(path: Path): Path; +} + +// @public (undocumented) +export function asPosixPath(path: Path): PosixPath; + +// @public (undocumented) +export function asWindowsPath(path: Path): WindowsPath; + +// @public +export class BaseException extends Error { + constructor(message?: string); +} + +// @public +export function basename(path: Path): PathFragment; + +// @public (undocumented) +function buildJsonPointer(fragments: string[]): JsonPointer; + +// @public +function camelize(str: string): string; + +// @public +function capitalize(str: string): string; + +// @public (undocumented) +export class CircularDependencyFoundException extends BaseException { + constructor(); +} + +// @public +function classify(str: string): string; + +// @public +class CordHost extends SimpleMemoryHost { + constructor(_back: ReadonlyHost); + // (undocumented) + protected _back: ReadonlyHost; + // (undocumented) + get backend(): ReadonlyHost; + // (undocumented) + get capabilities(): HostCapabilities; + clone(): CordHost; + commit(host: Host, force?: boolean): Observable; + create(path: Path, content: FileBuffer): Observable; + // (undocumented) + delete(path: Path): Observable; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + protected _filesToCreate: Set; + // (undocumented) + protected _filesToDelete: Set; + // (undocumented) + protected _filesToOverwrite: Set; + // (undocumented) + protected _filesToRename: Map; + // (undocumented) + protected _filesToRenameRevert: Map; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + overwrite(path: Path, content: FileBuffer): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + records(): CordHostRecord[]; + // (undocumented) + rename(from: Path, to: Path): Observable; + // (undocumented) + stat(path: Path): Observable | null; + // (undocumented) + watch(path: Path, options?: HostWatchOptions): null; + // (undocumented) + willCreate(path: Path): boolean; + // (undocumented) + willDelete(path: Path): boolean; + // (undocumented) + willOverwrite(path: Path): boolean; + // (undocumented) + willRename(path: Path): boolean; + // (undocumented) + willRenameTo(path: Path, to: Path): boolean; + // (undocumented) + write(path: Path, content: FileBuffer): Observable; +} + +// @public (undocumented) +interface CordHostCreate { + // (undocumented) + content: FileBuffer; + // (undocumented) + kind: 'create'; + // (undocumented) + path: Path; +} + +// @public (undocumented) +interface CordHostDelete { + // (undocumented) + kind: 'delete'; + // (undocumented) + path: Path; +} + +// @public (undocumented) +interface CordHostOverwrite { + // (undocumented) + content: FileBuffer; + // (undocumented) + kind: 'overwrite'; + // (undocumented) + path: Path; +} + +// @public (undocumented) +type CordHostRecord = CordHostCreate | CordHostOverwrite | CordHostRename | CordHostDelete; + +// @public (undocumented) +interface CordHostRename { + // (undocumented) + from: Path; + // (undocumented) + kind: 'rename'; + // (undocumented) + to: Path; +} + +// @public (undocumented) +class CoreSchemaRegistry implements SchemaRegistry { + constructor(formats?: SchemaFormat[]); + // (undocumented) + addFormat(format: SchemaFormat): void; + addPostTransform(visitor: JsonVisitor, deps?: JsonVisitor[]): void; + addPreTransform(visitor: JsonVisitor, deps?: JsonVisitor[]): void; + // (undocumented) + addSmartDefaultProvider(source: string, provider: SmartDefaultProvider): void; + compile(schema: JsonSchema): Promise; + // (undocumented) + registerUriHandler(handler: UriHandler): void; + // (undocumented) + protected _resolver(ref: string, validate?: ValidateFunction): { + context?: ValidateFunction; + schema?: JsonObject; + }; + // (undocumented) + usePromptProvider(provider: PromptProvider): void; + // (undocumented) + useXDeprecatedProvider(onUsage: (message: string) => void): void; + ɵflatten(schema: JsonObject): Promise; +} + +// @public (undocumented) +function createSyncHost(handler: SyncHostHandler): Host; + +// @public (undocumented) +function createWorkspaceHost(host: virtualFs.Host): WorkspaceHost; + +// @public +function dasherize(str: string): string; + +// @public +function decamelize(str: string): string; + +// @public +export function deepCopy(value: T): T; + +// @public (undocumented) +type DefinitionCollectionListener = (name: string, newValue: V | undefined, collection: DefinitionCollection) => void; + +// @public (undocumented) +export class DependencyNotFoundException extends BaseException { + constructor(); +} + +// @public +export function dirname(path: Path): Path; + +// @public (undocumented) +class Empty implements ReadonlyHost { + // (undocumented) + readonly capabilities: HostCapabilities; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + stat(path: Path): Observable | null>; +} + +// @public (undocumented) +export function extname(path: Path): string; + +// @public (undocumented) +export class FileAlreadyExistException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +type FileBuffer = ArrayBuffer; + +// @public (undocumented) +type FileBufferLike = ArrayBufferLike; + +// @public (undocumented) +function fileBufferToString(fileBuffer: FileBuffer): string; + +// @public (undocumented) +export class FileDoesNotExistException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export function fragment(path: string): PathFragment; + +// @public (undocumented) +export function getSystemPath(path: Path): string; + +// @public (undocumented) +function getTypesOfSchema(schema: JsonSchema): Set; + +// @public (undocumented) +interface Host extends ReadonlyHost { + // (undocumented) + delete(path: Path): Observable; + // (undocumented) + rename(from: Path, to: Path): Observable; + // (undocumented) + watch(path: Path, options?: HostWatchOptions): Observable | null; + // (undocumented) + write(path: Path, content: FileBufferLike): Observable; +} + +// @public (undocumented) +interface HostCapabilities { + // (undocumented) + synchronous: boolean; +} + +// @public (undocumented) +interface HostWatchEvent { + // (undocumented) + readonly path: Path; + // (undocumented) + readonly time: Date; + // (undocumented) + readonly type: HostWatchEventType; +} + +// @public (undocumented) +enum HostWatchEventType { + // (undocumented) + Changed = 0, + // (undocumented) + Created = 1, + // (undocumented) + Deleted = 2, + // (undocumented) + Renamed = 3 +} + +// @public (undocumented) +interface HostWatchOptions { + // (undocumented) + readonly persistent?: boolean; + // (undocumented) + readonly recursive?: boolean; +} + +// @public (undocumented) +function indentBy(indentations: number): TemplateTag; + +// @public (undocumented) +class IndentLogger extends Logger { + constructor(name: string, parent?: Logger | null, indentation?: string); +} + +// @public (undocumented) +export class InvalidPathException extends BaseException { + constructor(path: string); +} + +// @public +export function isAbsolute(p: Path): boolean; + +// @public (undocumented) +export function isJsonArray(value: JsonValue): value is JsonArray; + +// @public (undocumented) +export function isJsonObject(value: JsonValue): value is JsonObject; + +// @public (undocumented) +function isJsonSchema(value: unknown): value is JsonSchema; + +// @public +export function isPromise(obj: any): obj is Promise; + +// @public +export function join(p1: Path, ...others: string[]): Path; + +// @public (undocumented) +function joinJsonPointer(root: JsonPointer, ...others: string[]): JsonPointer; + +declare namespace json { + export { + schema, + isJsonObject, + isJsonArray, + JsonArray, + JsonObject, + JsonValue + } +} +export { json } + +// @public +export interface JsonArray extends Array { +} + +// @public (undocumented) +export interface JsonObject { + // (undocumented) + [prop: string]: JsonValue; +} + +// @public (undocumented) +type JsonPointer = string & { + __PRIVATE_DEVKIT_JSON_POINTER: void; +}; + +// @public +type JsonSchema = JsonObject | boolean; + +// @public (undocumented) +interface JsonSchemaVisitor { + // (undocumented) + (current: JsonObject | JsonArray, pointer: JsonPointer, parentSchema?: JsonObject | JsonArray, index?: string): void; +} + +// @public (undocumented) +export type JsonValue = boolean | string | number | JsonArray | JsonObject | null; + +// @public (undocumented) +interface JsonVisitor { + // (undocumented) + (value: JsonValue, pointer: JsonPointer, schema?: JsonObject, root?: JsonObject | JsonArray): Observable | JsonValue; +} + +// @public (undocumented) +class LevelCapLogger extends LevelTransformLogger { + constructor(name: string, parent: Logger | null, levelCap: LogLevel); + // (undocumented) + readonly levelCap: LogLevel; + // (undocumented) + static levelMap: { + [cap: string]: { + [level: string]: string; + }; + }; + // (undocumented) + readonly name: string; + // (undocumented) + readonly parent: Logger | null; +} + +// @public (undocumented) +class LevelTransformLogger extends Logger { + constructor(name: string, parent: Logger | null, levelTransform: (level: LogLevel) => LogLevel); + // (undocumented) + createChild(name: string): Logger; + // (undocumented) + readonly levelTransform: (level: LogLevel) => LogLevel; + // (undocumented) + log(level: LogLevel, message: string, metadata?: JsonObject): void; + // (undocumented) + readonly name: string; + // (undocumented) + readonly parent: Logger | null; +} + +// @public +function levenshtein(a: string, b: string): number; + +// @public (undocumented) +interface LogEntry extends LoggerMetadata { + // (undocumented) + level: LogLevel; + // (undocumented) + message: string; + // (undocumented) + timestamp: number; +} + +// @public (undocumented) +class Logger extends Observable implements LoggerApi { + constructor(name: string, parent?: Logger | null); + // (undocumented) + asApi(): LoggerApi; + // (undocumented) + complete(): void; + // (undocumented) + createChild(name: string): Logger; + // (undocumented) + debug(message: string, metadata?: JsonObject): void; + // (undocumented) + error(message: string, metadata?: JsonObject): void; + // (undocumented) + fatal(message: string, metadata?: JsonObject): void; + // (undocumented) + forEach(next: (value: LogEntry) => void, promiseCtor?: PromiseConstructorLike): Promise; + // (undocumented) + info(message: string, metadata?: JsonObject): void; + // (undocumented) + lift(operator: Operator): Observable; + // (undocumented) + log(level: LogLevel, message: string, metadata?: JsonObject): void; + // (undocumented) + protected _metadata: LoggerMetadata; + // (undocumented) + readonly name: string; + // (undocumented) + next(entry: LogEntry): void; + // (undocumented) + protected get _observable(): Observable; + protected set _observable(v: Observable); + // (undocumented) + readonly parent: Logger | null; + // (undocumented) + protected readonly _subject: Subject; + // (undocumented) + subscribe(): Subscription; + // (undocumented) + subscribe(observer: PartialObserver): Subscription; + // (undocumented) + subscribe(next?: (value: LogEntry) => void, error?: (error: Error) => void, complete?: () => void): Subscription; + // (undocumented) + toString(): string; + // (undocumented) + warn(message: string, metadata?: JsonObject): void; +} + +// @public (undocumented) +interface LoggerApi { + // (undocumented) + createChild(name: string): Logger; + // (undocumented) + debug(message: string, metadata?: JsonObject): void; + // (undocumented) + error(message: string, metadata?: JsonObject): void; + // (undocumented) + fatal(message: string, metadata?: JsonObject): void; + // (undocumented) + info(message: string, metadata?: JsonObject): void; + // (undocumented) + log(level: LogLevel, message: string, metadata?: JsonObject): void; + // (undocumented) + warn(message: string, metadata?: JsonObject): void; +} + +// @public (undocumented) +interface LoggerMetadata extends JsonObject { + // (undocumented) + name: string; + // (undocumented) + path: string[]; +} + +declare namespace logging { + export { + IndentLogger, + LevelTransformLogger, + LevelCapLogger, + LoggerMetadata, + LogEntry, + LoggerApi, + LogLevel, + Logger, + NullLogger, + TransformLogger + } +} +export { logging } + +// @public (undocumented) +type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'; + +// @public +function mergeSchemas(...schemas: (JsonSchema | undefined)[]): JsonSchema; + +// @public +export function noCacheNormalize(path: string): Path; + +// @public +export function normalize(path: string): Path; + +// @public +export const NormalizedRoot: Path; + +// @public +export const NormalizedSep: Path; + +// @public (undocumented) +class NullLogger extends Logger { + constructor(parent?: Logger | null); + // (undocumented) + asApi(): LoggerApi; +} + +// @public (undocumented) +function oneLine(strings: TemplateStringsArray, ...values: any[]): string; + +// @public (undocumented) +function parseJsonPointer(pointer: JsonPointer): string[]; + +// @public (undocumented) +export class PartiallyOrderedSet { + // (undocumented) + [Symbol.iterator](): IterableIterator; + // (undocumented) + get [Symbol.toStringTag](): 'PartiallyOrderedSet'; + // (undocumented) + add(item: T, deps?: Set | T[]): this; + // (undocumented) + protected _checkCircularDependencies(item: T, deps: Set): void; + // (undocumented) + clear(): void; + // (undocumented) + delete(item: T): boolean; + entries(): IterableIterator<[T, T]>; + // (undocumented) + forEach(callbackfn: (value: T, value2: T, set: PartiallyOrderedSet) => void, thisArg?: any): void; + // (undocumented) + has(item: T): boolean; + keys(): IterableIterator; + // (undocumented) + get size(): number; + values(): IterableIterator; +} + +// @public +export type Path = string & { + __PRIVATE_DEVKIT_PATH: void; +}; + +// @public (undocumented) +export const path: TemplateTag; + +// @public (undocumented) +export class PathCannotBeFragmentException extends BaseException { + constructor(path: string); +} + +// @public +export type PathFragment = Path & { + __PRIVATE_DEVKIT_PATH_FRAGMENT: void; +}; + +// @public (undocumented) +export class PathIsDirectoryException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export class PathIsFileException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export class PathMustBeAbsoluteException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +class PatternMatchingHost extends ResolverHost { + // (undocumented) + addPattern(pattern: string | string[], replacementFn: ReplacementFunction): void; + // (undocumented) + protected _patterns: Map; + // (undocumented) + protected _resolve(path: Path): Path; +} + +// @public (undocumented) +export type PosixPath = string & { + __PRIVATE_DEVKIT_POSIX_PATH: void; +}; + +// @public +export class PriorityQueue { + constructor(_comparator: (x: T, y: T) => number); + // (undocumented) + clear(): void; + // (undocumented) + peek(): T | undefined; + // (undocumented) + pop(): T | undefined; + // (undocumented) + push(item: T): void; + // (undocumented) + get size(): number; + // (undocumented) + toArray(): Array; +} + +// @public (undocumented) +interface ProjectDefinition { + // (undocumented) + readonly extensions: Record; + // (undocumented) + prefix?: string; + // (undocumented) + root: string; + // (undocumented) + sourceRoot?: string; + // (undocumented) + readonly targets: TargetDefinitionCollection; +} + +// @public (undocumented) +class ProjectDefinitionCollection extends DefinitionCollection { + constructor(initial?: Record, listener?: DefinitionCollectionListener); + // (undocumented) + add(definition: { + name: string; + root: string; + sourceRoot?: string; + prefix?: string; + targets?: Record; + [key: string]: unknown; + }): ProjectDefinition; + // (undocumented) + set(name: string, value: ProjectDefinition): this; +} + +// @public (undocumented) +interface PromptDefinition { + // (undocumented) + default?: string | string[] | number | boolean | null; + // (undocumented) + id: string; + // (undocumented) + items?: Array; + // (undocumented) + message: string; + // (undocumented) + multiselect?: boolean; + // (undocumented) + propertyTypes: Set; + // (undocumented) + raw?: string | JsonObject; + // (undocumented) + type: string; + // (undocumented) + validator?: (value: JsonValue) => boolean | string | Promise; +} + +// @public (undocumented) +type PromptProvider = (definitions: Array) => ObservableInput<{ + [id: string]: JsonValue; +}>; + +// @public (undocumented) +interface ReadonlyHost { + // (undocumented) + readonly capabilities: HostCapabilities; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + stat(path: Path): Observable | null> | null; +} + +// @public +function readWorkspace(path: string, host: WorkspaceHost, format?: WorkspaceFormat): Promise<{ + workspace: WorkspaceDefinition; +}>; + +// @public (undocumented) +interface ReferenceResolver { + // (undocumented) + (ref: string, context?: ContextT): { + context?: ContextT; + schema?: JsonObject; + }; +} + +// @public +export function relative(from: Path, to: Path): Path; + +// @public (undocumented) +type ReplacementFunction = (path: Path) => Path; + +// @public +export function resetNormalizeCache(): void; + +// @public +export function resolve(p1: Path, p2: Path): Path; + +// @public +abstract class ResolverHost implements Host { + constructor(_delegate: Host); + // (undocumented) + get capabilities(): HostCapabilities; + // (undocumented) + protected _delegate: Host; + // (undocumented) + delete(path: Path): Observable; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + rename(from: Path, to: Path): Observable; + // (undocumented) + protected abstract _resolve(path: Path): Path; + // (undocumented) + stat(path: Path): Observable | null> | null; + // (undocumented) + watch(path: Path, options?: HostWatchOptions): Observable | null; + // (undocumented) + write(path: Path, content: FileBuffer): Observable; +} + +// @public +class SafeReadonlyHost implements ReadonlyHost { + constructor(_delegate: ReadonlyHost); + // (undocumented) + get capabilities(): HostCapabilities; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + stat(path: Path): Observable | null> | null; +} + +declare namespace schema { + export { + transforms, + JsonPointer, + SchemaValidatorResult, + SchemaValidatorError, + SchemaValidatorOptions, + SchemaValidator, + SchemaFormatter, + SchemaFormat, + SmartDefaultProvider, + SchemaKeywordValidator, + PromptDefinition, + PromptProvider, + SchemaRegistry, + JsonSchemaVisitor, + JsonVisitor, + buildJsonPointer, + joinJsonPointer, + parseJsonPointer, + UriHandler, + SchemaValidationException, + CoreSchemaRegistry, + isJsonSchema, + mergeSchemas, + JsonSchema, + visitJson, + visitJsonSchema, + ReferenceResolver, + getTypesOfSchema + } +} +export { schema } + +// @public (undocumented) +interface SchemaFormat { + // (undocumented) + formatter: SchemaFormatter; + // (undocumented) + name: string; +} + +// @public (undocumented) +type SchemaFormatter = Format; + +// @public (undocumented) +interface SchemaKeywordValidator { + // (undocumented) + (data: JsonValue, schema: JsonValue, parent: JsonObject | JsonArray | undefined, parentProperty: string | number | undefined, pointer: JsonPointer, rootData: JsonValue): boolean | Observable; +} + +// @public (undocumented) +interface SchemaRegistry { + // (undocumented) + addFormat(format: SchemaFormat): void; + addPostTransform(visitor: JsonVisitor, deps?: JsonVisitor[]): void; + addPreTransform(visitor: JsonVisitor, deps?: JsonVisitor[]): void; + // (undocumented) + addSmartDefaultProvider(source: string, provider: SmartDefaultProvider): void; + // (undocumented) + compile(schema: Object): Promise; + // (undocumented) + usePromptProvider(provider: PromptProvider): void; + // (undocumented) + useXDeprecatedProvider(onUsage: (message: string) => void): void; + // (undocumented) + ɵflatten(schema: JsonObject | string): Promise; +} + +// @public (undocumented) +class SchemaValidationException extends BaseException { + constructor(errors?: SchemaValidatorError[], baseMessage?: string); + // (undocumented) + static createMessages(errors?: SchemaValidatorError[]): string[]; + // (undocumented) + readonly errors: SchemaValidatorError[]; +} + +// @public (undocumented) +interface SchemaValidator { + // (undocumented) + (data: JsonValue, options?: SchemaValidatorOptions): Promise; +} + +// @public (undocumented) +type SchemaValidatorError = Partial; + +// @public (undocumented) +interface SchemaValidatorOptions { + // (undocumented) + applyPostTransforms?: boolean; + // (undocumented) + applyPreTransforms?: boolean; + // (undocumented) + withPrompts?: boolean; +} + +// @public (undocumented) +interface SchemaValidatorResult { + // (undocumented) + data: JsonValue; + // (undocumented) + errors?: SchemaValidatorError[]; + // (undocumented) + success: boolean; +} + +// @public (undocumented) +class ScopedHost extends ResolverHost { + constructor(delegate: Host, _root?: Path); + // (undocumented) + protected _resolve(path: Path): Path; + // (undocumented) + protected _root: Path; +} + +// @public (undocumented) +class SimpleMemoryHost implements Host<{}> { + constructor(); + // (undocumented) + protected _cache: Map>; + // (undocumented) + get capabilities(): HostCapabilities; + // (undocumented) + delete(path: Path): Observable; + // (undocumented) + protected _delete(path: Path): void; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + protected _exists(path: Path): boolean; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + protected _isDirectory(path: Path): boolean; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + protected _isFile(path: Path): boolean; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + protected _list(path: Path): PathFragment[]; + // (undocumented) + protected _newDirStats(): Stats; + // (undocumented) + protected _newFileStats(content: FileBuffer, oldStats?: Stats): Stats; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + protected _read(path: Path): FileBuffer; + // (undocumented) + rename(from: Path, to: Path): Observable; + // (undocumented) + protected _rename(from: Path, to: Path): void; + // (undocumented) + reset(): void; + // (undocumented) + stat(path: Path): Observable | null> | null; + // (undocumented) + protected _stat(path: Path): Stats | null; + // (undocumented) + protected _toAbsolute(path: Path): Path; + // (undocumented) + protected _updateWatchers(path: Path, type: HostWatchEventType): void; + // (undocumented) + watch(path: Path, options?: HostWatchOptions): Observable | null; + // (undocumented) + protected _watch(path: Path, options?: HostWatchOptions): Observable; + // (undocumented) + write(path: Path, content: FileBuffer): Observable; + protected _write(path: Path, content: FileBuffer): void; +} + +// @public (undocumented) +interface SimpleMemoryHostStats { + // (undocumented) + readonly content: FileBuffer | null; + // (undocumented) + inspect(): string; +} + +// @public (undocumented) +interface SmartDefaultProvider { + // (undocumented) + (schema: JsonObject): T | Observable; +} + +// @public +export function split(path: Path): PathFragment[]; + +// @public (undocumented) +type Stats = T & { + isFile(): boolean; + isDirectory(): boolean; + readonly size: number; + readonly atime: Date; + readonly mtime: Date; + readonly ctime: Date; + readonly birthtime: Date; +}; + +declare namespace strings { + export { + decamelize, + dasherize, + camelize, + classify, + underscore, + capitalize, + levenshtein + } +} +export { strings } + +// @public (undocumented) +function stringToFileBuffer(str: string): FileBuffer; + +// @public (undocumented) +function stripIndent(strings: TemplateStringsArray, ...values: any[]): string; + +// @public (undocumented) +function stripIndents(strings: TemplateStringsArray, ...values: any[]): string; + +// @public +class SyncDelegateHost { + constructor(_delegate: Host); + // (undocumented) + get capabilities(): HostCapabilities; + // (undocumented) + get delegate(): Host; + // (undocumented) + protected _delegate: Host; + // (undocumented) + delete(path: Path): void; + // (undocumented) + protected _doSyncCall(observable: Observable): ResultT; + // (undocumented) + exists(path: Path): boolean; + // (undocumented) + isDirectory(path: Path): boolean; + // (undocumented) + isFile(path: Path): boolean; + // (undocumented) + list(path: Path): PathFragment[]; + // (undocumented) + read(path: Path): FileBuffer; + // (undocumented) + rename(from: Path, to: Path): void; + // (undocumented) + stat(path: Path): Stats | null; + // (undocumented) + watch(path: Path, options?: HostWatchOptions): Observable | null; + // (undocumented) + write(path: Path, content: FileBufferLike): void; +} + +// @public (undocumented) +interface SyncHostHandler { + // (undocumented) + delete(path: Path): void; + // (undocumented) + exists(path: Path): boolean; + // (undocumented) + isDirectory(path: Path): boolean; + // (undocumented) + isFile(path: Path): boolean; + // (undocumented) + list(path: Path): PathFragment[]; + // (undocumented) + read(path: Path): FileBuffer; + // (undocumented) + rename(from: Path, to: Path): void; + // (undocumented) + stat(path: Path): Stats | null; + // (undocumented) + write(path: Path, content: FileBufferLike): void; +} + +// @public (undocumented) +class SynchronousDelegateExpectedException extends BaseException { + constructor(); +} + +declare namespace tags { + export { + oneLine, + indentBy, + stripIndent, + stripIndents, + trimNewlines, + TemplateTag + } +} +export { tags } + +// @public (undocumented) +interface TargetDefinition { + // (undocumented) + builder: string; + // (undocumented) + configurations?: Record | undefined>; + // (undocumented) + defaultConfiguration?: string; + // (undocumented) + options?: Record; +} + +// @public (undocumented) +class TargetDefinitionCollection extends DefinitionCollection { + constructor(initial?: Record, listener?: DefinitionCollectionListener); + // (undocumented) + add(definition: { + name: string; + } & TargetDefinition): TargetDefinition; + // (undocumented) + set(name: string, value: TargetDefinition): this; +} + +// @public +export function template(content: string, options?: TemplateOptions): (input: T) => string; + +// @public +export interface TemplateAst { + // (undocumented) + children: TemplateAstNode[]; + // (undocumented) + content: string; + // (undocumented) + fileName: string; +} + +// @public +export interface TemplateAstBase { + // (undocumented) + end: Position; + // (undocumented) + start: Position; +} + +// @public +export interface TemplateAstComment extends TemplateAstBase { + // (undocumented) + kind: 'comment'; + // (undocumented) + text: string; +} + +// @public +export interface TemplateAstContent extends TemplateAstBase { + // (undocumented) + content: string; + // (undocumented) + kind: 'content'; +} + +// @public +export interface TemplateAstEscape extends TemplateAstBase { + // (undocumented) + expression: string; + // (undocumented) + kind: 'escape'; +} + +// @public +export interface TemplateAstEvaluate extends TemplateAstBase { + // (undocumented) + expression: string; + // (undocumented) + kind: 'evaluate'; +} + +// @public +export interface TemplateAstInterpolate extends TemplateAstBase { + // (undocumented) + expression: string; + // (undocumented) + kind: 'interpolate'; +} + +// @public (undocumented) +export type TemplateAstNode = TemplateAstContent | TemplateAstEvaluate | TemplateAstComment | TemplateAstEscape | TemplateAstInterpolate; + +// @public (undocumented) +export interface TemplateOptions { + // (undocumented) + fileName?: string; + // (undocumented) + module?: boolean | { + exports: {}; + }; + // (undocumented) + sourceMap?: boolean; + // (undocumented) + sourceRoot?: string; + // (undocumented) + sourceURL?: string; +} + +// @public +export function templateParser(sourceText: string, fileName: string): TemplateAst; + +// @public +interface TemplateTag { + // (undocumented) + (template: TemplateStringsArray, ...substitutions: any[]): R; +} + +declare namespace test { + export { + TestLogRecord, + TestHost + } +} + +// @public (undocumented) +class TestHost extends SimpleMemoryHost { + // (undocumented) + $exists(path: string): boolean; + // (undocumented) + $isDirectory(path: string): boolean; + // (undocumented) + $isFile(path: string): boolean; + // (undocumented) + $list(path: string): PathFragment[]; + // (undocumented) + $read(path: string): string; + // (undocumented) + $write(path: string, content: string): void; + constructor(map?: { + [path: string]: string; + }); + // (undocumented) + clearRecords(): void; + // (undocumented) + clone(): TestHost; + // (undocumented) + protected _delete(path: Path): void; + // (undocumented) + protected _exists(path: Path): boolean; + // (undocumented) + get files(): Path[]; + // (undocumented) + protected _isDirectory(path: Path): boolean; + // (undocumented) + protected _isFile(path: Path): boolean; + // (undocumented) + protected _list(path: Path): PathFragment[]; + // (undocumented) + protected _read(path: Path): ArrayBuffer; + // (undocumented) + get records(): TestLogRecord[]; + // (undocumented) + protected _records: TestLogRecord[]; + // (undocumented) + protected _rename(from: Path, to: Path): void; + // (undocumented) + protected _stat(path: Path): Stats | null; + // (undocumented) + get sync(): SyncDelegateHost<{}>; + // (undocumented) + protected _sync: SyncDelegateHost<{}> | null; + // (undocumented) + protected _watch(path: Path, options?: HostWatchOptions): Observable; + // (undocumented) + protected _write(path: Path, content: FileBuffer): void; +} + +// @public (undocumented) +type TestLogRecord = { + kind: 'write' | 'read' | 'delete' | 'list' | 'exists' | 'isDirectory' | 'isFile' | 'stat' | 'watch'; + path: Path; +} | { + kind: 'rename'; + from: Path; + to: Path; +}; + +// @public (undocumented) +class TransformLogger extends Logger { + constructor(name: string, transform: (stream: Observable) => Observable, parent?: Logger | null); +} + +declare namespace transforms { + export { + addUndefinedObjectDefaults, + addUndefinedDefaults + } +} + +// @public (undocumented) +function trimNewlines(strings: TemplateStringsArray, ...values: any[]): string; + +// @public +function underscore(str: string): string; + +// @public (undocumented) +export class UnknownException extends BaseException { + constructor(message: string); +} + +// @public (undocumented) +type UriHandler = (uri: string) => Observable | Promise | null | undefined; + +declare namespace virtualFs { + export { + test, + AliasHost, + stringToFileBuffer, + fileBufferToString, + createSyncHost, + SyncHostHandler, + Empty, + FileBuffer, + FileBufferLike, + HostWatchOptions, + HostWatchEventType, + Stats, + HostWatchEvent, + HostCapabilities, + ReadonlyHost, + Host, + SimpleMemoryHostStats, + SimpleMemoryHost, + ReplacementFunction, + PatternMatchingHost, + CordHostCreate, + CordHostOverwrite, + CordHostRename, + CordHostDelete, + CordHostRecord, + CordHost, + SafeReadonlyHost, + ScopedHost, + SynchronousDelegateExpectedException, + SyncDelegateHost, + ResolverHost + } +} +export { virtualFs } + +// @public +function visitJson(json: JsonValue, visitor: JsonVisitor, schema?: JsonSchema, refResolver?: ReferenceResolver, context?: ContextT): Observable; + +// @public (undocumented) +function visitJsonSchema(schema: JsonSchema, visitor: JsonSchemaVisitor): void; + +// @public (undocumented) +export type WindowsPath = string & { + __PRIVATE_DEVKIT_WINDOWS_PATH: void; +}; + +// @public (undocumented) +interface WorkspaceDefinition { + // (undocumented) + readonly extensions: Record; + // (undocumented) + readonly projects: ProjectDefinitionCollection; +} + +// @public +enum WorkspaceFormat { + // (undocumented) + JSON = 0 +} + +// @public (undocumented) +interface WorkspaceHost { + // (undocumented) + isDirectory(path: string): Promise; + // (undocumented) + isFile(path: string): Promise; + // (undocumented) + readFile(path: string): Promise; + // (undocumented) + writeFile(path: string, data: string): Promise; +} + +declare namespace workspaces { + export { + WorkspaceHost, + createWorkspaceHost, + WorkspaceFormat, + readWorkspace, + writeWorkspace, + WorkspaceDefinition, + ProjectDefinition, + TargetDefinition, + DefinitionCollectionListener, + ProjectDefinitionCollection, + TargetDefinitionCollection + } +} +export { workspaces } + +// @public +function writeWorkspace(workspace: WorkspaceDefinition, host: WorkspaceHost, path?: string, format?: WorkspaceFormat): Promise; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/core/node/index.api.md b/goldens/public-api/angular_devkit/core/node/index.api.md new file mode 100644 index 000000000000..cb18462521ca --- /dev/null +++ b/goldens/public-api/angular_devkit/core/node/index.api.md @@ -0,0 +1,77 @@ +## API Report File for "@angular-devkit/core_node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Observable } from 'rxjs'; +import { Operator } from 'rxjs'; +import { PartialObserver } from 'rxjs'; +import { Stats as Stats_2 } from 'node:fs'; +import { Subject } from 'rxjs'; +import { Subscription } from 'rxjs'; + +// @public +export function createConsoleLogger(verbose?: boolean, stdout?: ProcessOutput, stderr?: ProcessOutput, colors?: Partial string>>): logging.Logger; + +// @public +export class NodeJsAsyncHost implements virtualFs.Host { + // (undocumented) + get capabilities(): virtualFs.HostCapabilities; + // (undocumented) + delete(path: Path): Observable; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + rename(from: Path, to: Path): Observable; + // (undocumented) + stat(path: Path): Observable>; + // (undocumented) + watch(path: Path, _options?: virtualFs.HostWatchOptions): Observable | null; + // (undocumented) + write(path: Path, content: virtualFs.FileBuffer): Observable; +} + +// @public +export class NodeJsSyncHost implements virtualFs.Host { + // (undocumented) + get capabilities(): virtualFs.HostCapabilities; + // (undocumented) + delete(path: Path): Observable; + // (undocumented) + exists(path: Path): Observable; + // (undocumented) + isDirectory(path: Path): Observable; + // (undocumented) + isFile(path: Path): Observable; + // (undocumented) + list(path: Path): Observable; + // (undocumented) + read(path: Path): Observable; + // (undocumented) + rename(from: Path, to: Path): Observable; + // (undocumented) + stat(path: Path): Observable>; + // (undocumented) + watch(path: Path, _options?: virtualFs.HostWatchOptions): Observable | null; + // (undocumented) + write(path: Path, content: virtualFs.FileBuffer): Observable; +} + +// @public (undocumented) +export interface ProcessOutput { + // (undocumented) + write(buffer: string | Buffer): boolean; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/core/node/testing/index.api.md b/goldens/public-api/angular_devkit/core/node/testing/index.api.md new file mode 100644 index 000000000000..116019d266a1 --- /dev/null +++ b/goldens/public-api/angular_devkit/core/node/testing/index.api.md @@ -0,0 +1,27 @@ +## API Report File for "@angular-devkit/core_node_testing" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import * as fs from 'node:fs'; +import { Observable } from 'rxjs'; + +// @public +export class TempScopedNodeJsSyncHost extends virtualFs.ScopedHost { + constructor(); + // (undocumented) + get files(): Path[]; + // (undocumented) + get root(): Path; + // (undocumented) + protected _root: Path; + // (undocumented) + get sync(): virtualFs.SyncDelegateHost; + // (undocumented) + protected _sync?: virtualFs.SyncDelegateHost; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/schematics/index.api.md b/goldens/public-api/angular_devkit/schematics/index.api.md new file mode 100644 index 000000000000..505bd2c39920 --- /dev/null +++ b/goldens/public-api/angular_devkit/schematics/index.api.md @@ -0,0 +1,1057 @@ +## API Report File for "@angular-devkit/schematics" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BaseException } from '@angular-devkit/core'; +import { JsonValue } from '@angular-devkit/core'; +import { logging } from '@angular-devkit/core'; +import { Observable } from 'rxjs'; +import { Path } from '@angular-devkit/core'; +import { PathFragment } from '@angular-devkit/core'; +import { schema } from '@angular-devkit/core'; +import { strings } from '@angular-devkit/core'; +import { Subject } from 'rxjs'; +import { Url } from 'node:url'; +import { virtualFs } from '@angular-devkit/core'; + +// @public (undocumented) +export type Action = CreateFileAction | OverwriteFileAction | RenameFileAction | DeleteFileAction; + +// @public (undocumented) +export interface ActionBase { + // (undocumented) + readonly id: number; + // (undocumented) + readonly parent: number; + // (undocumented) + readonly path: Path; +} + +// @public (undocumented) +export class ActionList implements Iterable { + // (undocumented) + [Symbol.iterator](): IterableIterator; + // (undocumented) + protected _action(action: Partial): void; + // (undocumented) + create(path: Path, content: Buffer): void; + // (undocumented) + delete(path: Path): void; + // (undocumented) + find(predicate: (value: Action) => boolean): Action | null; + // (undocumented) + forEach(fn: (value: Action, index: number, array: Action[]) => void, thisArg?: {}): void; + // (undocumented) + get(i: number): Action; + // (undocumented) + has(action: Action): boolean; + // (undocumented) + get length(): number; + // (undocumented) + optimize(): void; + // (undocumented) + overwrite(path: Path, content: Buffer): void; + // (undocumented) + push(action: Action): void; + // (undocumented) + rename(path: Path, to: Path): void; +} + +// @public +export function apply(source: Source, rules: Rule[]): Source; + +// @public (undocumented) +export function applyContentTemplate(options: T): FileOperator; + +// @public (undocumented) +export function applyPathTemplate(data: T, options?: PathTemplateOptions): FileOperator; + +// @public (undocumented) +export function applyTemplates(options: T): Rule; + +// @public (undocumented) +export function applyToSubtree(path: string, rules: Rule[]): Rule; + +// @public (undocumented) +export function asSource(rule: Rule): Source; + +// @public (undocumented) +export type AsyncFileOperator = (tree: FileEntry) => Observable; + +// @public +abstract class BaseWorkflow implements Workflow { + constructor(options: BaseWorkflowOptions); + // (undocumented) + get context(): Readonly; + // (undocumented) + protected _context: WorkflowExecutionContext[]; + // (undocumented) + protected _createSinks(): Sink[]; + // (undocumented) + protected _dryRun: boolean; + // (undocumented) + get engine(): Engine<{}, {}>; + // (undocumented) + protected _engine: Engine<{}, {}>; + // (undocumented) + get engineHost(): EngineHost<{}, {}>; + // (undocumented) + protected _engineHost: EngineHost<{}, {}>; + // (undocumented) + execute(options: Partial & RequiredWorkflowExecutionContext): Observable; + // (undocumented) + protected _force: boolean; + // (undocumented) + protected _host: virtualFs.Host; + // (undocumented) + get lifeCycle(): Observable; + // (undocumented) + protected _lifeCycle: Subject; + // (undocumented) + get registry(): schema.SchemaRegistry; + // (undocumented) + protected _registry: schema.CoreSchemaRegistry; + // (undocumented) + get reporter(): Observable; + // (undocumented) + protected _reporter: Subject; +} + +// @public (undocumented) +interface BaseWorkflowOptions { + // (undocumented) + dryRun?: boolean; + // (undocumented) + engineHost: EngineHost<{}, {}>; + // (undocumented) + force?: boolean; + // (undocumented) + host: virtualFs.Host; + // (undocumented) + registry?: schema.CoreSchemaRegistry; +} + +// @public (undocumented) +export function branchAndMerge(rule: Rule, strategy?: MergeStrategy): Rule; + +// @public (undocumented) +export function callRule(rule: Rule, input: Tree_2 | Observable, context: SchematicContext): Observable; + +// @public (undocumented) +export function callSource(source: Source, context: SchematicContext): Observable; + +// @public +export function chain(rules: Iterable | AsyncIterable): Rule; + +// @public (undocumented) +export class CircularCollectionException extends BaseException { + constructor(name: string); +} + +// @public +export interface Collection { + // (undocumented) + readonly baseDescriptions?: Array>; + // (undocumented) + createSchematic(name: string, allowPrivate?: boolean): Schematic; + // (undocumented) + readonly description: CollectionDescription; + // (undocumented) + listSchematicNames(includeHidden?: boolean): string[]; +} + +// @public +export type CollectionDescription = CollectionMetadataT & { + readonly name: string; + readonly extends?: string[]; +}; + +// @public (undocumented) +export class CollectionImpl implements Collection { + constructor(_description: CollectionDescription, _engine: SchematicEngine, baseDescriptions?: CollectionDescription[] | undefined); + // (undocumented) + readonly baseDescriptions?: CollectionDescription[] | undefined; + // (undocumented) + createSchematic(name: string, allowPrivate?: boolean): Schematic; + // (undocumented) + get description(): CollectionDescription; + // (undocumented) + listSchematicNames(includeHidden?: boolean): string[]; + // (undocumented) + get name(): string; +} + +// @public (undocumented) +export function composeFileOperators(operators: FileOperator[]): FileOperator; + +// @public (undocumented) +export class ContentHasMutatedException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export function contentTemplate(options: T): Rule; + +// @public (undocumented) +export interface CreateFileAction extends ActionBase { + // (undocumented) + readonly content: Buffer; + // (undocumented) + readonly kind: 'c'; +} + +// @public (undocumented) +export class DelegateTree implements Tree_2 { + // (undocumented) + [TreeSymbol]: () => this; + constructor(_other: Tree_2); + // (undocumented) + get actions(): Action[]; + // (undocumented) + apply(action: Action, strategy?: MergeStrategy): void; + // (undocumented) + beginUpdate(path: string): UpdateRecorder; + // (undocumented) + branch(): Tree_2; + // (undocumented) + commitUpdate(record: UpdateRecorder): void; + // (undocumented) + create(path: string, content: Buffer | string): void; + // (undocumented) + delete(path: string): void; + // (undocumented) + exists(path: string): boolean; + // (undocumented) + get(path: string): FileEntry | null; + // (undocumented) + getDir(path: string): DirEntry; + // (undocumented) + merge(other: Tree_2, strategy?: MergeStrategy): void; + // (undocumented) + protected _other: Tree_2; + // (undocumented) + overwrite(path: string, content: Buffer | string): void; + // (undocumented) + read(path: string): Buffer | null; + // (undocumented) + readJson(path: string): JsonValue; + // (undocumented) + readText(path: string): string; + // (undocumented) + rename(from: string, to: string): void; + // (undocumented) + get root(): DirEntry; + // (undocumented) + visit(visitor: FileVisitor): void; +} + +// @public (undocumented) +export interface DeleteFileAction extends ActionBase { + // (undocumented) + readonly kind: 'd'; +} + +// @public (undocumented) +export interface DirEntry { + // (undocumented) + dir(name: PathFragment): DirEntry; + // (undocumented) + file(name: PathFragment): FileEntry | null; + // (undocumented) + readonly parent: DirEntry | null; + // (undocumented) + readonly path: Path; + // (undocumented) + readonly subdirs: PathFragment[]; + // (undocumented) + readonly subfiles: PathFragment[]; + // (undocumented) + visit(visitor: FileVisitor): void; +} + +// @public (undocumented) +export interface DryRunCreateEvent { + // (undocumented) + content: ArrayBufferLike; + // (undocumented) + kind: 'create'; + // (undocumented) + path: string; +} + +// @public (undocumented) +export interface DryRunDeleteEvent { + // (undocumented) + kind: 'delete'; + // (undocumented) + path: string; +} + +// @public (undocumented) +export interface DryRunErrorEvent { + // (undocumented) + description: 'alreadyExist' | 'doesNotExist'; + // (undocumented) + kind: 'error'; + // (undocumented) + path: string; +} + +// @public (undocumented) +export type DryRunEvent = DryRunErrorEvent | DryRunDeleteEvent | DryRunCreateEvent | DryRunUpdateEvent | DryRunRenameEvent; + +// @public (undocumented) +export interface DryRunRenameEvent { + // (undocumented) + kind: 'rename'; + // (undocumented) + path: string; + // (undocumented) + to: string; +} + +// @public (undocumented) +export class DryRunSink extends HostSink { + constructor(host: virtualFs.Host, force?: boolean); + // (undocumented) + _done(): Observable; + // (undocumented) + protected _fileAlreadyExistException(path: string): void; + // (undocumented) + protected _fileAlreadyExistExceptionSet: Set; + // (undocumented) + protected _fileDoesNotExistException(path: string): void; + // (undocumented) + protected _fileDoesNotExistExceptionSet: Set; + // (undocumented) + readonly reporter: Observable; + // (undocumented) + protected _subject: Subject; +} + +// @public (undocumented) +export interface DryRunUpdateEvent { + // (undocumented) + content: ArrayBufferLike; + // (undocumented) + kind: 'update'; + // (undocumented) + path: string; +} + +// @public +export function empty(): Source; + +// @public (undocumented) +export class EmptyTree extends HostTree { + constructor(); +} + +// @public +export interface Engine { + // (undocumented) + createCollection(name: string, requester?: Collection): Collection; + // (undocumented) + createContext(schematic: Schematic, parent?: Partial>, executionOptions?: Partial): TypedSchematicContext; + // (undocumented) + createSchematic(name: string, collection: Collection): Schematic; + // (undocumented) + createSourceFromUrl(url: Url, context: TypedSchematicContext): Source; + // (undocumented) + readonly defaultMergeStrategy: MergeStrategy; + // (undocumented) + executePostTasks(): Observable; + // (undocumented) + transformOptions(schematic: Schematic, options: OptionT, context?: TypedSchematicContext): Observable; + // (undocumented) + readonly workflow: Workflow | null; +} + +// @public +export interface EngineHost { + // (undocumented) + createCollectionDescription(name: string, requester?: CollectionDescription): CollectionDescription; + // (undocumented) + createSchematicDescription(name: string, collection: CollectionDescription): SchematicDescription | null; + // (undocumented) + createSourceFromUrl(url: Url, context: TypedSchematicContext): Source | null; + // (undocumented) + createTaskExecutor(name: string): Observable; + // (undocumented) + readonly defaultMergeStrategy?: MergeStrategy; + // (undocumented) + getSchematicRuleFactory(schematic: SchematicDescription, collection: CollectionDescription): RuleFactory; + // (undocumented) + hasTaskExecutor(name: string): boolean; + // (undocumented) + listSchematicNames(collection: CollectionDescription, includeHidden?: boolean): string[]; + // (undocumented) + transformContext(context: TypedSchematicContext): TypedSchematicContext | void; + // (undocumented) + transformOptions(schematic: SchematicDescription, options: OptionT, context?: TypedSchematicContext): Observable; +} + +// @public (undocumented) +export interface ExecutionOptions { + // (undocumented) + interactive: boolean; + // (undocumented) + scope: string; +} + +// @public +export function externalSchematic(collectionName: string, schematicName: string, options: OptionT, executionOptions?: Partial): Rule; + +// @public (undocumented) +export class FileAlreadyExistException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export class FileDoesNotExistException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export interface FileEntry { + // (undocumented) + readonly content: Buffer; + // (undocumented) + readonly path: Path; +} + +// @public +export type FileOperator = (entry: FileEntry) => FileEntry | null; + +// @public (undocumented) +export interface FilePredicate { + // (undocumented) + (path: Path, entry?: Readonly | null): T; +} + +// @public (undocumented) +export type FileVisitor = FilePredicate; + +// @public (undocumented) +export const FileVisitorCancelToken: symbol; + +// @public (undocumented) +export function filter(predicate: FilePredicate): Rule; + +// @public (undocumented) +export class FilterHostTree extends HostTree { + constructor(tree: HostTree, filter?: FilePredicate); +} + +// @public (undocumented) +export function forEach(operator: FileOperator): Rule; + +declare namespace formats { + export { + htmlSelectorFormat, + pathFormat, + standardFormats + } +} +export { formats } + +// @public (undocumented) +export class HostCreateTree extends HostTree { + constructor(host: virtualFs.ReadonlyHost); +} + +// @public (undocumented) +export class HostDirEntry implements DirEntry { + constructor(parent: DirEntry | null, path: Path, _host: virtualFs.SyncDelegateHost, _tree: Tree_2); + // (undocumented) + dir(name: PathFragment): DirEntry; + // (undocumented) + file(name: PathFragment): FileEntry | null; + // (undocumented) + protected _host: virtualFs.SyncDelegateHost; + // (undocumented) + readonly parent: DirEntry | null; + // (undocumented) + readonly path: Path; + // (undocumented) + get subdirs(): PathFragment[]; + // (undocumented) + get subfiles(): PathFragment[]; + // (undocumented) + protected _tree: Tree_2; + // (undocumented) + visit(visitor: FileVisitor): void; +} + +// @public (undocumented) +export class HostSink extends SimpleSinkBase { + constructor(_host: virtualFs.Host, _force?: boolean); + // (undocumented) + protected _createFile(path: Path, content: Buffer): Observable; + // (undocumented) + protected _deleteFile(path: Path): Observable; + // (undocumented) + _done(): Observable; + // (undocumented) + protected _filesToCreate: Map; + // (undocumented) + protected _filesToDelete: Set; + // (undocumented) + protected _filesToRename: Set<[Path, Path]>; + // (undocumented) + protected _filesToUpdate: Map; + // (undocumented) + protected _force: boolean; + // (undocumented) + protected _host: virtualFs.Host; + // (undocumented) + protected _overwriteFile(path: Path, content: Buffer): Observable; + // (undocumented) + protected _renameFile(from: Path, to: Path): Observable; + // (undocumented) + protected _validateCreateAction(action: CreateFileAction): Observable; + // (undocumented) + protected _validateFileExists(p: Path): Observable; +} + +// @public (undocumented) +export class HostTree implements Tree_2 { + // (undocumented) + [TreeSymbol]: () => this; + constructor(_backend?: virtualFs.ReadonlyHost<{}>); + // (undocumented) + get actions(): Action[]; + // (undocumented) + apply(action: Action, strategy?: MergeStrategy): void; + // (undocumented) + protected _backend: virtualFs.ReadonlyHost<{}>; + // (undocumented) + beginUpdate(path: string): UpdateRecorder; + // (undocumented) + branch(): Tree_2; + // (undocumented) + commitUpdate(record: UpdateRecorder): void; + // (undocumented) + create(path: string, content: Buffer | string): void; + // (undocumented) + delete(path: string): void; + // (undocumented) + exists(path: string): boolean; + // (undocumented) + get(path: string): FileEntry | null; + // (undocumented) + getDir(path: string): DirEntry; + // (undocumented) + static isHostTree(tree: Tree_2): tree is HostTree; + // (undocumented) + merge(other: Tree_2, strategy?: MergeStrategy): void; + // (undocumented) + protected _normalizePath(path: string): Path; + // (undocumented) + overwrite(path: string, content: Buffer | string): void; + // (undocumented) + read(path: string): Buffer | null; + // (undocumented) + readJson(path: string): JsonValue; + // (undocumented) + readText(path: string): string; + // (undocumented) + rename(from: string, to: string): void; + // (undocumented) + get root(): DirEntry; + // (undocumented) + visit(visitor: FileVisitor): void; + // (undocumented) + protected _willCreate(path: Path): boolean; + // (undocumented) + protected _willDelete(path: Path): boolean; + // (undocumented) + protected _willOverwrite(path: Path): boolean; + // (undocumented) + protected _willRename(path: Path): boolean; +} + +// @public (undocumented) +const htmlSelectorFormat: schema.SchemaFormat; + +// @public (undocumented) +export class InvalidPipeException extends BaseException { + constructor(name: string); +} + +// @public +export class InvalidRuleResultException extends BaseException { + constructor(value?: {}); +} + +// @public (undocumented) +export class InvalidSchematicsNameException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class InvalidSourceResultException extends BaseException { + constructor(value?: {}); +} + +// @public (undocumented) +export class InvalidUpdateRecordException extends BaseException { + constructor(); +} + +// @public (undocumented) +export function isContentAction(action: Action): action is CreateFileAction | OverwriteFileAction; + +// @public (undocumented) +interface LifeCycleEvent { + // (undocumented) + kind: 'start' | 'end' | 'workflow-start' | 'workflow-end' | 'post-tasks-start' | 'post-tasks-end'; +} + +// @public (undocumented) +export class MergeConflictException extends BaseException { + constructor(path: string); +} + +// @public (undocumented) +export enum MergeStrategy { + // (undocumented) + AllowCreationConflict = 4, + // (undocumented) + AllowDeleteConflict = 8, + // (undocumented) + AllowOverwriteConflict = 2, + // (undocumented) + ContentOnly = 2, + // (undocumented) + Default = 0, + // (undocumented) + Error = 1, + // (undocumented) + Overwrite = 14 +} + +// @public +export function mergeWith(source: Source, strategy?: MergeStrategy): Rule; + +// @public (undocumented) +export function move(from: string, to: string): Rule; + +// @public (undocumented) +export function move(to: string): Rule; + +// @public (undocumented) +export function noop(): Rule; + +// @public (undocumented) +export class OptionIsNotDefinedException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export interface OverwriteFileAction extends ActionBase { + // (undocumented) + readonly content: Buffer; + // (undocumented) + readonly kind: 'o'; +} + +// @public (undocumented) +export function partitionApplyMerge(predicate: FilePredicate, ruleYes: Rule, ruleNo?: Rule): Rule; + +// @public (undocumented) +const pathFormat: schema.SchemaFormat; + +// @public (undocumented) +export function pathTemplate(options: T): Rule; + +// @public (undocumented) +export type PathTemplateData = { + [key: string]: PathTemplateValue | PathTemplateData | PathTemplatePipeFunction; +}; + +// @public (undocumented) +export interface PathTemplateOptions { + // (undocumented) + interpolationEnd: string; + // (undocumented) + interpolationStart: string; + // (undocumented) + pipeSeparator?: string; +} + +// @public (undocumented) +export type PathTemplatePipeFunction = (x: string) => PathTemplateValue; + +// @public (undocumented) +export type PathTemplateValue = boolean | string | number | undefined; + +// @public (undocumented) +export class PrivateSchematicException extends BaseException { + constructor(name: string, collection: CollectionDescription<{}>); +} + +// @public (undocumented) +export interface RandomOptions { + // (undocumented) + multi?: boolean | number; + // (undocumented) + multiFiles?: boolean | number; + // (undocumented) + root?: string; +} + +// @public (undocumented) +export interface RenameFileAction extends ActionBase { + // (undocumented) + readonly kind: 'r'; + // (undocumented) + readonly to: Path; +} + +// @public +export function renameTemplateFiles(): Rule; + +// @public (undocumented) +interface RequiredWorkflowExecutionContext { + // (undocumented) + collection: string; + // (undocumented) + options: object; + // (undocumented) + schematic: string; +} + +// @public (undocumented) +export type Rule = (tree: Tree_2, context: SchematicContext) => Tree_2 | Observable | Rule | Promise | void; + +// @public +export type RuleFactory = (options: T) => Rule; + +// @public +export interface Schematic { + // (undocumented) + call(options: OptionT, host: Observable, parentContext?: Partial>, executionOptions?: Partial): Observable; + // (undocumented) + readonly collection: Collection; + // (undocumented) + readonly description: SchematicDescription; +} + +// @public +export function schematic(schematicName: string, options: OptionT, executionOptions?: Partial): Rule; + +// @public +export type SchematicContext = TypedSchematicContext<{}, {}>; + +// @public +export type SchematicDescription = SchematicMetadataT & { + readonly collection: CollectionDescription; + readonly name: string; + readonly private?: boolean; + readonly hidden?: boolean; +}; + +// @public (undocumented) +export class SchematicEngine implements Engine { + constructor(_host: EngineHost, _workflow?: Workflow | undefined); + // (undocumented) + createCollection(name: string, requester?: Collection): Collection; + // (undocumented) + createContext(schematic: Schematic, parent?: Partial>, executionOptions?: Partial): TypedSchematicContext; + // (undocumented) + createSchematic(name: string, collection: Collection, allowPrivate?: boolean): Schematic; + // (undocumented) + createSourceFromUrl(url: Url, context: TypedSchematicContext): Source; + // (undocumented) + get defaultMergeStrategy(): MergeStrategy; + // (undocumented) + executePostTasks(): Observable; + // (undocumented) + listSchematicNames(collection: Collection, includeHidden?: boolean): string[]; + // (undocumented) + transformOptions(schematic: Schematic, options: OptionT, context?: TypedSchematicContext): Observable; + // (undocumented) + get workflow(): Workflow | null; + // (undocumented) + protected _workflow?: Workflow | undefined; +} + +// @public (undocumented) +export class SchematicEngineConflictingException extends BaseException { + constructor(); +} + +// @public (undocumented) +export class SchematicImpl implements Schematic { + constructor(_description: SchematicDescription, _factory: RuleFactory<{}>, _collection: Collection, _engine: Engine); + // (undocumented) + call(options: OptionT, host: Observable, parentContext?: Partial>, executionOptions?: Partial): Observable; + // (undocumented) + get collection(): Collection; + // (undocumented) + get description(): SchematicDescription; +} + +// @public (undocumented) +export class SchematicsException extends BaseException { +} + +// @public (undocumented) +export abstract class SimpleSinkBase implements Sink { + // (undocumented) + commit(tree: Tree_2): Observable; + // (undocumented) + commitSingleAction(action: Action): Observable; + // (undocumented) + protected abstract _createFile(path: string, content: Buffer): Observable; + // (undocumented) + protected abstract _deleteFile(path: string): Observable; + // (undocumented) + protected abstract _done(): Observable; + // (undocumented) + protected _fileAlreadyExistException(path: string): void; + // (undocumented) + protected _fileDoesNotExistException(path: string): void; + // (undocumented) + protected abstract _overwriteFile(path: string, content: Buffer): Observable; + // (undocumented) + postCommit: () => void | Observable; + // (undocumented) + postCommitAction: (action: Action) => void | Observable; + // (undocumented) + preCommit: () => void | Observable; + // (undocumented) + preCommitAction: (action: Action) => void | Action | PromiseLike | Observable; + // (undocumented) + protected abstract _renameFile(path: string, to: string): Observable; + // (undocumented) + protected _validateCreateAction(action: CreateFileAction): Observable; + // (undocumented) + protected _validateDeleteAction(action: DeleteFileAction): Observable; + // (undocumented) + protected abstract _validateFileExists(p: string): Observable; + // (undocumented) + protected _validateOverwriteAction(action: OverwriteFileAction): Observable; + // (undocumented) + protected _validateRenameAction(action: RenameFileAction): Observable; + // (undocumented) + validateSingleAction(action: Action): Observable; +} + +// @public (undocumented) +export interface Sink { + // (undocumented) + commit(tree: Tree_2): Observable; +} + +// @public +export type Source = (context: SchematicContext) => Tree_2 | Observable; + +// @public +export function source(tree: Tree_2): Source; + +// @public (undocumented) +const standardFormats: schema.SchemaFormat[]; + +export { strings } + +// @public (undocumented) +export interface TaskConfiguration { + // (undocumented) + dependencies?: Array; + // (undocumented) + name: string; + // (undocumented) + options?: T; +} + +// @public (undocumented) +export interface TaskConfigurationGenerator { + // (undocumented) + toConfiguration(): TaskConfiguration; +} + +// @public (undocumented) +export type TaskExecutor = (options: T | undefined, context: SchematicContext) => Promise | Observable; + +// @public (undocumented) +export interface TaskExecutorFactory { + // (undocumented) + create(options?: T): Promise | Observable; + // (undocumented) + readonly name: string; +} + +// @public (undocumented) +export interface TaskId { + // (undocumented) + readonly id: number; +} + +// @public (undocumented) +export interface TaskInfo { + // (undocumented) + readonly configuration: TaskConfiguration; + // (undocumented) + readonly context: SchematicContext; + // (undocumented) + readonly id: number; + // (undocumented) + readonly priority: number; +} + +// @public (undocumented) +export class TaskScheduler { + constructor(_context: SchematicContext); + // (undocumented) + finalize(): ReadonlyArray; + // (undocumented) + schedule(taskConfiguration: TaskConfiguration): TaskId; +} + +// @public (undocumented) +export function template(options: T): Rule; + +// @public (undocumented) +export const TEMPLATE_FILENAME_RE: RegExp; + +// @public (undocumented) +export type Tree = Tree_2; + +// @public (undocumented) +export const Tree: TreeConstructor; + +// @public (undocumented) +export interface TreeConstructor { + // (undocumented) + branch(tree: Tree_2): Tree_2; + // (undocumented) + empty(): Tree_2; + // (undocumented) + merge(tree: Tree_2, other: Tree_2, strategy?: MergeStrategy): Tree_2; + // (undocumented) + optimize(tree: Tree_2): Tree_2; + // (undocumented) + partition(tree: Tree_2, predicate: FilePredicate): [Tree_2, Tree_2]; +} + +// @public (undocumented) +export const TreeSymbol: symbol; + +// @public +export interface TypedSchematicContext { + // (undocumented) + addTask(task: TaskConfigurationGenerator, dependencies?: Array): TaskId; + // (undocumented) + readonly debug: boolean; + // (undocumented) + readonly engine: Engine; + // (undocumented) + readonly interactive: boolean; + // (undocumented) + readonly logger: logging.LoggerApi; + // (undocumented) + readonly schematic: Schematic; + // (undocumented) + readonly strategy: MergeStrategy; +} + +// @public (undocumented) +export class UnimplementedException extends BaseException { + constructor(); +} + +// @public (undocumented) +export class UnknownActionException extends BaseException { + constructor(action: Action); +} + +// @public (undocumented) +export class UnknownCollectionException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class UnknownPipeException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class UnknownSchematicException extends BaseException { + constructor(name: string, collection: CollectionDescription<{}>); +} + +// @public (undocumented) +export class UnknownTaskDependencyException extends BaseException { + constructor(id: TaskId); +} + +// @public (undocumented) +export class UnknownUrlSourceProtocol extends BaseException { + constructor(url: string); +} + +// @public (undocumented) +export class UnregisteredTaskException extends BaseException { + constructor(name: string, schematic?: SchematicDescription<{}, {}>); +} + +// @public (undocumented) +export class UnsuccessfulWorkflowExecution extends BaseException { + constructor(); +} + +// @public (undocumented) +export interface UpdateRecorder { + // (undocumented) + insertLeft(index: number, content: Buffer | string): UpdateRecorder; + // (undocumented) + insertRight(index: number, content: Buffer | string): UpdateRecorder; + // (undocumented) + remove(index: number, length: number): UpdateRecorder; +} + +// @public (undocumented) +export function url(urlString: string): Source; + +// @public (undocumented) +export function when(predicate: FilePredicate, operator: FileOperator): FileOperator; + +// @public (undocumented) +interface Workflow { + // (undocumented) + readonly context: Readonly; + // (undocumented) + execute(options: Partial & RequiredWorkflowExecutionContext): Observable; +} + +declare namespace workflow { + export { + BaseWorkflowOptions, + BaseWorkflow, + RequiredWorkflowExecutionContext, + WorkflowExecutionContext, + LifeCycleEvent, + Workflow + } +} +export { workflow } + +// @public (undocumented) +interface WorkflowExecutionContext extends RequiredWorkflowExecutionContext { + // (undocumented) + allowPrivate?: boolean; + // (undocumented) + debug: boolean; + // (undocumented) + logger: logging.Logger; + // (undocumented) + parentContext?: Readonly; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/schematics/tasks/index.api.md b/goldens/public-api/angular_devkit/schematics/tasks/index.api.md new file mode 100644 index 000000000000..ad9b7d8d7f0b --- /dev/null +++ b/goldens/public-api/angular_devkit/schematics/tasks/index.api.md @@ -0,0 +1,54 @@ +## API Report File for "@angular-devkit/schematics_tasks" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// @public (undocumented) +export class NodePackageInstallTask implements TaskConfigurationGenerator { + constructor(workingDirectory?: string); + constructor(options: NodePackageInstallTaskOptions); + // (undocumented) + allowScripts: boolean; + // (undocumented) + hideOutput: boolean; + // (undocumented) + packageManager?: string; + // (undocumented) + packageName?: string; + // (undocumented) + quiet: boolean; + // (undocumented) + toConfiguration(): TaskConfiguration; + // (undocumented) + workingDirectory?: string; +} + +// @public (undocumented) +export class RepositoryInitializerTask implements TaskConfigurationGenerator { + constructor(workingDirectory?: string | undefined, commitOptions?: CommitOptions | undefined); + // (undocumented) + commitOptions?: CommitOptions | undefined; + // (undocumented) + toConfiguration(): TaskConfiguration; + // (undocumented) + workingDirectory?: string | undefined; +} + +// @public (undocumented) +export class RunSchematicTask implements TaskConfigurationGenerator> { + constructor(s: string, o: T); + constructor(c: string, s: string, o: T); + // (undocumented) + protected _collection: string | null; + // (undocumented) + protected _options: T; + // (undocumented) + protected _schematic: string; + // (undocumented) + toConfiguration(): TaskConfiguration>; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/schematics/testing/index.api.md b/goldens/public-api/angular_devkit/schematics/testing/index.api.md new file mode 100644 index 000000000000..abf89972c862 --- /dev/null +++ b/goldens/public-api/angular_devkit/schematics/testing/index.api.md @@ -0,0 +1,43 @@ +## API Report File for "@angular-devkit/schematics_testing" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { JsonValue } from '@angular-devkit/core'; +import { logging } from '@angular-devkit/core'; +import { Observable } from 'rxjs'; +import { Path } from '@angular-devkit/core'; +import { PathFragment } from '@angular-devkit/core'; +import { Url } from 'node:url'; + +// @public (undocumented) +export class SchematicTestRunner { + constructor(_collectionName: string, collectionPath: string); + // (undocumented) + callRule(rule: Rule, tree: Tree_2, parentContext?: Partial): Observable; + // (undocumented) + get engine(): SchematicEngine<{}, {}>; + // (undocumented) + get logger(): logging.Logger; + // (undocumented) + registerCollection(collectionName: string, collectionPath: string): void; + // (undocumented) + runExternalSchematic(collectionName: string, schematicName: string, opts?: SchematicSchemaT, tree?: Tree_2): Promise; + // (undocumented) + runSchematic(schematicName: string, opts?: SchematicSchemaT, tree?: Tree_2): Promise; + // (undocumented) + get tasks(): TaskConfiguration[]; +} + +// @public (undocumented) +export class UnitTestTree extends DelegateTree { + // (undocumented) + get files(): string[]; + // (undocumented) + readContent(path: string): string; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/angular_devkit/schematics/tools/index.api.md b/goldens/public-api/angular_devkit/schematics/tools/index.api.md new file mode 100644 index 000000000000..c93e3ec27762 --- /dev/null +++ b/goldens/public-api/angular_devkit/schematics/tools/index.api.md @@ -0,0 +1,280 @@ +## API Report File for "@angular-devkit/schematics_tools" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BaseException } from '@angular-devkit/core'; +import { JsonObject } from '@angular-devkit/core'; +import { JsonValue } from '@angular-devkit/core'; +import { logging } from '@angular-devkit/core'; +import { Observable } from 'rxjs'; +import { Path } from '@angular-devkit/core'; +import { PathFragment } from '@angular-devkit/core'; +import { schema } from '@angular-devkit/core'; +import { Subject } from 'rxjs'; +import { Url } from 'node:url'; +import { virtualFs } from '@angular-devkit/core'; + +// @public (undocumented) +export class CollectionCannotBeResolvedException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class CollectionMissingFieldsException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class CollectionMissingSchematicsMapException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export type ContextTransform = (context: FileSystemSchematicContext) => FileSystemSchematicContext; + +// @public +export class ExportStringRef { + constructor(ref: string, parentPath?: string, inner?: boolean); + // (undocumented) + get module(): string; + // (undocumented) + get path(): string; + // (undocumented) + get ref(): T | undefined; +} + +// @public (undocumented) +export class FactoryCannotBeResolvedException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export type FileSystemCollection = Collection; + +// @public (undocumented) +export type FileSystemCollectionDesc = CollectionDescription; + +// @public (undocumented) +export interface FileSystemCollectionDescription { + // (undocumented) + readonly encapsulation?: boolean; + // (undocumented) + readonly name: string; + // (undocumented) + readonly path: string; + // (undocumented) + readonly schematics: { + [name: string]: FileSystemSchematicDesc; + }; + // (undocumented) + readonly version?: string; +} + +// @public +export type FileSystemEngine = Engine; + +// @public +export class FileSystemEngineHost extends FileSystemEngineHostBase { + constructor(_root: string); + // (undocumented) + createTaskExecutor(name: string): Observable; + // (undocumented) + hasTaskExecutor(name: string): boolean; + // (undocumented) + protected _resolveCollectionPath(name: string): string; + // (undocumented) + protected _resolveReferenceString(refString: string, parentPath: string): { + ref: RuleFactory<{}>; + path: string; + } | null; + // (undocumented) + protected _root: string; + // (undocumented) + protected _transformCollectionDescription(name: string, desc: Partial): FileSystemCollectionDesc; + // (undocumented) + protected _transformSchematicDescription(name: string, _collection: FileSystemCollectionDesc, desc: Partial): FileSystemSchematicDesc; +} + +// @public +export abstract class FileSystemEngineHostBase implements FileSystemEngineHost_2 { + // (undocumented) + createCollectionDescription(name: string, requester?: FileSystemCollectionDesc): FileSystemCollectionDesc; + // (undocumented) + createSchematicDescription(name: string, collection: FileSystemCollectionDesc): FileSystemSchematicDesc | null; + // (undocumented) + createSourceFromUrl(url: Url): Source | null; + // (undocumented) + createTaskExecutor(name: string): Observable; + // (undocumented) + getSchematicRuleFactory(schematic: FileSystemSchematicDesc, _collection: FileSystemCollectionDesc): RuleFactory; + // (undocumented) + hasTaskExecutor(name: string): boolean; + // (undocumented) + listSchematicNames(collection: FileSystemCollectionDesc, includeHidden?: boolean): string[]; + // (undocumented) + registerContextTransform(t: ContextTransform): void; + // (undocumented) + registerOptionsTransform(t: OptionTransform): void; + // (undocumented) + registerTaskExecutor(factory: TaskExecutorFactory, options?: T): void; + // (undocumented) + protected abstract _resolveCollectionPath(name: string, requester?: string): string; + // (undocumented) + protected abstract _resolveReferenceString(name: string, parentPath: string, collectionDescription: FileSystemCollectionDesc): { + ref: RuleFactory<{}>; + path: string; + } | null; + // (undocumented) + protected abstract _transformCollectionDescription(name: string, desc: Partial): FileSystemCollectionDesc; + // (undocumented) + transformContext(context: FileSystemSchematicContext): FileSystemSchematicContext; + // (undocumented) + transformOptions(schematic: FileSystemSchematicDesc, options: OptionT, context?: FileSystemSchematicContext): Observable; + // (undocumented) + protected abstract _transformSchematicDescription(name: string, collection: FileSystemCollectionDesc, desc: Partial): FileSystemSchematicDesc; +} + +// @public (undocumented) +export type FileSystemSchematic = Schematic; + +// @public (undocumented) +export type FileSystemSchematicContext = TypedSchematicContext; + +// @public (undocumented) +export type FileSystemSchematicDesc = SchematicDescription; + +// @public (undocumented) +export interface FileSystemSchematicDescription extends FileSystemSchematicJsonDescription { + // (undocumented) + readonly factoryFn: RuleFactory<{}>; + // (undocumented) + readonly path: string; + // (undocumented) + readonly schemaJson?: JsonObject; +} + +// @public (undocumented) +export interface FileSystemSchematicJsonDescription { + // (undocumented) + readonly aliases?: string[]; + // (undocumented) + readonly collection: FileSystemCollectionDescription; + // (undocumented) + readonly description: string; + // (undocumented) + readonly extends?: string; + // (undocumented) + readonly factory: string; + // (undocumented) + readonly name: string; + // (undocumented) + readonly schema?: string; +} + +// @public (undocumented) +export class InvalidCollectionJsonException extends BaseException { + constructor(_name: string, path: string, jsonException?: Error); +} + +// @public +export class NodeModulesEngineHost extends FileSystemEngineHostBase { + constructor(paths?: string[] | undefined); + // (undocumented) + protected _resolveCollectionPath(name: string, requester?: string): string; + // (undocumented) + protected _resolveReferenceString(refString: string, parentPath: string, collectionDescription?: FileSystemCollectionDesc): { + ref: RuleFactory<{}>; + path: string; + } | null; + // (undocumented) + protected _transformCollectionDescription(name: string, desc: Partial): FileSystemCollectionDesc; + // (undocumented) + protected _transformSchematicDescription(name: string, _collection: FileSystemCollectionDesc, desc: Partial): FileSystemSchematicDesc; +} + +// @public +export class NodeModulesTestEngineHost extends NodeModulesEngineHost { + // (undocumented) + clearTasks(): void; + // (undocumented) + registerCollection(name: string, path: string): void; + // (undocumented) + protected _resolveCollectionPath(name: string, requester?: string): string; + // (undocumented) + get tasks(): TaskConfiguration[]; + // (undocumented) + transformContext(context: FileSystemSchematicContext): FileSystemSchematicContext; +} + +// @public (undocumented) +export class NodePackageDoesNotSupportSchematics extends BaseException { + constructor(name: string); +} + +// @public +export class NodeWorkflow extends workflow.BaseWorkflow { + constructor(root: string, options: NodeWorkflowOptions); + constructor(host: virtualFs.Host, options: NodeWorkflowOptions & { + root?: Path; + }); + // (undocumented) + get engine(): FileSystemEngine; + // (undocumented) + get engineHost(): NodeModulesEngineHost; +} + +// @public (undocumented) +export interface NodeWorkflowOptions { + // (undocumented) + dryRun?: boolean; + // (undocumented) + engineHostCreator?: (options: NodeWorkflowOptions) => NodeModulesEngineHost; + // (undocumented) + force?: boolean; + // (undocumented) + optionTransforms?: OptionTransform | null, object>[]; + // (undocumented) + packageManager?: string; + // (undocumented) + packageManagerForce?: boolean; + // (undocumented) + packageRegistry?: string; + // (undocumented) + registry?: schema.CoreSchemaRegistry; + // (undocumented) + resolvePaths?: string[]; + // (undocumented) + schemaValidation?: boolean; +} + +// @public (undocumented) +export type OptionTransform = (schematic: FileSystemSchematicDescription, options: T, context?: FileSystemSchematicContext) => Observable | PromiseLike | R; + +// @public (undocumented) +export class SchematicMissingDescriptionException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class SchematicMissingFactoryException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class SchematicMissingFieldsException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export class SchematicNameCollisionException extends BaseException { + constructor(name: string); +} + +// @public (undocumented) +export function validateOptionsWithSchema(registry: schema.SchemaRegistry): (schematic: FileSystemSchematicDescription, options: T, context?: FileSystemSchematicContext) => Observable; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/goldens/public-api/ngtools/webpack/index.api.md b/goldens/public-api/ngtools/webpack/index.api.md new file mode 100644 index 000000000000..13ddc85cabd8 --- /dev/null +++ b/goldens/public-api/ngtools/webpack/index.api.md @@ -0,0 +1,56 @@ +## API Report File for "@ngtools/webpack" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { Compiler } from 'webpack'; +import type { CompilerOptions } from '@angular/compiler-cli'; +import type { LoaderContext } from 'webpack'; + +// @public (undocumented) +function angularWebpackLoader(this: LoaderContext, content: string, map: string): void; +export default angularWebpackLoader; + +// @public (undocumented) +export const AngularWebpackLoaderPath: string; + +// @public (undocumented) +export class AngularWebpackPlugin { + constructor(options?: Partial); + // (undocumented) + apply(compiler: Compiler): void; + // (undocumented) + get options(): AngularWebpackPluginOptions; +} + +// @public (undocumented) +export interface AngularWebpackPluginOptions { + // (undocumented) + compilerOptions?: CompilerOptions; + // (undocumented) + directTemplateLoading: boolean; + // (undocumented) + emitClassMetadata: boolean; + // (undocumented) + emitNgModuleScope: boolean; + // (undocumented) + emitSetClassDebugInfo?: boolean; + // (undocumented) + fileReplacements: Record; + // (undocumented) + inlineStyleFileExtension?: string; + // (undocumented) + jitMode: boolean; + // (undocumented) + substitutions: Record; + // (undocumented) + tsconfig: string; +} + +// @public (undocumented) +export const imageDomains: Set; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/index.js b/index.js deleted file mode 100644 index caf1fbab6759..000000000000 --- a/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* jshint node: true */ -'use strict'; - -var path = require('path'); - -module.exports = { - name: 'ng' -}; \ No newline at end of file diff --git a/lib/broccoli/angular2-app.js b/lib/broccoli/angular2-app.js deleted file mode 100644 index d3eac5805d51..000000000000 --- a/lib/broccoli/angular2-app.js +++ /dev/null @@ -1,224 +0,0 @@ -var path = require('path'); -var Concat = require('broccoli-concat'); -var configReplace = require('./broccoli-config-replace'); -var compileWithTypescript = require('./broccoli-typescript').default; -var fs = require('fs'); -var Funnel = require('broccoli-funnel'); -var mergeTrees = require('broccoli-merge-trees'); -var Project = require('ember-cli/lib/models/project'); - -module.exports = Angular2App; - -function Angular2App(defaults, options, additionalPaths) { - this._initProject(); - this._notifyAddonIncluded(); - this.options = options; - this.additionalPaths = additionalPaths || []; -} - -Angular2App.prototype.toTree = function() { - var sourceTree = 'src'; - var vendorNpmFiles = [ - 'systemjs/dist/system-polyfills.js', - 'systemjs/dist/system.src.js', - 'es6-shim/es6-shim.js', - 'angular2/bundles/angular2-polyfills.js', - 'rxjs/bundles/Rx.js', - 'angular2/bundles/angular2.dev.js', - 'angular2/bundles/http.dev.js', - 'angular2/bundles/router.dev.js', - 'angular2/bundles/upgrade.dev.js' - ]; - - if (this.options && this.options.vendorNpmFiles) { - vendorNpmFiles = vendorNpmFiles.concat(this.options.vendorNpmFiles); - } - - var tsConfigCompilerOptions = JSON.parse(fs.readFileSync('src/tsconfig.json', 'utf-8')).compilerOptions; - tsConfigCompilerOptions.rootFilePaths = ['typings.d.ts'].concat(this.additionalPaths) - .map(function(name) { - return path.join(process.cwd(), sourceTree, name) - }); - - var tsTree = compileWithTypescript(sourceTree, tsConfigCompilerOptions); - var tsSrcTree = new Funnel(sourceTree, { - include: ['**/*.ts'], - allowEmpty: true - }); - - var jsTree = new Funnel(sourceTree, { - include: ['**/*.js'], - allowEmpty: true - }); - - var assetTree = new Funnel(sourceTree, { - include: ['**/*.*'], - exclude: ['**/*.ts', '**/*.js'], - allowEmpty: true - }); - - var vendorNpmTree = new Funnel('node_modules', { - include: vendorNpmFiles, - destDir: 'vendor' - }); - - var vendorNpmInputFiles = vendorNpmFiles.map(function(file) { - return '**/' + file.substr(file.lastIndexOf('/') + 1); - }); - - var vendorNpmJs = new Concat(vendorNpmTree, { - inputFiles: vendorNpmInputFiles, - outputFile: '/thirdparty/vendor.js' - }); - - var thirdPartyJsTree = new Funnel('node_modules', { - include: ['ng2*/bundles/*.js'], - exclude: ['ng2*/bundles/*.min.js', 'ng2*/bundles/*.standalone.js'], - }); - - var thirdPartyJs = new Concat(thirdPartyJsTree, { - inputFiles: ['**/*.js'], - outputFile: '/thirdparty/libs.js', - allowNone: true - }); - - return mergeTrees([ - assetTree, - tsSrcTree, - tsTree, - jsTree, - this.index(), - vendorNpmJs, - thirdPartyJs - ], { overwrite: true }); -}; - -/** - @private - @method _initProject - @param {Object} options - */ -Angular2App.prototype._initProject = function() { - this.project = Project.closestSync(process.cwd()); - - /*if (options.configPath) { - this.project.configPath = function() { return options.configPath; }; - }*/ -}; - -/** - @private - @method _notifyAddonIncluded - */ -Angular2App.prototype._notifyAddonIncluded = function() { - this.initializeAddons(); - this.project.addons = this.project.addons.filter(function(addon) { - addon.app = this; - - if (!addon.isEnabled || addon.isEnabled()) { - if (addon.included) { - addon.included(this); - } - - return addon; - } - }, this); -}; - -/** - Loads and initializes addons for this project. - Calls initializeAddons on the Project. - - @private - @method initializeAddons - */ -Angular2App.prototype.initializeAddons = function() { - this.project.initializeAddons(); -}; - -/** - Returns the content for a specific type (section) for index.html. - - Currently supported types: - - 'head' - //- 'config-module' - //- 'app' - //- 'head-footer' - //- 'test-header-footer' - //- 'body-footer' - //- 'test-body-footer' - - Addons can also implement this method and could also define additional - types (eg. 'some-addon-section'). - - @private - @method contentFor - @param {RegExP} match Regular expression to match against - @param {String} type Type of content - @return {String} The content. - */ -Angular2App.prototype.contentFor = function(match, type) { - var content = []; - - /*switch (type) { - case 'head': this._contentForHead(content, config); break; - case 'config-module': this._contentForConfigModule(content, config); break; - case 'app-boot': this._contentForAppBoot(content, config); break; - }*/ - - content = this.project.addons.reduce(function(content, addon) { - var addonContent = addon.contentFor ? addon.contentFor(type) : null; - if (addonContent) { - return content.concat(addonContent); - } - - return content; - }, content); - - - return content.join('\n'); -}; - -/** - @private - @method _configReplacePatterns - @return - */ -Angular2App.prototype._configReplacePatterns = function() { - return [/*{ - match: /\{\{EMBER_ENV\}\}/g, - replacement: calculateEmberENV - }, */{ - match: /\{\{content-for ['"](.+)["']\}\}/g, - replacement: this.contentFor.bind(this) - }/*, { - match: /\{\{MODULE_PREFIX\}\}/g, - replacement: calculateModulePrefix - }*/]; -}; - - -/** - Returns the tree for app/index.html - - @private - @method index - @return {Tree} Tree for app/index.html - */ -Angular2App.prototype.index = function() { - var htmlName = 'index.html'; - var files = [ - 'index.html' - ]; - - var index = new Funnel('src', { - files: files, - description: 'Funnel: index.html' - }); - - - return configReplace(index, { - files: [ htmlName ], - patterns: this._configReplacePatterns() - }); -}; diff --git a/lib/broccoli/broccoli-config-replace.js b/lib/broccoli/broccoli-config-replace.js deleted file mode 100644 index ae5450d209e7..000000000000 --- a/lib/broccoli/broccoli-config-replace.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -var fs = require('fs-extra'); -var path = require('path'); -var Writer = require('broccoli-writer'); - -function CustomReplace (inputTree, options) { - if (!(this instanceof CustomReplace)) { - return new CustomReplace(inputTree, options); - } - - Writer.call(this, inputTree, options); // this._super(); - - this.inputTree = inputTree; - this.options = options; -} -CustomReplace.prototype = Object.create(Writer.prototype); -CustomReplace.prototype.constructor = CustomReplace; - -CustomReplace.prototype.write = function (readTree, destDir) { - var inputDir; - - return readTree(this.inputTree) - .then(function(path) { - inputDir = path; - - return { - inputDir: inputDir, - destDir: destDir - }; - }) - .then(this.process.bind(this)); -}; - -CustomReplace.prototype.process = function(results) { - var files = this.options.files; - - for (var i = 0, l = files.length; i < l; i++) { - var file = files[i]; - var filePath = path.join(results.inputDir, file); - var destPath = path.join(results.destDir, file); - - this.processFile(filePath, destPath); - } -}; - -CustomReplace.prototype.processFile = function(filePath, destPath) { - var contents = fs.readFileSync(filePath, { encoding: 'utf8' }); - - for (var i = 0, l = this.options.patterns.length; i < l; i++) { - // jshint loopfunc:true - var pattern = this.options.patterns[i]; - var replacement = pattern.replacement; - - if (typeof pattern.replacement === 'function') { - replacement = function() { - var args = Array.prototype.slice.call(arguments); - return pattern.replacement.apply(null, args); - }; - } - - contents = contents.replace(pattern.match, replacement); - } - - if (!fs.existsSync(path.dirname(destPath))) { - fs.mkdirsSync(path.dirname(destPath)); - } - fs.writeFileSync(destPath, contents, { encoding: 'utf8' }); -}; - -module.exports = CustomReplace; diff --git a/lib/broccoli/broccoli-dest-copy.js b/lib/broccoli/broccoli-dest-copy.js deleted file mode 100644 index 686cd55010f9..000000000000 --- a/lib/broccoli/broccoli-dest-copy.js +++ /dev/null @@ -1,37 +0,0 @@ -/// -/// -"use strict"; -var fs = require('fs'); -var fse = require('fs-extra'); -var path = require('path'); -var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin'); -/** - * Intercepts each file as it is copied to the destination tempdir, - * and tees a copy to the given path outside the tmp dir. - */ -var DestCopy = (function () { - function DestCopy(inputPath, cachePath, outputRoot) { - this.inputPath = inputPath; - this.cachePath = cachePath; - this.outputRoot = outputRoot; - } - DestCopy.prototype.rebuild = function (treeDiff) { - var _this = this; - treeDiff.addedPaths.concat(treeDiff.changedPaths) - .forEach(function (changedFilePath) { - var destFilePath = path.join(_this.outputRoot, changedFilePath); - var destDirPath = path.dirname(destFilePath); - fse.mkdirsSync(destDirPath); - fse.copySync(path.join(_this.inputPath, changedFilePath), destFilePath); - }); - treeDiff.removedPaths.forEach(function (removedFilePath) { - var destFilePath = path.join(_this.outputRoot, removedFilePath); - // TODO: what about obsolete directories? we are not cleaning those up yet - fs.unlinkSync(destFilePath); - }); - }; - return DestCopy; -}()); -exports.__esModule = true; -exports["default"] = diffing_broccoli_plugin_1.wrapDiffingPlugin(DestCopy); -//# sourceMappingURL=broccoli-dest-copy.js.map \ No newline at end of file diff --git a/lib/broccoli/broccoli-dest-copy.js.map b/lib/broccoli/broccoli-dest-copy.js.map deleted file mode 100644 index dc9987c197ce..000000000000 --- a/lib/broccoli/broccoli-dest-copy.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"broccoli-dest-copy.js","sourceRoot":"","sources":["broccoli-dest-copy.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,0DAA0D;;AAE1D,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,wCAAmE,2BAA2B,CAAC,CAAA;AAE/F;;;GAGG;AACH;IACE,kBAAoB,SAAS,EAAU,SAAS,EAAU,UAAkB;QAAxD,cAAS,GAAT,SAAS,CAAA;QAAU,cAAS,GAAT,SAAS,CAAA;QAAU,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAGhF,0BAAO,GAAP,UAAQ,QAAoB;QAA5B,iBAgBC;QAfC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;aAC5C,OAAO,CAAC,UAAC,eAAe;YACvB,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAE/D,IAAI,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAC7C,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YAC5B,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,eAAe,CAAC,EAAE,YAAY,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEP,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,UAAC,eAAe;YAC5C,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAE/D,0EAA0E;YAC1E,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IACH,eAAC;AAAD,CAAC,AArBD,IAqBC;AAED;qBAAe,2CAAiB,CAAC,QAAQ,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/broccoli/broccoli-flatten.js b/lib/broccoli/broccoli-flatten.js deleted file mode 100644 index 9e5d34b71a22..000000000000 --- a/lib/broccoli/broccoli-flatten.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; -var fs = require('fs'); -var fse = require('fs-extra'); -var path = require('path'); -var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin'); -var symlinkOrCopy = require('symlink-or-copy').sync; -var isWindows = process.platform === 'win32'; -/** - * Intercepts each changed file and replaces its contents with - * the associated changes. - */ -var DiffingFlatten = (function () { - function DiffingFlatten(inputPath, cachePath, options) { - this.inputPath = inputPath; - this.cachePath = cachePath; - this.options = options; - } - DiffingFlatten.prototype.rebuild = function (treeDiff) { - var _this = this; - var pathsToUpdate = treeDiff.addedPaths; - // since we need to run on Windows as well we can't rely on symlinks being available, - // which means that we need to respond to both added and changed paths - if (isWindows) { - pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths); - } - pathsToUpdate.forEach(function (changedFilePath) { - var sourceFilePath = path.join(_this.inputPath, changedFilePath); - var destFilePath = path.join(_this.cachePath, path.basename(changedFilePath)); - var destDirPath = path.dirname(destFilePath); - if (!fs.existsSync(destDirPath)) { - fse.mkdirpSync(destDirPath); - } - if (!fs.existsSync(destFilePath)) { - symlinkOrCopy(sourceFilePath, destFilePath); - } - else { - throw new Error(("Duplicate file '" + path.basename(changedFilePath) + "' ") + - ("found in path '" + changedFilePath + "'")); - } - }); - treeDiff.removedPaths.forEach(function (removedFilePath) { - var destFilePath = path.join(_this.cachePath, path.basename(removedFilePath)); - fs.unlinkSync(destFilePath); - }); - }; - return DiffingFlatten; -}()); -exports.DiffingFlatten = DiffingFlatten; -exports.__esModule = true; -exports["default"] = diffing_broccoli_plugin_1.wrapDiffingPlugin(DiffingFlatten); -//# sourceMappingURL=broccoli-flatten.js.map \ No newline at end of file diff --git a/lib/broccoli/broccoli-flatten.js.map b/lib/broccoli/broccoli-flatten.js.map deleted file mode 100644 index 34a6eb84d2a9..000000000000 --- a/lib/broccoli/broccoli-flatten.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"broccoli-flatten.js","sourceRoot":"","sources":["broccoli-flatten.ts"],"names":[],"mappings":";AAAA,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,wCAAmE,2BAA2B,CAAC,CAAA;AAC/F,IAAI,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC;AAEpD,IAAI,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAG7C;;;GAGG;AACH;IACE,wBAAoB,SAAS,EAAU,SAAS,EAAU,OAAO;QAA7C,cAAS,GAAT,SAAS,CAAA;QAAU,cAAS,GAAT,SAAS,CAAA;QAAU,YAAO,GAAP,OAAO,CAAA;IAAG,CAAC;IAGrE,gCAAO,GAAP,UAAQ,QAAoB;QAA5B,iBA8BC;QA7BC,IAAI,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC;QAExC,qFAAqF;QACrF,sEAAsE;QACtE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;YACd,aAAa,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC9D,CAAC;QAED,aAAa,CAAC,OAAO,CAAC,UAAC,eAAe;YACpC,IAAI,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;YAChE,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;YAC7E,IAAI,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAE7C,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;gBAChC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YAC9B,CAAC;YAED,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;gBACjC,aAAa,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;YAC9C,CAAC;YAAC,IAAI,CAAC,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC,sBAAmB,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,QAAI;oBACrD,qBAAkB,eAAe,OAAG,CAAC,CAAC;YACxD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,UAAC,eAAe;YAC5C,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;YAC7E,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IACH,qBAAC;AAAD,CAAC,AAnCD,IAmCC;AAnCY,sBAAc,iBAmC1B,CAAA;AAED;qBAAe,2CAAiB,CAAC,cAAc,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/broccoli/broccoli-merge-trees.js b/lib/broccoli/broccoli-merge-trees.js deleted file mode 100644 index 64c700442134..000000000000 --- a/lib/broccoli/broccoli-merge-trees.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict"; -var fs = require('fs'); -var fse = require('fs-extra'); -var path = require('path'); -var symlinkOrCopySync = require('symlink-or-copy').sync; -var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin'); -var isWindows = process.platform === 'win32'; -function outputFileSync(sourcePath, destPath) { - var dirname = path.dirname(destPath); - fse.mkdirsSync(dirname, { fs: fs }); - symlinkOrCopySync(sourcePath, destPath); -} -function pathOverwrittenError(path) { - var msg = 'Either remove the duplicate or enable the "overwrite" option for this merge.'; - return new Error("Duplicate path found while merging trees. Path: \"" + path + "\".\n" + msg); -} -var MergeTrees = (function () { - function MergeTrees(inputPaths, cachePath, options) { - if (options === void 0) { options = {}; } - this.inputPaths = inputPaths; - this.cachePath = cachePath; - this.pathCache = Object.create(null); - this.firstBuild = true; - this.options = options || {}; - } - MergeTrees.prototype.rebuild = function (treeDiffs) { - var _this = this; - var overwrite = this.options.overwrite; - var pathsToEmit = []; - var pathsToRemove = []; - var emitted = Object.create(null); - var contains = function (cache, val) { - for (var i = 0, ii = cache.length; i < ii; ++i) { - if (cache[i] === val) - return true; - } - return false; - }; - var emit = function (relativePath) { - // ASSERT(!emitted[relativePath]); - pathsToEmit.push(relativePath); - emitted[relativePath] = true; - }; - if (this.firstBuild) { - this.firstBuild = false; - // Build initial cache - treeDiffs.reverse().forEach(function (treeDiff, index) { - index = treeDiffs.length - 1 - index; - treeDiff.addedPaths.forEach(function (changedPath) { - var cache = _this.pathCache[changedPath]; - if (cache === undefined) { - _this.pathCache[changedPath] = [index]; - pathsToEmit.push(changedPath); - } - else if (overwrite) { - // ASSERT(contains(pathsToEmit, changedPath)); - cache.unshift(index); - } - else { - throw pathOverwrittenError(changedPath); - } - }); - }); - } - else { - // Update cache - treeDiffs.reverse().forEach(function (treeDiff, index) { - index = treeDiffs.length - 1 - index; - if (treeDiff.removedPaths) { - treeDiff.removedPaths.forEach(function (removedPath) { - var cache = _this.pathCache[removedPath]; - // ASSERT(cache !== undefined); - // ASSERT(contains(cache, index)); - if (cache[cache.length - 1] === index) { - pathsToRemove.push(path.join(_this.cachePath, removedPath)); - cache.pop(); - if (cache.length === 0) { - _this.pathCache[removedPath] = undefined; - } - else if (!emitted[removedPath]) { - if (cache.length === 1 && !overwrite) { - throw pathOverwrittenError(removedPath); - } - emit(removedPath); - } - } - }); - } - var pathsToUpdate = treeDiff.addedPaths; - if (isWindows) { - pathsToUpdate = pathsToUpdate.concat(treeDiff.changedPaths); - } - pathsToUpdate.forEach(function (changedPath) { - var cache = _this.pathCache[changedPath]; - if (cache === undefined) { - // File was added - _this.pathCache[changedPath] = [index]; - emit(changedPath); - } - else if (!contains(cache, index)) { - cache.push(index); - cache.sort(function (a, b) { return a - b; }); - if (cache.length > 1 && !overwrite) { - throw pathOverwrittenError(changedPath); - } - if (cache[cache.length - 1] === index && !emitted[changedPath]) { - emit(changedPath); - } - } - }); - }); - } - pathsToRemove.forEach(function (destPath) { return fse.removeSync(destPath); }); - pathsToEmit.forEach(function (emittedPath) { - var cache = _this.pathCache[emittedPath]; - var destPath = path.join(_this.cachePath, emittedPath); - var sourceIndex = cache[cache.length - 1]; - var sourceInputPath = _this.inputPaths[sourceIndex]; - var sourcePath = path.join(sourceInputPath, emittedPath); - if (cache.length > 1) { - fse.removeSync(destPath); - } - outputFileSync(sourcePath, destPath); - }); - }; - return MergeTrees; -}()); -exports.MergeTrees = MergeTrees; -exports.__esModule = true; -exports["default"] = diffing_broccoli_plugin_1.wrapDiffingPlugin(MergeTrees); -//# sourceMappingURL=broccoli-merge-trees.js.map \ No newline at end of file diff --git a/lib/broccoli/broccoli-merge-trees.js.map b/lib/broccoli/broccoli-merge-trees.js.map deleted file mode 100644 index ad0f9477cbe9..000000000000 --- a/lib/broccoli/broccoli-merge-trees.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"broccoli-merge-trees.js","sourceRoot":"","sources":["broccoli-merge-trees.ts"],"names":[],"mappings":";AAAA,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,IAAI,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC;AACxD,wCAAmE,2BAA2B,CAAC,CAAA;AAE/F,IAAI,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAM7C,wBAAwB,UAAU,EAAE,QAAQ;IAC1C,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,EAAC,EAAE,EAAE,EAAE,EAAC,CAAC,CAAC;IAClC,iBAAiB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,8BAA8B,IAAI;IAChC,IAAM,GAAG,GAAG,8EAA8E,CAAC;IAC3F,MAAM,CAAC,IAAI,KAAK,CAAC,uDAAoD,IAAI,aAAO,GAAK,CAAC,CAAC;AACzF,CAAC;AAED;IAKE,oBAAmB,UAAoB,EAAS,SAAiB,EACrD,OAA+B;QAA/B,uBAA+B,GAA/B,YAA+B;QADxB,eAAU,GAAV,UAAU,CAAU;QAAS,cAAS,GAAT,SAAS,CAAQ;QAJzD,cAAS,GAA8B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE3D,eAAU,GAAY,IAAI,CAAC;QAIjC,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,EAAE,CAAC;IAC/B,CAAC;IAED,4BAAO,GAAP,UAAQ,SAAuB;QAA/B,iBAoGC;QAnGC,IAAI,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;QACvC,IAAI,WAAW,GAAa,EAAE,CAAC;QAC/B,IAAI,aAAa,GAAa,EAAE,CAAC;QACjC,IAAI,OAAO,GAA6B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,QAAQ,GAAG,UAAC,KAAK,EAAE,GAAG;YACxB,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;gBAC/C,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;oBAAC,MAAM,CAAC,IAAI,CAAC;YACpC,CAAC;YACD,MAAM,CAAC,KAAK,CAAC;QACf,CAAC,CAAC;QAEF,IAAI,IAAI,GAAG,UAAC,YAAY;YACtB,kCAAkC;YAClC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC/B,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;QAC/B,CAAC,CAAC;QAEF,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YAExB,sBAAsB;YACtB,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,UAAC,QAAoB,EAAE,KAAK;gBACtD,KAAK,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC;gBACrC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAC,WAAW;oBACtC,IAAI,KAAK,GAAG,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;oBACxC,EAAE,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;wBACxB,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;wBACtC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBAChC,CAAC;oBAAC,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;wBACrB,8CAA8C;wBAC9C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;oBACvB,CAAC;oBAAC,IAAI,CAAC,CAAC;wBACN,MAAM,oBAAoB,CAAC,WAAW,CAAC,CAAC;oBAC1C,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QAEL,CAAC;QAAC,IAAI,CAAC,CAAC;YACN,eAAe;YACf,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,UAAC,QAAoB,EAAE,KAAK;gBACtD,KAAK,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC;gBACrC,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;oBAC1B,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,UAAC,WAAW;wBACxC,IAAI,KAAK,GAAG,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;wBACxC,+BAA+B;wBAC/B,kCAAkC;wBAClC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;4BACtC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;4BAC3D,KAAK,CAAC,GAAG,EAAE,CAAC;4BACZ,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;gCACvB,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC;4BAC1C,CAAC;4BAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;gCACjC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oCACrC,MAAM,oBAAoB,CAAC,WAAW,CAAC,CAAC;gCAC1C,CAAC;gCACD,IAAI,CAAC,WAAW,CAAC,CAAC;4BACpB,CAAC;wBACH,CAAC;oBACH,CAAC,CAAC,CAAC;gBACL,CAAC;gBAED,IAAI,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC;gBAExC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;oBACd,aAAa,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;gBAC9D,CAAC;gBAED,aAAa,CAAC,OAAO,CAAC,UAAC,WAAW;oBAChC,IAAI,KAAK,GAAG,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;oBACxC,EAAE,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;wBACxB,iBAAiB;wBACjB,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;wBACtC,IAAI,CAAC,WAAW,CAAC,CAAC;oBACpB,CAAC;oBAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;wBACnC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;wBAClB,KAAK,CAAC,IAAI,CAAC,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,GAAG,CAAC,EAAL,CAAK,CAAC,CAAC;wBAC5B,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;4BACnC,MAAM,oBAAoB,CAAC,WAAW,CAAC,CAAC;wBAC1C,CAAC;wBACD,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;4BAC/D,IAAI,CAAC,WAAW,CAAC,CAAC;wBACpB,CAAC;oBACH,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QAED,aAAa,CAAC,OAAO,CAAC,UAAC,QAAQ,IAAK,OAAA,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAxB,CAAwB,CAAC,CAAC;QAC9D,WAAW,CAAC,OAAO,CAAC,UAAC,WAAW;YAC9B,IAAI,KAAK,GAAG,KAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YACxC,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;YACtD,IAAI,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC1C,IAAI,eAAe,GAAG,KAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YACnD,IAAI,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;YACzD,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;gBACrB,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC3B,CAAC;YACD,cAAc,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC;IACH,iBAAC;AAAD,CAAC,AA/GD,IA+GC;AA/GY,kBAAU,aA+GtB,CAAA;AAED;qBAAe,2CAAiB,CAAC,UAAU,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/broccoli/broccoli-replace.js b/lib/broccoli/broccoli-replace.js deleted file mode 100644 index 9fde024bda65..000000000000 --- a/lib/broccoli/broccoli-replace.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; -var fs = require('fs'); -var fse = require('fs-extra'); -var path = require('path'); -var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin'); -var minimatch = require('minimatch'); -var FILE_ENCODING = { encoding: 'utf-8' }; -/** - * Intercepts each changed file and replaces its contents with - * the associated changes. - */ -var DiffingReplace = (function () { - function DiffingReplace(inputPath, cachePath, options) { - this.inputPath = inputPath; - this.cachePath = cachePath; - this.options = options; - } - DiffingReplace.prototype.rebuild = function (treeDiff) { - var _this = this; - var patterns = this.options.patterns; - var files = this.options.files; - treeDiff.addedPaths.concat(treeDiff.changedPaths) - .forEach(function (changedFilePath) { - var sourceFilePath = path.join(_this.inputPath, changedFilePath); - var destFilePath = path.join(_this.cachePath, changedFilePath); - var destDirPath = path.dirname(destFilePath); - if (!fs.existsSync(destDirPath)) { - fse.mkdirpSync(destDirPath); - } - var fileMatches = files.some(function (filePath) { return minimatch(changedFilePath, filePath); }); - if (fileMatches) { - var content = fs.readFileSync(sourceFilePath, FILE_ENCODING); - patterns.forEach(function (pattern) { - var replacement = pattern.replacement; - if (typeof replacement === 'function') { - replacement = function (content) { - return pattern.replacement(content, changedFilePath); - }; - } - content = content.replace(pattern.match, replacement); - }); - fs.writeFileSync(destFilePath, content, FILE_ENCODING); - } - else if (!fs.existsSync(destFilePath)) { - try { - fs.symlinkSync(sourceFilePath, destFilePath); - } - catch (e) { - fs.writeFileSync(destFilePath, fs.readFileSync(sourceFilePath)); - } - } - }); - treeDiff.removedPaths.forEach(function (removedFilePath) { - var destFilePath = path.join(_this.cachePath, removedFilePath); - fs.unlinkSync(destFilePath); - }); - }; - return DiffingReplace; -}()); -exports.__esModule = true; -exports["default"] = diffing_broccoli_plugin_1.wrapDiffingPlugin(DiffingReplace); -//# sourceMappingURL=broccoli-replace.js.map \ No newline at end of file diff --git a/lib/broccoli/broccoli-replace.js.map b/lib/broccoli/broccoli-replace.js.map deleted file mode 100644 index 6561a83f0a72..000000000000 --- a/lib/broccoli/broccoli-replace.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"broccoli-replace.js","sourceRoot":"","sources":["broccoli-replace.ts"],"names":[],"mappings":";AAAA,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,wCAAmE,2BAA2B,CAAC,CAAA;AAE/F,IAAI,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AACrC,IAAI,aAAa,GAAG,EAAC,QAAQ,EAAE,OAAO,EAAC,CAAC;AAExC;;;GAGG;AACH;IACE,wBAAoB,SAAS,EAAU,SAAS,EAAU,OAAO;QAA7C,cAAS,GAAT,SAAS,CAAA;QAAU,cAAS,GAAT,SAAS,CAAA;QAAU,YAAO,GAAP,OAAO,CAAA;IAAG,CAAC;IAErE,gCAAO,GAAP,UAAQ,QAAoB;QAA5B,iBAwCC;QAvCC,IAAI,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACrC,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAE/B,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;aAC5C,OAAO,CAAC,UAAC,eAAe;YACvB,IAAI,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;YAChE,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;YAC9D,IAAI,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAE7C,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;gBAChC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YAC9B,CAAC;YAED,IAAI,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,UAAC,QAAQ,IAAK,OAAA,SAAS,CAAC,eAAe,EAAE,QAAQ,CAAC,EAApC,CAAoC,CAAC,CAAC;YACjF,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;gBAChB,IAAI,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;gBAC7D,QAAQ,CAAC,OAAO,CAAC,UAAC,OAAO;oBACvB,IAAI,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;oBACtC,EAAE,CAAC,CAAC,OAAO,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC;wBACtC,WAAW,GAAG,UAAS,OAAO;4BAC5B,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;wBACvD,CAAC,CAAC;oBACJ,CAAC;oBACD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;gBACxD,CAAC,CAAC,CAAC;gBACH,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;YACzD,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;gBACxC,IAAI,CAAC;oBACH,EAAE,CAAC,WAAW,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;gBAC/C,CAAE;gBAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBACX,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC;gBAClE,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEP,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,UAAC,eAAe;YAC5C,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;YAC9D,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IACH,qBAAC;AAAD,CAAC,AA5CD,IA4CC;AAED;qBAAe,2CAAiB,CAAC,cAAc,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/broccoli/broccoli-tree-stabilizer.js b/lib/broccoli/broccoli-tree-stabilizer.js deleted file mode 100644 index 2dd177d2f5ed..000000000000 --- a/lib/broccoli/broccoli-tree-stabilizer.js +++ /dev/null @@ -1,38 +0,0 @@ -/// -/// -"use strict"; -var fs = require('fs'); -var symlinkOrCopy = require('symlink-or-copy'); -/** - * Stabilizes the inputPath for the following plugins in the build tree. - * - * All broccoli plugins that inherit from `broccoli-writer` or `broccoli-filter` change their - * outputPath during each rebuild. - * - * This means that all following plugins in the build tree can't rely on their inputPath being - * immutable. This results in breakage of any plugin that is not expecting such behavior. - * - * For example all `DiffingBroccoliPlugin`s expect their inputPath to be stable. - * - * By inserting this plugin into the tree after any misbehaving plugin, we can stabilize the - * inputPath for the following plugin in the tree and correct the surprising behavior. - */ -var TreeStabilizer = (function () { - function TreeStabilizer(inputTree) { - this.inputTree = inputTree; - } - TreeStabilizer.prototype.rebuild = function () { - fs.rmdirSync(this.outputPath); - // TODO: investigate if we can use rename the directory instead to improve performance on - // Windows - symlinkOrCopy.sync(this.inputPath, this.outputPath); - }; - TreeStabilizer.prototype.cleanup = function () { }; - return TreeStabilizer; -}()); -function stabilizeTree(inputTree) { - return new TreeStabilizer(inputTree); -} -exports.__esModule = true; -exports["default"] = stabilizeTree; -//# sourceMappingURL=broccoli-tree-stabilizer.js.map \ No newline at end of file diff --git a/lib/broccoli/broccoli-tree-stabilizer.js.map b/lib/broccoli/broccoli-tree-stabilizer.js.map deleted file mode 100644 index fd154bff28c8..000000000000 --- a/lib/broccoli/broccoli-tree-stabilizer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"broccoli-tree-stabilizer.js","sourceRoot":"","sources":["broccoli-tree-stabilizer.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,kDAAkD;;AAElD,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAI,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAG/C;;;;;;;;;;;;;GAaG;AACH;IAKE,wBAAmB,SAAuB;QAAvB,cAAS,GAAT,SAAS,CAAc;IAAG,CAAC;IAG9C,gCAAO,GAAP;QACE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE9B,yFAAyF;QACzF,UAAU;QACV,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC;IAGD,gCAAO,GAAP,cAAW,CAAC;IACd,qBAAC;AAAD,CAAC,AAlBD,IAkBC;AAGD,uBAAsC,SAAS;IAC7C,MAAM,CAAC,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;AACvC,CAAC;AAFD;kCAEC,CAAA"} \ No newline at end of file diff --git a/lib/broccoli/broccoli-typescript.js b/lib/broccoli/broccoli-typescript.js deleted file mode 100644 index ba4d9016aec4..000000000000 --- a/lib/broccoli/broccoli-typescript.js +++ /dev/null @@ -1,327 +0,0 @@ -/// -"use strict"; -var fs = require('fs'); -var fse = require('fs-extra'); -var path = require('path'); -var ts = require('typescript'); -var diffing_broccoli_plugin_1 = require('./diffing-broccoli-plugin'); -var FS_OPTS = { - encoding: 'utf-8' -}; -// Sub-directory where the @internal typing files (.d.ts) are stored -exports.INTERNAL_TYPINGS_PATH = 'internal_typings'; -// Monkey patch the TS compiler to be able to re-emit files with @internal symbols -var tsEmitInternal = false; -var originalEmitFiles = ts.emitFiles; -ts.emitFiles = function (resolver, host, targetSourceFile) { - if (tsEmitInternal) { - var orignalgetCompilerOptions_1 = host.getCompilerOptions; - host.getCompilerOptions = function () { - var options = clone(orignalgetCompilerOptions_1.call(host)); - options.stripInternal = false; - options.outDir = options.outDir + "/" + exports.INTERNAL_TYPINGS_PATH; - return options; - }; - } - return originalEmitFiles(resolver, host, targetSourceFile); -}; -/** - * Broccoli plugin that implements incremental Typescript compiler. - * - * It instantiates a typescript compiler instance that keeps all the state about the project and - * can re-emit only the files that actually changed. - * - * Limitations: only files that map directly to the changed source file via naming conventions are - * re-emitted. This primarily affects code that uses `const enum`s, because changing the enum value - * requires global emit, which can affect many files. - */ -var DiffingTSCompiler = (function () { - function DiffingTSCompiler(inputPath, cachePath, options) { - this.inputPath = inputPath; - this.cachePath = cachePath; - this.options = options; - this.fileRegistry = Object.create(null); - this.firstRun = true; - this.previousRunFailed = false; - // Whether to generate the @internal typing files (they are only generated when `stripInternal` is - // true) - this.genInternalTypings = false; - if (options.rootFilePaths) { - this.rootFilePaths = options.rootFilePaths.splice(0); - delete options.rootFilePaths; - } - else { - this.rootFilePaths = []; - } - if (options.internalTypings) { - this.genInternalTypings = true; - delete options.internalTypings; - } - // the conversion is a bit awkward, see https://github.com/Microsoft/TypeScript/issues/5276 - // in 1.8 use convertCompilerOptionsFromJson - this.tsOpts = - ts.parseJsonConfigFileContent({ compilerOptions: options, files: [] }, null, null).options; - if (this.tsOpts.stripInternal === false) { - // @internal are included in the generated .d.ts, do not generate them separately - this.genInternalTypings = false; - } - // TODO: the above turns rootDir set to './' into an empty string - looks like a tsc bug - // check back when we upgrade to 1.7.x - if (this.tsOpts.rootDir === '') { - this.tsOpts.rootDir = './'; - } - this.tsOpts.outDir = this.cachePath; - this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths, this.fileRegistry, this.inputPath); - this.tsService = ts.createLanguageService(this.tsServiceHost, ts.createDocumentRegistry()); - } - DiffingTSCompiler.prototype.rebuild = function (treeDiff) { - var _this = this; - var pathsToEmit = []; - var pathsWithErrors = []; - var errorMessages = []; - treeDiff.addedPaths.concat(treeDiff.changedPaths) - .forEach(function (tsFilePath) { - if (!_this.fileRegistry[tsFilePath]) { - _this.fileRegistry[tsFilePath] = { version: 0 }; - _this.rootFilePaths.push(tsFilePath); - } - else { - _this.fileRegistry[tsFilePath].version++; - } - pathsToEmit.push(tsFilePath); - }); - treeDiff.removedPaths.forEach(function (tsFilePath) { - console.log('removing outputs for', tsFilePath); - _this.rootFilePaths.splice(_this.rootFilePaths.indexOf(tsFilePath), 1); - _this.fileRegistry[tsFilePath] = null; - _this.removeOutputFor(tsFilePath); - }); - if (this.firstRun) { - this.firstRun = false; - this.doFullBuild(); - } - else { - tsEmitInternal = false; - pathsToEmit.forEach(function (tsFilePath) { - var output = _this.tsService.getEmitOutput(tsFilePath); - if (output.emitSkipped) { - var errorFound = _this.collectErrors(tsFilePath); - if (errorFound) { - pathsWithErrors.push(tsFilePath); - errorMessages.push(errorFound); - } - } - else { - output.outputFiles.forEach(function (o) { - var destDirPath = path.dirname(o.name); - fse.mkdirsSync(destDirPath); - fs.writeFileSync(o.name, _this.fixSourceMapSources(o.text), FS_OPTS); - }); - } - }); - if (pathsWithErrors.length) { - this.previousRunFailed = true; - var error = new Error('Typescript found the following errors:\n' + errorMessages.join('\n')); - error['showStack'] = false; - throw error; - } - else if (this.previousRunFailed) { - this.doFullBuild(); - } - else if (this.genInternalTypings) { - // serialize the .d.ts files containing @internal symbols - tsEmitInternal = true; - pathsToEmit.forEach(function (tsFilePath) { - var output = _this.tsService.getEmitOutput(tsFilePath); - if (!output.emitSkipped) { - output.outputFiles.forEach(function (o) { - if (endsWith(o.name, '.d.ts')) { - var destDirPath = path.dirname(o.name); - fse.mkdirsSync(destDirPath); - fs.writeFileSync(o.name, _this.fixSourceMapSources(o.text), FS_OPTS); - } - }); - } - }); - tsEmitInternal = false; - } - } - }; - DiffingTSCompiler.prototype.collectErrors = function (tsFilePath) { - var allDiagnostics = this.tsService.getCompilerOptionsDiagnostics() - .concat(this.tsService.getSyntacticDiagnostics(tsFilePath)) - .concat(this.tsService.getSemanticDiagnostics(tsFilePath)); - var errors = []; - allDiagnostics.forEach(function (diagnostic) { - var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); - if (diagnostic.file) { - var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - errors.push(" " + diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message); - } - else { - errors.push(" Error: " + message); - } - }); - if (errors.length) { - return errors.join('\n'); - } - }; - DiffingTSCompiler.prototype.doFullBuild = function () { - var _this = this; - var program = this.tsService.getProgram(); - tsEmitInternal = false; - var emitResult = program.emit(undefined, function (absoluteFilePath, fileContent) { - fse.mkdirsSync(path.dirname(absoluteFilePath)); - fs.writeFileSync(absoluteFilePath, _this.fixSourceMapSources(fileContent), FS_OPTS); - }); - if (this.genInternalTypings) { - // serialize the .d.ts files containing @internal symbols - tsEmitInternal = true; - program.emit(undefined, function (absoluteFilePath, fileContent) { - if (endsWith(absoluteFilePath, '.d.ts')) { - fse.mkdirsSync(path.dirname(absoluteFilePath)); - fs.writeFileSync(absoluteFilePath, fileContent, FS_OPTS); - } - }); - tsEmitInternal = false; - } - if (emitResult.emitSkipped) { - var allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); - var errorMessages_1 = []; - allDiagnostics.forEach(function (diagnostic) { - var pos = ''; - if (diagnostic.file) { - var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - pos = diagnostic.file.fileName + " (" + (line + 1) + ", " + (character + 1) + "): "; - } - var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - errorMessages_1.push(" " + pos + message); - }); - if (errorMessages_1.length) { - this.previousRunFailed = true; - var error = new Error('Typescript found the following errors:\n' + errorMessages_1.join('\n')); - error['showStack'] = false; - throw error; - } - else { - this.previousRunFailed = false; - } - } - }; - /** - * There is a bug in TypeScript 1.6, where the sourceRoot and inlineSourceMap properties - * are exclusive. This means that the sources property always contains relative paths - * (e.g, ../../../../angular2/src/di/injector.ts). - * - * Here, we normalize the sources property and remove the ../../../ - * - * This issue is fixed in https://github.com/Microsoft/TypeScript/pull/5620. - * Once we switch to TypeScript 1.8, we can remove this method. - */ - DiffingTSCompiler.prototype.fixSourceMapSources = function (content) { - try { - var marker = "//# sourceMappingURL=data:application/json;base64,"; - var index = content.indexOf(marker); - if (index == -1) - return content; - var base = content.substring(0, index + marker.length); - var sourceMapBit = new Buffer(content.substring(index + marker.length), 'base64').toString("utf8"); - var sourceMaps = JSON.parse(sourceMapBit); - var source = sourceMaps.sources[0]; - sourceMaps.sources = [source.substring(source.lastIndexOf("../") + 3)]; - return "" + base + new Buffer(JSON.stringify(sourceMaps)).toString('base64'); - } - catch (e) { - return content; - } - }; - DiffingTSCompiler.prototype.removeOutputFor = function (tsFilePath) { - var absoluteJsFilePath = path.join(this.cachePath, tsFilePath.replace(/\.ts$/, '.js')); - var absoluteMapFilePath = path.join(this.cachePath, tsFilePath.replace(/.ts$/, '.js.map')); - var absoluteDtsFilePath = path.join(this.cachePath, tsFilePath.replace(/\.ts$/, '.d.ts')); - if (fs.existsSync(absoluteJsFilePath)) { - fs.unlinkSync(absoluteJsFilePath); - if (fs.existsSync(absoluteMapFilePath)) { - // source map could be inline or not generated - fs.unlinkSync(absoluteMapFilePath); - } - fs.unlinkSync(absoluteDtsFilePath); - } - }; - DiffingTSCompiler.includeExtensions = ['.ts']; - DiffingTSCompiler.excludeExtensions = ['.d.ts']; - return DiffingTSCompiler; -}()); -var CustomLanguageServiceHost = (function () { - function CustomLanguageServiceHost(compilerOptions, fileNames, fileRegistry, treeInputPath) { - this.compilerOptions = compilerOptions; - this.fileNames = fileNames; - this.fileRegistry = fileRegistry; - this.treeInputPath = treeInputPath; - this.currentDirectory = process.cwd(); - this.defaultLibFilePath = ts.getDefaultLibFilePath(compilerOptions).replace(/\\/g, '/'); - } - CustomLanguageServiceHost.prototype.getScriptFileNames = function () { return this.fileNames; }; - CustomLanguageServiceHost.prototype.getScriptVersion = function (fileName) { - return this.fileRegistry[fileName] && this.fileRegistry[fileName].version.toString(); - }; - /** - * This method is called quite a bit to lookup 3 kinds of paths: - * 1/ files in the fileRegistry - * - these are the files in our project that we are watching for changes - * - in the future we could add caching for these files and invalidate the cache when - * the file is changed lazily during lookup - * 2/ .d.ts and library files not in the fileRegistry - * - these are not our files, they come from tsd or typescript itself - * - these files change only rarely but since we need them very rarely, it's not worth the - * cache invalidation hassle to cache them - * 3/ bogus paths that typescript compiler tries to lookup during import resolution - * - these paths are tricky to cache since files come and go and paths that was bogus in the - * past might not be bogus later - * - * In the initial experiments the impact of this caching was insignificant (single digit %) and - * not worth the potential issues with stale cache records. - */ - CustomLanguageServiceHost.prototype.getScriptSnapshot = function (tsFilePath) { - var absoluteTsFilePath; - if (tsFilePath == this.defaultLibFilePath || path.isAbsolute(tsFilePath)) { - absoluteTsFilePath = tsFilePath; - } - else if (this.compilerOptions.moduleResolution === 2 /* NodeJs */ && - tsFilePath.match(/^node_modules/)) { - absoluteTsFilePath = path.resolve(tsFilePath); - } - else if (tsFilePath.match(/^rxjs/)) { - absoluteTsFilePath = path.resolve('node_modules', tsFilePath); - } - else { - absoluteTsFilePath = path.join(this.treeInputPath, tsFilePath); - } - if (!fs.existsSync(absoluteTsFilePath)) { - // TypeScript seems to request lots of bogus paths during import path lookup and resolution, - // so we we just return undefined when the path is not correct. - return undefined; - } - return ts.ScriptSnapshot.fromString(fs.readFileSync(absoluteTsFilePath, FS_OPTS)); - }; - CustomLanguageServiceHost.prototype.getCurrentDirectory = function () { return this.currentDirectory; }; - CustomLanguageServiceHost.prototype.getCompilationSettings = function () { return this.compilerOptions; }; - CustomLanguageServiceHost.prototype.getDefaultLibFileName = function (options) { - // ignore options argument, options should not change during the lifetime of the plugin - return this.defaultLibFilePath; - }; - return CustomLanguageServiceHost; -}()); -exports.__esModule = true; -exports["default"] = diffing_broccoli_plugin_1.wrapDiffingPlugin(DiffingTSCompiler); -function clone(object) { - var result = {}; - for (var id in object) { - result[id] = object[id]; - } - return result; -} -function endsWith(str, substring) { - return str.indexOf(substring, str.length - substring.length) !== -1; -} -//# sourceMappingURL=broccoli-typescript.js.map \ No newline at end of file diff --git a/lib/broccoli/broccoli-typescript.js.map b/lib/broccoli/broccoli-typescript.js.map deleted file mode 100644 index b4427f9782bd..000000000000 --- a/lib/broccoli/broccoli-typescript.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"broccoli-typescript.js","sourceRoot":"","sources":["broccoli-typescript.ts"],"names":[],"mappings":"AAAA,kDAAkD;;AAElD,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,GAAG,WAAW,UAAU,CAAC,CAAC;AACjC,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,IAAY,EAAE,WAAM,YAAY,CAAC,CAAA;AACjC,wCAAmE,2BAA2B,CAAC,CAAA;AAK/F,IAAM,OAAO,GAAG;IACd,QAAQ,EAAE,OAAO;CAClB,CAAC;AAEF,oEAAoE;AACvD,6BAAqB,GAAW,kBAAkB,CAAC;AAEhE,kFAAkF;AAClF,IAAI,cAAc,GAAY,KAAK,CAAC;AAEpC,IAAM,iBAAiB,GAAmB,EAAG,CAAC,SAAS,CAAC;AAElD,EAAG,CAAC,SAAS,GAAG,UAAS,QAAa,EAAE,IAAS,EAAE,gBAAqB;IAC5E,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;QACnB,IAAM,2BAAyB,GAAG,IAAI,CAAC,kBAAkB,CAAC;QAC1D,IAAI,CAAC,kBAAkB,GAAG;YACxB,IAAI,OAAO,GAAG,KAAK,CAAC,2BAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,aAAa,GAAG,KAAK,CAAC;YAC9B,OAAO,CAAC,MAAM,GAAM,OAAO,CAAC,MAAM,SAAI,6BAAuB,CAAC;YAC9D,MAAM,CAAC,OAAO,CAAC;QACjB,CAAC,CAAA;IACH,CAAC;IACD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH;IAeE,2BAAmB,SAAiB,EAAS,SAAiB,EAAS,OAAO;QAA3D,cAAS,GAAT,SAAS,CAAQ;QAAS,cAAS,GAAT,SAAS,CAAQ;QAAS,YAAO,GAAP,OAAO,CAAA;QAbtE,iBAAY,GAAiB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAIjD,aAAQ,GAAY,IAAI,CAAC;QACzB,sBAAiB,GAAY,KAAK,CAAC;QAC3C,kGAAkG;QAClG,QAAQ;QACA,uBAAkB,GAAY,KAAK,CAAC;QAM1C,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACrD,OAAO,OAAO,CAAC,aAAa,CAAC;QAC/B,CAAC;QAAC,IAAI,CAAC,CAAC;YACN,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QAC1B,CAAC;QAED,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAC/B,OAAO,OAAO,CAAC,eAAe,CAAC;QACjC,CAAC;QAED,2FAA2F;QAC3F,4CAA4C;QAC5C,IAAI,CAAC,MAAM;YACP,EAAE,CAAC,0BAA0B,CAAC,EAAC,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC;QAE7F,EAAE,CAAC,CAAO,IAAI,CAAC,MAAO,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC;YAC/C,iFAAiF;YACjF,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;QAClC,CAAC;QAED,wFAAwF;QACxF,4CAA4C;QAC5C,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;QAEpC,IAAI,CAAC,aAAa,GAAG,IAAI,yBAAyB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAC/B,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACtF,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAC7F,CAAC;IAGD,mCAAO,GAAP,UAAQ,QAAoB;QAA5B,iBA0EC;QAzEC,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,eAAe,GAAG,EAAE,CAAC;QACzB,IAAI,aAAa,GAAG,EAAE,CAAC;QAEvB,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;aAC5C,OAAO,CAAC,UAAC,UAAU;YAClB,EAAE,CAAC,CAAC,CAAC,KAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBACnC,KAAI,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,EAAC,OAAO,EAAE,CAAC,EAAC,CAAC;gBAC7C,KAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACtC,CAAC;YAAC,IAAI,CAAC,CAAC;gBACN,KAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;YAC1C,CAAC;YAED,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEP,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,UAAC,UAAU;YACvC,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAAC;YAEhD,KAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAI,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;YACrE,KAAI,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;YACrC,KAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,CAAC;QAAC,IAAI,CAAC,CAAC;YACN,cAAc,GAAG,KAAK,CAAC;YACvB,WAAW,CAAC,OAAO,CAAC,UAAC,UAAU;gBAC7B,IAAI,MAAM,GAAG,KAAI,CAAC,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;gBAEtD,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;oBACvB,IAAI,UAAU,GAAG,KAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;oBAChD,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;wBACf,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;wBACjC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;gBAAC,IAAI,CAAC,CAAC;oBACN,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,UAAA,CAAC;wBAC1B,IAAI,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;wBACvC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;wBAC5B,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,KAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;oBACtE,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC3B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;gBAC9B,IAAI,KAAK,GACL,IAAI,KAAK,CAAC,0CAA0C,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrF,KAAK,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;gBAC3B,MAAM,KAAK,CAAC;YACd,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;gBAClC,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;gBACnC,yDAAyD;gBACzD,cAAc,GAAG,IAAI,CAAC;gBACtB,WAAW,CAAC,OAAO,CAAC,UAAC,UAAU;oBAC7B,IAAI,MAAM,GAAG,KAAI,CAAC,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;oBACtD,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;wBACxB,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,UAAA,CAAC;4BAC1B,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;gCAC9B,IAAI,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gCACvC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;gCAC5B,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,KAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;4BACtE,CAAC;wBACH,CAAC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC,CAAC,CAAC;gBACH,cAAc,GAAG,KAAK,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAEO,yCAAa,GAArB,UAAsB,UAAU;QAC9B,IAAI,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,6BAA6B,EAAE;aACzC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;aAC1D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,cAAc,CAAC,OAAO,CAAC,UAAA,UAAU;YAC/B,IAAI,OAAO,GAAG,EAAE,CAAC,4BAA4B,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YAC5E,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;gBACpB,IAAA,oEAAuF,EAAlF,cAAI,EAAE,wBAAS,CAAoE;gBACxF,MAAM,CAAC,IAAI,CAAC,OAAK,UAAU,CAAC,IAAI,CAAC,QAAQ,WAAK,IAAI,GAAG,CAAC,WAAI,SAAS,GAAG,CAAC,YAAM,OAAS,CAAC,CAAC;YAC1F,CAAC;YAAC,IAAI,CAAC,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,cAAY,OAAS,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAClB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,uCAAW,GAAnB;QAAA,iBA6CC;QA5CC,IAAI,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;QAE1C,cAAc,GAAG,KAAK,CAAC;QACvB,IAAI,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,UAAC,gBAAgB,EAAE,WAAW;YACrE,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;YAC/C,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,KAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QACrF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAC5B,yDAAyD;YACzD,cAAc,GAAG,IAAI,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,UAAC,gBAAgB,EAAE,WAAW;gBACpD,EAAE,CAAC,CAAC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;oBACxC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;oBAC/C,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC,CAAC,CAAC;YACH,cAAc,GAAG,KAAK,CAAC;QACzB,CAAC;QAED,EAAE,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;YAC3B,IAAI,cAAc,GAAG,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;YACtF,IAAI,eAAa,GAAG,EAAE,CAAC;YAEvB,cAAc,CAAC,OAAO,CAAC,UAAA,UAAU;gBAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;gBACb,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;oBACpB,IAAA,oEAAuF,EAAlF,cAAI,EAAE,wBAAS,CAAoE;oBACxF,GAAG,GAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,WAAK,IAAI,GAAG,CAAC,YAAK,SAAS,GAAG,CAAC,SAAK,CAAA;gBACvE,CAAC;gBACD,IAAI,OAAO,GAAG,EAAE,CAAC,4BAA4B,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;gBAC5E,eAAa,CAAC,IAAI,CAAC,OAAK,GAAG,GAAG,OAAS,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,CAAC,eAAa,CAAC,MAAM,CAAC,CAAC,CAAC;gBACzB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;gBAC9B,IAAI,KAAK,GACL,IAAI,KAAK,CAAC,0CAA0C,GAAG,eAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrF,KAAK,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;gBAC3B,MAAM,KAAK,CAAC;YACd,CAAC;YAAC,IAAI,CAAC,CAAC;gBACN,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;OASG;IACK,+CAAmB,GAA3B,UAA4B,OAAe;QACzC,IAAI,CAAC;YACH,IAAM,MAAM,GAAG,oDAAoD,CAAC;YACpE,IAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACtC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;gBAAC,MAAM,CAAC,OAAO,CAAC;YAEhC,IAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YACzD,IAAM,YAAY,GACd,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACpF,IAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5C,IAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACrC,UAAU,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACvE,MAAM,CAAC,KAAG,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAG,CAAC;QAC/E,CAAE;QAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACX,MAAM,CAAC,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAEO,2CAAe,GAAvB,UAAwB,UAAkB;QACxC,IAAI,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QACvF,IAAI,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;QAC3F,IAAI,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAE1F,EAAE,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACtC,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YAClC,EAAE,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;gBACvC,8CAA8C;gBAC9C,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;YACrC,CAAC;YACD,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAhOM,mCAAiB,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5B,mCAAiB,GAAG,CAAC,OAAO,CAAC,CAAC;IAgOvC,wBAAC;AAAD,CAAC,AA7OD,IA6OC;AAGD;IAKE,mCAAoB,eAAmC,EAAU,SAAmB,EAChE,YAA0B,EAAU,aAAqB;QADzD,oBAAe,GAAf,eAAe,CAAoB;QAAU,cAAS,GAAT,SAAS,CAAU;QAChE,iBAAY,GAAZ,YAAY,CAAc;QAAU,kBAAa,GAAb,aAAa,CAAQ;QAC3E,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QACtC,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC,qBAAqB,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC1F,CAAC;IAGD,sDAAkB,GAAlB,cAAiC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAGzD,oDAAgB,GAAhB,UAAiB,QAAgB;QAC/B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACvF,CAAC;IAGD;;;;;;;;;;;;;;;;OAgBG;IACH,qDAAiB,GAAjB,UAAkB,UAAkB;QAClC,IAAI,kBAAkB,CAAC;QAEvB,EAAE,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACzE,kBAAkB,GAAG,UAAU,CAAC;QAClC,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,gBAAgB,KAAK,cAA8B;YACxE,UAAU,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YAC7C,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACrC,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QAChE,CAAC;QAAC,IAAI,CAAC,CAAC;YACN,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QACjE,CAAC;QAGD,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACvC,4FAA4F;YAC5F,+DAA+D;YAC/D,MAAM,CAAC,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,CAAC,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;IACpF,CAAC;IAGD,uDAAmB,GAAnB,cAAgC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAE/D,0DAAsB,GAAtB,cAA+C,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;IAE7E,yDAAqB,GAArB,UAAsB,OAA2B;QAC/C,uFAAuF;QACvF,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC;IACjC,CAAC;IACH,gCAAC;AAAD,CAAC,AArED,IAqEC;AAED;qBAAe,2CAAiB,CAAC,iBAAiB,CAAC,CAAC;AAEpD,eAAkB,MAAS;IACzB,IAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,GAAG,CAAC,CAAC,IAAM,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC;QACxB,MAAM,CAAC,EAAE,CAAC,GAAS,MAAO,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IACD,MAAM,CAAI,MAAM,CAAC;AACnB,CAAC;AAED,kBAAkB,GAAW,EAAE,SAAiB;IAC9C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACtE,CAAC"} \ No newline at end of file diff --git a/lib/broccoli/diffing-broccoli-plugin.js b/lib/broccoli/diffing-broccoli-plugin.js deleted file mode 100644 index d8e2be13766c..000000000000 --- a/lib/broccoli/diffing-broccoli-plugin.js +++ /dev/null @@ -1,160 +0,0 @@ -/// -/// -/// -"use strict"; -var fs = require('fs'); -var tree_differ_1 = require('./tree-differ'); -var broccoli_tree_stabilizer_1 = require('./broccoli-tree-stabilizer'); -var symlinkOrCopy = require('symlink-or-copy'); -var tree_differ_2 = require('./tree-differ'); -exports.DiffResult = tree_differ_2.DiffResult; -/** - * Makes writing diffing plugins easy. - * - * Factory method that takes a class that implements the DiffingBroccoliPlugin interface and returns - * an instance of BroccoliTree. - * - * @param pluginClass - * @returns {DiffingBroccoliPlugin} - */ -function wrapDiffingPlugin(pluginClass) { - return function () { return new DiffingPluginWrapper(pluginClass, arguments); }; -} -exports.wrapDiffingPlugin = wrapDiffingPlugin; -var DiffingPluginWrapper = (function () { - function DiffingPluginWrapper(pluginClass, wrappedPluginArguments) { - this.pluginClass = pluginClass; - this.wrappedPluginArguments = wrappedPluginArguments; - this.treeDiffer = null; - this.treeDiffers = null; - this.initialized = false; - this.wrappedPlugin = null; - this.inputTree = null; - this.inputTrees = null; - this.description = null; - // props monkey-patched by broccoli builder: - this.inputPath = null; - this.inputPaths = null; - this.cachePath = null; - this.outputPath = null; - this.diffResult = null; - if (Array.isArray(wrappedPluginArguments[0])) { - this.inputTrees = this.stabilizeTrees(wrappedPluginArguments[0]); - } - else { - this.inputTree = this.stabilizeTree(wrappedPluginArguments[0]); - } - this.description = this.pluginClass.name; - } - DiffingPluginWrapper.prototype.getDiffResult = function () { - var _this = this; - var returnOrCalculateDiffResult = function (tree, index) { - // returnOrCalculateDiffResult will do one of two things: - // - // If `this.diffResult` is null, calculate a DiffResult using TreeDiffer - // for the input tree. - // - // Otherwise, `this.diffResult` was produced from the output of the - // inputTree's rebuild() method, and can be used without being checked. - // Set `this.diffResult` to null and return the previously stored value. - var diffResult = tree.diffResult; - if (diffResult) - return diffResult; - var differ = index === false ? _this.treeDiffer : _this.treeDiffers[index]; - return differ.diffTree(); - }; - if (this.inputTrees) { - return this.inputTrees.map(returnOrCalculateDiffResult); - } - else if (this.inputTree) { - return returnOrCalculateDiffResult(this.inputTree, false); - } - else { - throw new Error("Missing TreeDiffer"); - } - }; - DiffingPluginWrapper.prototype.maybeStoreDiffResult = function (value) { - if (!(value instanceof tree_differ_1.DiffResult)) - value = null; - this.diffResult = (value); - }; - DiffingPluginWrapper.prototype.rebuild = function () { - var _this = this; - try { - var firstRun = !this.initialized; - this.init(); - var diffResult = this.getDiffResult(); - var result = this.wrappedPlugin.rebuild(diffResult); - if (result) { - var resultPromise = (result); - if (resultPromise.then) { - // rebuild() -> Promise<> - return resultPromise.then(function (result) { - _this.maybeStoreDiffResult(result); - _this.relinkOutputAndCachePaths(); - }); - } - } - this.maybeStoreDiffResult((result)); - this.relinkOutputAndCachePaths(); - } - catch (e) { - e.message = "[" + this.description + "]: " + e.message; - throw e; - } - }; - DiffingPluginWrapper.prototype.cleanup = function () { - if (this.wrappedPlugin && this.wrappedPlugin.cleanup) { - this.wrappedPlugin.cleanup(); - } - }; - DiffingPluginWrapper.prototype.relinkOutputAndCachePaths = function () { - // just symlink the cache and output tree - fs.rmdirSync(this.outputPath); - symlinkOrCopy.sync(this.cachePath, this.outputPath); - }; - DiffingPluginWrapper.prototype.init = function () { - if (!this.initialized) { - var includeExtensions_1 = this.pluginClass.includeExtensions || []; - var excludeExtensions_1 = this.pluginClass.excludeExtensions || []; - var description_1 = this.description; - this.initialized = true; - if (this.inputPaths) { - this.treeDiffers = - this.inputPaths.map(function (inputPath) { return new tree_differ_1.TreeDiffer(description_1, inputPath, includeExtensions_1, excludeExtensions_1); }); - } - else if (this.inputPath) { - this.treeDiffer = - new tree_differ_1.TreeDiffer(description_1, this.inputPath, includeExtensions_1, excludeExtensions_1); - } - this.wrappedPlugin = new this.pluginClass(this.inputPaths || this.inputPath, this.cachePath, this.wrappedPluginArguments[1]); - } - }; - DiffingPluginWrapper.prototype.stabilizeTrees = function (trees) { - // Prevent extensions to prevent array from being mutated from the outside. - // For-loop used to avoid re-allocating a new array. - var stableTrees = []; - for (var i = 0; i < trees.length; ++i) { - // ignore null/undefined input tries in order to support conditional build pipelines - if (trees[i]) { - stableTrees.push(this.stabilizeTree(trees[i])); - } - } - if (stableTrees.length === 0) { - throw new Error('No input trees provided!'); - } - return Object.freeze(stableTrees); - }; - DiffingPluginWrapper.prototype.stabilizeTree = function (tree) { - // Ignore all DiffingPlugins as they are already stable, for others we don't know for sure - // so we need to stabilize them. - // Since it's not safe to use instanceof operator in node, we are checking the constructor.name. - // - // New-style/rebuild trees should always be stable. - var isNewStyleTree = !!(tree['newStyleTree'] || typeof tree.rebuild === 'function' || - tree['isReadAPICompatTree'] || tree.constructor['name'] === 'Funnel'); - return isNewStyleTree ? tree : broccoli_tree_stabilizer_1["default"](tree); - }; - return DiffingPluginWrapper; -}()); -//# sourceMappingURL=diffing-broccoli-plugin.js.map \ No newline at end of file diff --git a/lib/broccoli/diffing-broccoli-plugin.js.map b/lib/broccoli/diffing-broccoli-plugin.js.map deleted file mode 100644 index 40120655ee67..000000000000 --- a/lib/broccoli/diffing-broccoli-plugin.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"diffing-broccoli-plugin.js","sourceRoot":"","sources":["diffing-broccoli-plugin.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,0DAA0D;AAC1D,kDAAkD;;AAElD,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAG1B,4BAAqC,eAAe,CAAC,CAAA;AACrD,yCAA0B,4BAA4B,CAAC,CAAA;AACvD,IAAI,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAG/C,4BAAyB,eAAe,CAAC;AAAjC,8CAAiC;AAGzC;;;;;;;;GAQG;AACH,2BAAkC,WAAW;IAC3C,MAAM,CAAC,cAAa,MAAM,CAAC,IAAI,oBAAoB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACjF,CAAC;AAFe,yBAAiB,oBAEhC,CAAA;AAaD;IAiBE,8BAAoB,WAAW,EAAU,sBAAsB;QAA3C,gBAAW,GAAX,WAAW,CAAA;QAAU,2BAAsB,GAAtB,sBAAsB,CAAA;QAhB/D,eAAU,GAAe,IAAI,CAAC;QAC9B,gBAAW,GAAiB,IAAI,CAAC;QACjC,gBAAW,GAAG,KAAK,CAAC;QACpB,kBAAa,GAA0B,IAAI,CAAC;QAC5C,cAAS,GAAG,IAAI,CAAC;QACjB,eAAU,GAAG,IAAI,CAAC;QAClB,gBAAW,GAAG,IAAI,CAAC;QAEnB,4CAA4C;QAC5C,cAAS,GAAG,IAAI,CAAC;QACjB,eAAU,GAAG,IAAI,CAAC;QAClB,cAAS,GAAG,IAAI,CAAC;QACjB,eAAU,GAAG,IAAI,CAAC;QAEV,eAAU,GAAe,IAAI,CAAC;QAGpC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,CAAC;QAAC,IAAI,CAAC,CAAC;YACN,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IAC3C,CAAC;IAEO,4CAAa,GAArB;QAAA,iBAuBC;QAtBC,IAAI,2BAA2B,GAAG,UAAC,IAAI,EAAE,KAAK;YAC5C,yDAAyD;YACzD,EAAE;YACF,wEAAwE;YACxE,sBAAsB;YACtB,EAAE;YACF,mEAAmE;YACnE,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YACjC,EAAE,CAAC,CAAC,UAAU,CAAC;gBAAC,MAAM,CAAC,UAAU,CAAC;YAClC,IAAI,MAAM,GAAG,KAAK,KAAK,KAAK,GAAG,KAAI,CAAC,UAAU,GAAG,KAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACzE,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC3B,CAAC,CAAC;QAEF,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QAC1D,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YAC1B,MAAM,CAAC,2BAA2B,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC5D,CAAC;QAAC,IAAI,CAAC,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAEO,mDAAoB,GAA5B,UAA6B,KAA0B;QACrD,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,YAAY,wBAAU,CAAC,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC;QACjD,IAAI,CAAC,UAAU,GAAe,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,sCAAO,GAAP;QAAA,iBA0BC;QAzBC,IAAI,CAAC;YACH,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,CAAC;YAEZ,IAAI,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAEtC,IAAI,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAEpD,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;gBACX,IAAI,aAAa,GAA+B,CAAC,MAAM,CAAC,CAAC;gBACzD,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;oBACvB,yBAAyB;oBACzB,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,UAAC,MAA2B;wBACpD,KAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;wBAClC,KAAI,CAAC,yBAAyB,EAAE,CAAC;oBACnC,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,IAAI,CAAC,oBAAoB,CAAsB,CAAC,MAAM,CAAC,CAAC,CAAC;YACzD,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACnC,CAAE;QAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACX,CAAC,CAAC,OAAO,GAAG,MAAI,IAAI,CAAC,WAAW,WAAM,CAAC,CAAC,OAAS,CAAC;YAClD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAGD,sCAAO,GAAP;QACE,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;YACrD,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAGO,wDAAyB,GAAjC;QACE,yCAAyC;QACzC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC;IAGO,mCAAI,GAAZ;QACE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;YACtB,IAAI,mBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,iBAAiB,IAAI,EAAE,CAAC;YACjE,IAAI,mBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,iBAAiB,IAAI,EAAE,CAAC;YACjE,IAAI,aAAW,GAAG,IAAI,CAAC,WAAW,CAAC;YACnC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,WAAW;oBACZ,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAC,SAAS,IAAK,OAAA,IAAI,wBAAU,CACzB,aAAW,EAAE,SAAS,EAAE,mBAAiB,EAAE,mBAAiB,CAAC,EADlD,CACkD,CAAC,CAAC;YAC7F,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC1B,IAAI,CAAC,UAAU;oBACX,IAAI,wBAAU,CAAC,aAAW,EAAE,IAAI,CAAC,SAAS,EAAE,mBAAiB,EAAE,mBAAiB,CAAC,CAAC;YACxF,CAAC;YACD,IAAI,CAAC,aAAa,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EACjD,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAGO,6CAAc,GAAtB,UAAuB,KAAqB;QAC1C,2EAA2E;QAC3E,oDAAoD;QACpD,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YACtC,oFAAoF;YACpF,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACb,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,EAAE,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpC,CAAC;IAGO,4CAAa,GAArB,UAAsB,IAAkB;QACtC,0FAA0F;QAC1F,gCAAgC;QAChC,gGAAgG;QAChG,EAAE;QACF,mDAAmD;QACnD,IAAI,cAAc,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU;YAC1D,IAAI,CAAC,qBAAqB,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC;QAE9F,MAAM,CAAC,cAAc,GAAG,IAAI,GAAG,qCAAa,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IACH,2BAAC;AAAD,CAAC,AAtJD,IAsJC"} \ No newline at end of file diff --git a/lib/broccoli/tree-differ.js b/lib/broccoli/tree-differ.js deleted file mode 100644 index a79c825b3d2e..000000000000 --- a/lib/broccoli/tree-differ.js +++ /dev/null @@ -1,162 +0,0 @@ -/// -"use strict"; -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 __()); -}; -var fs = require('fs'); -var path = require('path'); -function tryStatSync(path) { - try { - return fs.statSync(path); - } - catch (e) { - if (e.code === "ENOENT") - return null; - throw e; - } -} -var TreeDiffer = (function () { - function TreeDiffer(label, rootPath, includeExtensions, excludeExtensions) { - this.label = label; - this.rootPath = rootPath; - this.fingerprints = Object.create(null); - this.nextFingerprints = Object.create(null); - this.include = null; - this.exclude = null; - this.rootDirName = path.basename(rootPath); - var buildRegexp = function (arr) { return new RegExp("(" + arr.reduce(combine, "") + ")$", "i"); }; - this.include = (includeExtensions || []).length ? buildRegexp(includeExtensions) : null; - this.exclude = (excludeExtensions || []).length ? buildRegexp(excludeExtensions) : null; - function combine(prev, curr) { - if (curr.charAt(0) !== ".") { - throw new Error("Extension must begin with '.'. Was: '" + curr + "'"); - } - var kSpecialRegexpChars = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g; - curr = '(' + curr.replace(kSpecialRegexpChars, '\\$&') + ')'; - return prev ? (prev + '|' + curr) : curr; - } - } - TreeDiffer.prototype.diffTree = function () { - var result = new DirtyCheckingDiffResult(this.label, this.rootDirName); - this.dirtyCheckPath(this.rootPath, result); - this.detectDeletionsAndUpdateFingerprints(result); - result.endTime = Date.now(); - return result; - }; - TreeDiffer.prototype.dirtyCheckPath = function (rootDir, result) { - var _this = this; - fs.readdirSync(rootDir).forEach(function (segment) { - var absolutePath = path.join(rootDir, segment); - var pathStat = fs.lstatSync(absolutePath); - if (pathStat.isSymbolicLink()) { - pathStat = tryStatSync(absolutePath); - if (pathStat === null) - return; - } - if (pathStat.isDirectory()) { - result.directoriesChecked++; - _this.dirtyCheckPath(absolutePath, result); - } - else { - if (!(_this.include && !absolutePath.match(_this.include)) && - !(_this.exclude && absolutePath.match(_this.exclude))) { - result.filesChecked++; - var relativeFilePath = path.relative(_this.rootPath, absolutePath); - switch (_this.isFileDirty(absolutePath, pathStat)) { - case FileStatus.Added: - result.addedPaths.push(relativeFilePath); - break; - case FileStatus.Changed: - result.changedPaths.push(relativeFilePath); - } - } - } - }); - return result; - }; - TreeDiffer.prototype.isFileDirty = function (path, stat) { - var oldFingerprint = this.fingerprints[path]; - var newFingerprint = stat.mtime.getTime() + " # " + stat.size; - this.nextFingerprints[path] = newFingerprint; - if (oldFingerprint) { - this.fingerprints[path] = null; - if (oldFingerprint === newFingerprint) { - // nothing changed - return FileStatus.Unchanged; - } - return FileStatus.Changed; - } - return FileStatus.Added; - }; - TreeDiffer.prototype.detectDeletionsAndUpdateFingerprints = function (result) { - for (var absolutePath in this.fingerprints) { - if (!(this.include && !absolutePath.match(this.include)) && - !(this.exclude && absolutePath.match(this.exclude))) { - if (this.fingerprints[absolutePath] !== null) { - var relativePath = path.relative(this.rootPath, absolutePath); - result.removedPaths.push(relativePath); - } - } - } - this.fingerprints = this.nextFingerprints; - this.nextFingerprints = Object.create(null); - }; - return TreeDiffer; -}()); -exports.TreeDiffer = TreeDiffer; -var DiffResult = (function () { - function DiffResult(label) { - if (label === void 0) { label = ''; } - this.label = label; - this.addedPaths = []; - this.changedPaths = []; - this.removedPaths = []; - } - DiffResult.prototype.log = function (verbose) { }; - DiffResult.prototype.toString = function () { - // TODO(@caitp): more meaningful logging - return ''; - }; - return DiffResult; -}()); -exports.DiffResult = DiffResult; -var DirtyCheckingDiffResult = (function (_super) { - __extends(DirtyCheckingDiffResult, _super); - function DirtyCheckingDiffResult(label, directoryName) { - _super.call(this, label); - this.directoryName = directoryName; - this.filesChecked = 0; - this.directoriesChecked = 0; - this.startTime = Date.now(); - this.endTime = null; - } - DirtyCheckingDiffResult.prototype.toString = function () { - return (pad(this.label, 30) + ", " + pad(this.endTime - this.startTime, 5) + "ms, ") + - (pad(this.addedPaths.length + this.changedPaths.length + this.removedPaths.length, 5) + " changes ") + - ("(files: " + pad(this.filesChecked, 5) + ", dirs: " + pad(this.directoriesChecked, 4) + ")"); - }; - DirtyCheckingDiffResult.prototype.log = function (verbose) { - var prefixedPaths = this.addedPaths.map(function (p) { return ("+ " + p); }) - .concat(this.changedPaths.map(function (p) { return ("* " + p); })) - .concat(this.removedPaths.map(function (p) { return ("- " + p); })); - console.log(("Tree diff: " + this) + ((verbose && prefixedPaths.length) ? - " [\n " + prefixedPaths.join('\n ') + "\n]" : - '')); - }; - return DirtyCheckingDiffResult; -}(DiffResult)); -function pad(value, length) { - value = '' + value; - var whitespaceLength = (value.length < length) ? length - value.length : 0; - whitespaceLength = whitespaceLength + 1; - return new Array(whitespaceLength).join(' ') + value; -} -var FileStatus; -(function (FileStatus) { - FileStatus[FileStatus["Added"] = 0] = "Added"; - FileStatus[FileStatus["Unchanged"] = 1] = "Unchanged"; - FileStatus[FileStatus["Changed"] = 2] = "Changed"; -})(FileStatus || (FileStatus = {})); -//# sourceMappingURL=tree-differ.js.map \ No newline at end of file diff --git a/lib/broccoli/tree-differ.js.map b/lib/broccoli/tree-differ.js.map deleted file mode 100644 index c24a31cdbb65..000000000000 --- a/lib/broccoli/tree-differ.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"tree-differ.js","sourceRoot":"","sources":["tree-differ.ts"],"names":[],"mappings":"AAAA,kDAAkD;;;;;;;AAElD,IAAO,EAAE,WAAW,IAAI,CAAC,CAAC;AAC1B,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAG9B,qBAAqB,IAAI;IACvB,IAAI,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAE;IAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACX,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC;QACrC,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAGD;IAOE,oBAAoB,KAAa,EAAU,QAAgB,EAAE,iBAA4B,EAC7E,iBAA4B;QADpB,UAAK,GAAL,KAAK,CAAQ;QAAU,aAAQ,GAAR,QAAQ,CAAQ;QANnD,iBAAY,GAA4B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5D,qBAAgB,GAA4B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAEhE,YAAO,GAAW,IAAI,CAAC;QACvB,YAAO,GAAW,IAAI,CAAC;QAI7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE3C,IAAI,WAAW,GAAG,UAAC,GAAG,IAAK,OAAA,IAAI,MAAM,CAAC,MAAI,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,OAAI,EAAE,GAAG,CAAC,EAAhD,CAAgD,CAAC;QAE5E,IAAI,CAAC,OAAO,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;QACxF,IAAI,CAAC,OAAO,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;QAExF,iBAAiB,IAAI,EAAE,IAAI;YACzB,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,0CAAwC,IAAI,MAAG,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,mBAAmB,GAAG,qCAAqC,CAAC;YAChE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC;YAC7D,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;QAC3C,CAAC;IACH,CAAC;IAGM,6BAAQ,GAAf;QACE,IAAI,MAAM,GAAG,IAAI,uBAAuB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,oCAAoC,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC;IAChB,CAAC;IAGO,mCAAc,GAAtB,UAAuB,OAAe,EAAE,MAA+B;QAAvE,iBA8BC;QA7BC,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,UAAC,OAAO;YACtC,IAAI,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,QAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YAC1C,EAAE,CAAC,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;gBAC9B,QAAQ,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;gBACrC,EAAE,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC;oBAAC,MAAM,CAAC;YAChC,CAAC;YAED,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;gBAC3B,MAAM,CAAC,kBAAkB,EAAE,CAAC;gBAC5B,KAAI,CAAC,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;YAAC,IAAI,CAAC,CAAC;gBACN,EAAE,CAAC,CAAC,CAAC,CAAC,KAAI,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAI,CAAC,OAAO,CAAC,CAAC;oBACpD,CAAC,CAAC,KAAI,CAAC,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,KAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;oBACxD,MAAM,CAAC,YAAY,EAAE,CAAC;oBACtB,IAAI,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;oBAElE,MAAM,CAAC,CAAC,KAAI,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;wBACjD,KAAK,UAAU,CAAC,KAAK;4BACnB,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;4BACzC,KAAK,CAAC;wBACR,KAAK,UAAU,CAAC,OAAO;4BACrB,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;oBAC/C,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC;IAChB,CAAC;IAGO,gCAAW,GAAnB,UAAoB,IAAY,EAAE,IAAc;QAC9C,IAAI,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,cAAc,GAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,WAAM,IAAI,CAAC,IAAM,CAAC;QAE9D,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC;QAE7C,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;YACnB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;YAE/B,EAAE,CAAC,CAAC,cAAc,KAAK,cAAc,CAAC,CAAC,CAAC;gBACtC,kBAAkB;gBAClB,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;YAC9B,CAAC;YAED,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;QAC5B,CAAC;QAED,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC;IAC1B,CAAC;IAGO,yDAAoC,GAA5C,UAA6C,MAAkB;QAC7D,GAAG,CAAC,CAAC,IAAI,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;YAC3C,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpD,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxD,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;oBAC7C,IAAI,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;oBAC9D,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAC1C,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IACH,iBAAC;AAAD,CAAC,AAxGD,IAwGC;AAxGY,kBAAU,aAwGtB,CAAA;AAGD;IAKE,oBAAmB,KAAkB;QAAzB,qBAAyB,GAAzB,UAAyB;QAAlB,UAAK,GAAL,KAAK,CAAa;QAJ9B,eAAU,GAAa,EAAE,CAAC;QAC1B,iBAAY,GAAa,EAAE,CAAC;QAC5B,iBAAY,GAAa,EAAE,CAAC;IAEK,CAAC;IAEzC,wBAAG,GAAH,UAAI,OAAgB,IAAS,CAAC;IAE9B,6BAAQ,GAAR;QACE,wCAAwC;QACxC,MAAM,CAAC,EAAE,CAAC;IACZ,CAAC;IACH,iBAAC;AAAD,CAAC,AAbD,IAaC;AAbY,kBAAU,aAatB,CAAA;AAED;IAAsC,2CAAU;IAM9C,iCAAY,KAAa,EAAS,aAAqB;QAAI,kBAAM,KAAK,CAAC,CAAC;QAAtC,kBAAa,GAAb,aAAa,CAAQ;QALhD,iBAAY,GAAW,CAAC,CAAC;QACzB,uBAAkB,GAAW,CAAC,CAAC;QAC/B,cAAS,GAAW,IAAI,CAAC,GAAG,EAAE,CAAC;QAC/B,YAAO,GAAW,IAAI,CAAC;IAE2C,CAAC;IAE1E,0CAAQ,GAAR;QACE,MAAM,CAAC,CAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,UAAK,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,UAAM;YACtE,CAAG,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,eAAW;YAClG,cAAW,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,gBAAW,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,OAAG,CAAC;IAC3F,CAAC;IAED,qCAAG,GAAH,UAAI,OAAO;QACT,IAAI,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAA,CAAC,IAAI,OAAA,QAAK,CAAC,CAAE,EAAR,CAAQ,CAAC;aAC7B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAA,CAAC,IAAI,OAAA,QAAK,CAAC,CAAE,EAAR,CAAQ,CAAC,CAAC;aAC5C,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAA,CAAC,IAAI,OAAA,QAAK,CAAC,CAAE,EAAR,CAAQ,CAAC,CAAC,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,iBAAc,IAAI,CAAE,GAAG,CAAC,CAAC,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC;YAC7B,WAAS,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,QAAK;YACxC,EAAE,CAAC,CAAC,CAAC;IAC/C,CAAC;IACH,8BAAC;AAAD,CAAC,AAtBD,CAAsC,UAAU,GAsB/C;AAGD,aAAa,KAAK,EAAE,MAAM;IACxB,KAAK,GAAG,EAAE,GAAG,KAAK,CAAC;IACnB,IAAI,gBAAgB,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3E,gBAAgB,GAAG,gBAAgB,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AACvD,CAAC;AAGD,IAAK,UAIJ;AAJD,WAAK,UAAU;IACb,6CAAK,CAAA;IACL,qDAAS,CAAA;IACT,iDAAO,CAAA;AACT,CAAC,EAJI,UAAU,KAAV,UAAU,QAId"} \ No newline at end of file diff --git a/lib/broccoli/ts-config-converter.js b/lib/broccoli/ts-config-converter.js deleted file mode 100644 index acb4568e654b..000000000000 --- a/lib/broccoli/ts-config-converter.js +++ /dev/null @@ -1,201 +0,0 @@ -var ts = require('typescript'); -/** - * Converts external tsconfig.json config to it's internal representation suitable for passing into - * TypeScript language services. - * - * @param config - * @returns {ts.CompilerOptions} - */ -function convertTsConfig(config) { - return Object.keys(config).reduce(function (internalConfig, configKey) { - var tsOption = tsOptionsMap[configKey]; - var configValue = config[configKey]; - switch (tsOption.type) { - case 'string': - case 'boolean': - // keep value as is - internalConfig[configKey] = configValue; - break; - default: - if (tsOption.type instanceof Object) { - // convert external value to internal value - internalConfig[configKey] = tsOption.type[configValue]; - } - else { - throw new Error('Unknown tsc option type: ' + tsOption.type); - } - } - return internalConfig; - }, {}); -} -exports.convertTsConfig = convertTsConfig; -// copied and customized from: -// https://github.com/Microsoft/TypeScript/blob/302db0a9d58fe6b108c4ec455883fa7a3c4fd991/src/compiler/commandLineParser.ts#L8 -var tscOptions = [ - { - name: "charset", - type: "string", - }, - { - name: "declaration", - type: "boolean", - }, - { - name: "diagnostics", - type: "boolean", - }, - { name: "emitBOM", type: "boolean" }, - { - name: "help", - type: "boolean", - }, - { - name: "init", - type: "boolean", - }, - { - name: "inlineSourceMap", - type: "boolean", - }, - { - name: "inlineSources", - type: "boolean", - }, - { - name: "jsx", - type: { "preserve": 1 /* Preserve */, "react": 2 /* React */ }, - }, - { - name: "listFiles", - type: "boolean", - }, - { - name: "locale", - type: "string", - }, - { - name: "mapRoot", - type: "string", - }, - { - name: "module", - type: { - "commonjs": 1 /* CommonJS */, - "amd": 2 /* AMD */, - "system": 4 /* System */, - "umd": 3 /* UMD */, - }, - }, - { - name: "newLine", - type: { "crlf": 0 /* CarriageReturnLineFeed */, "lf": 1 /* LineFeed */ }, - }, - { - name: "noEmit", - type: "boolean", - }, - { name: "noEmitHelpers", type: "boolean" }, - { - name: "noEmitOnError", - type: "boolean", - }, - { - name: "noImplicitAny", - type: "boolean", - }, - { - name: "noLib", - type: "boolean", - }, - { - name: "noResolve", - type: "boolean", - }, - { - name: "skipDefaultLibCheck", - type: "boolean", - }, - { - name: "out", - type: "string", - }, - { - name: "outFile", - type: "string", - }, - { - name: "outDir", - type: "string", - }, - { - name: "preserveConstEnums", - type: "boolean", - }, - { - name: "project", - type: "string", - }, - { - name: "removeComments", - type: "boolean", - }, - { - name: "rootDir", - type: "string", - }, - { - name: "isolatedModules", - type: "boolean", - }, - { - name: "sourceMap", - type: "boolean", - }, - { - name: "sourceRoot", - type: "string", - }, - { name: "suppressExcessPropertyErrors", type: "boolean" }, - { - name: "suppressImplicitAnyIndexErrors", - type: "boolean", - }, - { name: "stripInternal", type: "boolean" }, - { - name: "target", - type: { - "es3": 0 /* ES3 */, - "es5": 1 /* ES5 */, - "es6": 2 /* ES6 */, - }, - }, - { - name: "version", - type: "boolean", - }, - { - name: "watch", - type: "boolean", - }, - { - name: "experimentalDecorators", - type: "boolean", - }, - { - name: "emitDecoratorMetadata", - type: "boolean", - }, - { - name: "moduleResolution", - type: { "node": 2 /* NodeJs */, "classic": 1 /* Classic */ }, - } -]; -/** - * Creates a map out of the `tscOptions` array - */ -var tsOptionsMap = tscOptions.reduce(function (map, option) { - map[option.name] = option; - return map; -}, {}); - -//# sourceMappingURL=../broccoli/ts-config-converter.js.map \ No newline at end of file diff --git a/lib/broccoli/ts-config-converter.js.map b/lib/broccoli/ts-config-converter.js.map deleted file mode 100644 index 3a5b0f1598a1..000000000000 --- a/lib/broccoli/ts-config-converter.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["broccoli/ts-config-converter.ts"],"names":["convertTsConfig"],"mappings":"AAAA,IAAY,EAAE,WAAM,YAAY,CAAC,CAAA;AAEjC;;;;;;GAMG;AACH,yBAAgC,MAAM;IACpCA,MAAMA,CAAqBA,MAAMA,CAACA,IAAIA,CAACA,MAAMA,CAACA,CAACA,MAAMA,CAACA,UAACA,cAAcA,EAAEA,SAASA;QAC9EA,IAAIA,QAAQA,GAAGA,YAAYA,CAACA,SAASA,CAACA,CAACA;QACvCA,IAAIA,WAAWA,GAAGA,MAAMA,CAACA,SAASA,CAACA,CAACA;QAEpCA,MAAMA,CAACA,CAACA,QAAQA,CAACA,IAAIA,CAACA,CAACA,CAACA;YACtBA,KAAKA,QAAQA,CAACA;YACdA,KAAKA,SAASA;gBACZA,mBAAmBA;gBACnBA,cAAcA,CAACA,SAASA,CAACA,GAAGA,WAAWA,CAACA;gBACxCA,KAAKA,CAACA;YACRA;gBACEA,EAAEA,CAACA,CAACA,QAAQA,CAACA,IAAIA,YAAYA,MAAMA,CAACA,CAACA,CAACA;oBACpCA,2CAA2CA;oBAC3CA,cAAcA,CAACA,SAASA,CAACA,GAAGA,QAAQA,CAACA,IAAIA,CAACA,WAAWA,CAACA,CAACA;gBACzDA,CAACA;gBAACA,IAAIA,CAACA,CAACA;oBACNA,MAAMA,IAAIA,KAAKA,CAACA,2BAA2BA,GAAGA,QAAQA,CAACA,IAAIA,CAACA,CAACA;gBAC/DA,CAACA;QACLA,CAACA;QAEDA,MAAMA,CAACA,cAAcA,CAACA;IACxBA,CAACA,EAAEA,EAAEA,CAACA,CAACA;AACTA,CAACA;AAtBe,uBAAe,kBAsB9B,CAAA;AAGD,8BAA8B;AAC9B,6HAA6H;AAC7H,IAAI,UAAU,GAAG;IACf;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,SAAS;KAChB;IACD,EAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAC;IAClC;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,EAAC,UAAU,EAAE,gBAAmB,EAAE,OAAO,EAAE,aAAgB,EAAC;KACnE;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE;YACJ,UAAU,EAAE,gBAAsB;YAClC,KAAK,EAAE,WAAiB;YACxB,QAAQ,EAAE,cAAoB;YAC9B,KAAK,EAAE,WAAiB;SACzB;KACF;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,EAAC,MAAM,EAAE,8BAAqC,EAAE,IAAI,EAAE,gBAAuB,EAAC;KACrF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,SAAS;KAChB;IACD,EAAC,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,EAAC;IACxC;QACE,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;KACf;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,QAAQ;KACf;IACD,EAAC,IAAI,EAAE,8BAA8B,EAAE,IAAI,EAAE,SAAS,EAAC;IACvD;QACE,IAAI,EAAE,gCAAgC;QACtC,IAAI,EAAE,SAAS;KAChB;IACD,EAAC,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,EAAC;IACxC;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE;YACJ,KAAK,EAAE,WAAmB;YAC1B,KAAK,EAAE,WAAmB;YAC1B,KAAK,EAAE,WAAmB;SAC3B;KACF;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,wBAAwB;QAC9B,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,IAAI,EAAE,SAAS;KAChB;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,EAAC,MAAM,EAAE,cAA8B,EAAE,SAAS,EAAE,eAA+B,EAAC;KAC3F;CACF,CAAC;AAGF;;GAEG;AACH,IAAI,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,UAAC,GAAG,EAAE,MAAM;IAC/C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,GAAG,CAAC;AACb,CAAC,EAAE,EAAE,CAAC,CAAC","file":"broccoli/ts-config-converter.js","sourcesContent":["import * as ts from 'typescript';\n\n/**\n * Converts external tsconfig.json config to it's internal representation suitable for passing into\n * TypeScript language services.\n *\n * @param config\n * @returns {ts.CompilerOptions}\n */\nexport function convertTsConfig(config): ts.CompilerOptions {\n return Object.keys(config).reduce((internalConfig, configKey) => {\n let tsOption = tsOptionsMap[configKey];\n let configValue = config[configKey];\n\n switch (tsOption.type) {\n case 'string':\n case 'boolean':\n // keep value as is\n internalConfig[configKey] = configValue;\n break;\n default:\n if (tsOption.type instanceof Object) {\n // convert external value to internal value\n internalConfig[configKey] = tsOption.type[configValue];\n } else {\n throw new Error('Unknown tsc option type: ' + tsOption.type);\n }\n }\n\n return internalConfig;\n }, {});\n}\n\n\n// copied and customized from:\n// https://github.com/Microsoft/TypeScript/blob/302db0a9d58fe6b108c4ec455883fa7a3c4fd991/src/compiler/commandLineParser.ts#L8\nvar tscOptions = [\n {\n name: \"charset\",\n type: \"string\",\n },\n {\n name: \"declaration\",\n type: \"boolean\",\n },\n {\n name: \"diagnostics\",\n type: \"boolean\",\n },\n {name: \"emitBOM\", type: \"boolean\"},\n {\n name: \"help\",\n type: \"boolean\",\n },\n {\n name: \"init\",\n type: \"boolean\",\n },\n {\n name: \"inlineSourceMap\",\n type: \"boolean\",\n },\n {\n name: \"inlineSources\",\n type: \"boolean\",\n },\n {\n name: \"jsx\",\n type: {\"preserve\": ts.JsxEmit.Preserve, \"react\": ts.JsxEmit.React},\n },\n {\n name: \"listFiles\",\n type: \"boolean\",\n },\n {\n name: \"locale\",\n type: \"string\",\n },\n {\n name: \"mapRoot\",\n type: \"string\",\n },\n {\n name: \"module\",\n type: {\n \"commonjs\": ts.ModuleKind.CommonJS,\n \"amd\": ts.ModuleKind.AMD,\n \"system\": ts.ModuleKind.System,\n \"umd\": ts.ModuleKind.UMD,\n },\n },\n {\n name: \"newLine\",\n type: {\"crlf\": ts.NewLineKind.CarriageReturnLineFeed, \"lf\": ts.NewLineKind.LineFeed},\n },\n {\n name: \"noEmit\",\n type: \"boolean\",\n },\n {name: \"noEmitHelpers\", type: \"boolean\"},\n {\n name: \"noEmitOnError\",\n type: \"boolean\",\n },\n {\n name: \"noImplicitAny\",\n type: \"boolean\",\n },\n {\n name: \"noLib\",\n type: \"boolean\",\n },\n {\n name: \"noResolve\",\n type: \"boolean\",\n },\n {\n name: \"skipDefaultLibCheck\",\n type: \"boolean\",\n },\n {\n name: \"out\",\n type: \"string\",\n },\n {\n name: \"outFile\",\n type: \"string\",\n },\n {\n name: \"outDir\",\n type: \"string\",\n },\n {\n name: \"preserveConstEnums\",\n type: \"boolean\",\n },\n {\n name: \"project\",\n type: \"string\",\n },\n {\n name: \"removeComments\",\n type: \"boolean\",\n },\n {\n name: \"rootDir\",\n type: \"string\",\n },\n {\n name: \"isolatedModules\",\n type: \"boolean\",\n },\n {\n name: \"sourceMap\",\n type: \"boolean\",\n },\n {\n name: \"sourceRoot\",\n type: \"string\",\n },\n {name: \"suppressExcessPropertyErrors\", type: \"boolean\"},\n {\n name: \"suppressImplicitAnyIndexErrors\",\n type: \"boolean\",\n },\n {name: \"stripInternal\", type: \"boolean\"},\n {\n name: \"target\",\n type: {\n \"es3\": ts.ScriptTarget.ES3,\n \"es5\": ts.ScriptTarget.ES5,\n \"es6\": ts.ScriptTarget.ES6,\n },\n },\n {\n name: \"version\",\n type: \"boolean\",\n },\n {\n name: \"watch\",\n type: \"boolean\",\n },\n {\n name: \"experimentalDecorators\",\n type: \"boolean\",\n },\n {\n name: \"emitDecoratorMetadata\",\n type: \"boolean\",\n },\n {\n name: \"moduleResolution\",\n type: {\"node\": ts.ModuleResolutionKind.NodeJs, \"classic\": ts.ModuleResolutionKind.Classic},\n }\n];\n\n\n/**\n * Creates a map out of the `tscOptions` array\n */\nvar tsOptionsMap = tscOptions.reduce((map, option) => {\n map[option.name] = option;\n return map;\n}, {});\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/lib/cli/index.js b/lib/cli/index.js deleted file mode 100644 index 554ff500ceb1..000000000000 --- a/lib/cli/index.js +++ /dev/null @@ -1,32 +0,0 @@ -var cli = require('ember-cli/lib/cli'); -var path = require('path'); -var fs = require('fs'); - -module.exports = function (options) { - process.stdout.write = (function(write) { - return function(string, encoding, fd) { - if (/version:/.test(string) || /warning:/.test(string)) { - return; - } - string = string.replace(/ember-cli/g, 'angular-cli'); - string = string.replace(/ember/g, 'ng'); - write.apply(process.stdout, arguments) - } - })(process.stdout.write); - - process.stderr.write = (function(write) { - return function(string, encoding, fd) { - string = string.replace(/ember-cli/g, 'angular-cli'); - string = string.replace(/ember/g, 'ng'); - write.apply(process.stdout, arguments) - } - })(process.stderr.write); - - options.cli = { - name: 'ng', - root: path.join(__dirname, '..', '..'), - npmPackage: 'angular-cli' - }; - - return cli(options); -}; \ No newline at end of file diff --git a/modules/testing/builder/BUILD.bazel b/modules/testing/builder/BUILD.bazel new file mode 100644 index 000000000000..7f542efb0138 --- /dev/null +++ b/modules/testing/builder/BUILD.bazel @@ -0,0 +1,53 @@ +load("@npm//:defs.bzl", "npm_link_all_packages") +load("//tools:defaults.bzl", "jasmine_test", "ts_project") + +package(default_visibility = ["//visibility:public"]) + +npm_link_all_packages() + +ts_project( + name = "builder", + testonly = True, + srcs = glob( + include = [ + "src/**/*.ts", + ], + exclude = [ + "src/**/*_spec.ts", + ], + ), + data = [ + # Needed at runtime by some builder tests relying on SSR being + # resolvable in the test project. + ":node_modules/@angular/ssr", + ":node_modules/browser-sync", + ":node_modules/jsdom", + ":node_modules/vitest", + ":node_modules/@vitest/coverage-v8", + ] + glob(["projects/**/*"]), + deps = [ + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + ":node_modules/rxjs", + "//:node_modules/@types/node", + ], +) + +ts_project( + name = "unit_test_lib", + testonly = True, + srcs = glob( + include = [ + "src/**/*_spec.ts", + ], + ), + deps = [ + ":builder", + ":node_modules/@angular-devkit/architect", + ], +) + +jasmine_test( + name = "test", + data = [":unit_test_lib"], +) diff --git a/modules/testing/builder/package.json b/modules/testing/builder/package.json new file mode 100644 index 000000000000..fe92f53c0725 --- /dev/null +++ b/modules/testing/builder/package.json @@ -0,0 +1,13 @@ +{ + "devDependencies": { + "@angular-devkit/core": "workspace:*", + "@angular-devkit/architect": "workspace:*", + "@angular/ssr": "workspace:*", + "@angular-devkit/build-angular": "workspace:*", + "browser-sync": "3.0.4", + "@vitest/coverage-v8": "4.0.15", + "jsdom": "27.3.0", + "rxjs": "7.8.2", + "vitest": "4.0.15" + } +} diff --git a/modules/testing/builder/projects/hello-world-app/.editorconfig b/modules/testing/builder/projects/hello-world-app/.editorconfig new file mode 100644 index 000000000000..e89330a618c1 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/modules/testing/builder/projects/hello-world-app/.gitignore b/modules/testing/builder/projects/hello-world-app/.gitignore new file mode 100644 index 000000000000..e3038ca2d394 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/.gitignore @@ -0,0 +1,3 @@ +# Don't ignore node_modules, this project is not meant to be installed. +# Also, ~ import path in styles does only looks in the first node_modules found. +# /node_modules diff --git a/modules/testing/builder/projects/hello-world-app/README.md b/modules/testing/builder/projects/hello-world-app/README.md new file mode 100644 index 000000000000..3c578babc961 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/README.md @@ -0,0 +1,27 @@ +# HelloWorldApp + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.0-beta.1. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate --help` to see all the available schematics you can generate. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## 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/main/README.md). diff --git a/modules/testing/builder/projects/hello-world-app/angular.json b/modules/testing/builder/projects/hello-world-app/angular.json new file mode 100644 index 000000000000..95607701be8f --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/angular.json @@ -0,0 +1,191 @@ +{ + "$schema": "../../../../packages/angular_devkit/core/src/workspace/workspace-schema.json", + "version": 1, + "newProjectRoot": "./projects", + "cli": { + "cache": { + "enabled": false + } + }, + "schematics": {}, + "projects": { + "app": { + "root": "src", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": {}, + "targets": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "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" + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + }, + "inline-critical-css": { + "optimization": { + "styles": { + "minify": true, + "inlineCritical": true + }, + "scripts": true, + "fonts": true + } + }, + "sw": { + "ngswConfigPath": "src/ngsw-config.json", + "serviceWorker": true + } + } + }, + "server": { + "builder": "@angular-devkit/build-angular:server", + "options": { + "outputPath": "dist-server", + "main": "src/main.server.ts", + "tsConfig": "src/tsconfig.server.json", + "progress": false, + "sourceMap": true, + "optimization": false, + "extractLicenses": false + }, + "configurations": { + "production": { + "outputHashing": "media", + "sourceMap": false, + "optimization": true + } + } + }, + "app-shell": { + "builder": "@angular-devkit/build-angular:app-shell", + "options": { + "browserTarget": "app:build", + "serverTarget": "app:server" + }, + "configurations": { + "production": { + "browserTarget": "app:build:production" + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "app:build", + "watch": false + }, + "configurations": { + "production": { + "buildTarget": "app:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "app:build", + "progress": false, + "outputPath": "src" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "browsers": "ChromeHeadlessCI", + "progress": false, + "watch": false, + "styles": [ + { + "input": "src/styles.css" + } + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "serve-ssr": { + "builder": "@angular-devkit/build-angular:ssr-dev-server", + "options": { + "browserTarget": "app:build", + "serverTarget": "app:server", + "watch": false, + "progress": false + }, + "configurations": { + "production": { + "browserTarget": "app:build:production", + "serverTarget": "app:server:production" + } + } + }, + "prerender": { + "builder": "@angular-devkit/build-angular:prerender", + "options": { + "browserTarget": "app:build", + "serverTarget": "app:server" + }, + "configurations": { + "production": { + "browserTarget": "app:build:production", + "serverTarget": "app:server:production" + } + } + } + } + }, + "app-e2e": { + "root": "e2e", + "projectType": "application", + "targets": { + "e2e": { + "builder": "@angular-devkit/build-angular:private-protractor", + "options": { + "protractorConfig": "protractor.conf.js", + "devServerTarget": "app:serve", + "webdriverUpdate": false + } + } + } + } + } +} diff --git a/modules/testing/builder/projects/hello-world-app/e2e/app.e2e-spec.ts b/modules/testing/builder/projects/hello-world-app/e2e/app.e2e-spec.ts new file mode 100644 index 000000000000..862e95302ce3 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/e2e/app.e2e-spec.ts @@ -0,0 +1,22 @@ +/** + * @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.dev/license + */ + +import { AppPage } from './app.po'; + +describe('hello-world-app App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', async () => { + page.navigateTo(); + expect(await page.getTitleText()).toEqual('Welcome to app!'); + }); +}); diff --git a/modules/testing/builder/projects/hello-world-app/e2e/app.po.ts b/modules/testing/builder/projects/hello-world-app/e2e/app.po.ts new file mode 100644 index 000000000000..7cb6bc3fb743 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/e2e/app.po.ts @@ -0,0 +1,19 @@ +/** + * @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.dev/license + */ + +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo(): Promise { + return browser.get(browser.baseUrl) as Promise; + } + + getTitleText(): Promise { + return element(by.css('app-root h1')).getText() as Promise; + } +} diff --git a/modules/testing/builder/projects/hello-world-app/e2e/tsconfig.e2e.json b/modules/testing/builder/projects/hello-world-app/e2e/tsconfig.e2e.json new file mode 100644 index 000000000000..a82df00eef37 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/e2e/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es2018", + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/modules/testing/builder/projects/hello-world-app/karma.conf.js b/modules/testing/builder/projects/hello-world-app/karma.conf.js new file mode 100644 index 000000000000..7d5f7c8d98f5 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/karma.conf.js @@ -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.dev/license + */ + +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +const path = require('path'); +process.env.CHROME_BIN = require('puppeteer').executablePath(); + +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'), + ], + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: path.join(__dirname, './coverage'), + subdir: '.', + reporters: [ + {type: 'lcov'}, + ], + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + customLaunchers: { + ChromeHeadlessCI: { + base: 'ChromeHeadless', + flags: [ + '--disable-gpu', + '--no-sandbox' + ], + }, + }, + singleRun: false, + }); +}; diff --git a/modules/testing/builder/projects/hello-world-app/protractor.conf.js b/modules/testing/builder/projects/hello-world-app/protractor.conf.js new file mode 100644 index 000000000000..313f7ac7c53b --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/protractor.conf.js @@ -0,0 +1,43 @@ +/** + * @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.dev/license + */ + +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); +const { resolve } = require('path'); + +exports.config = { + allScriptsTimeout: 11000, + specs: ['./e2e/**/*.e2e-spec.ts'], + capabilities: { + browserName: 'chrome', + chromeOptions: { + args: ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'], + binary: require('puppeteer').executablePath(), + }, + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {}, + }, + onPrepare() { + require('ts-node').register({ + project: resolve(__dirname, './e2e/tsconfig.e2e.json'), + }); + jasmine.getEnv().addReporter(new SpecReporter({ + spec: { + displayStacktrace: StacktraceOption.PRETTY + } + })); + }, +}; diff --git a/addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.css b/modules/testing/builder/projects/hello-world-app/src/app/app.component.css similarity index 100% rename from addon/ng2/blueprints/component/files/src/app/components/__name__/__name__.css rename to modules/testing/builder/projects/hello-world-app/src/app/app.component.css diff --git a/modules/testing/builder/projects/hello-world-app/src/app/app.component.html b/modules/testing/builder/projects/hello-world-app/src/app/app.component.html new file mode 100644 index 000000000000..fe14e2bc127c --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/app/app.component.html @@ -0,0 +1,22 @@ + +
+

+ Welcome to {{ title }}! +

+ Angular Logo +
+

Here are some links to help you start:

+
+ +

i18n test

+ diff --git a/modules/testing/builder/projects/hello-world-app/src/app/app.component.spec.ts b/modules/testing/builder/projects/hello-world-app/src/app/app.component.spec.ts new file mode 100644 index 000000000000..37e8de10ba72 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/app/app.component.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.dev/license + */ + +import { TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; +describe('AppComponent', () => { + beforeEach(() => TestBed.configureTestingModule({ + declarations: [AppComponent], + })); + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + it(`should have as title 'app'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app'); + }); + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); + }); +}); diff --git a/modules/testing/builder/projects/hello-world-app/src/app/app.component.ts b/modules/testing/builder/projects/hello-world-app/src/app/app.component.ts new file mode 100644 index 000000000000..93a041e8aec7 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/app/app.component.ts @@ -0,0 +1,19 @@ +/** + * @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.dev/license + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + standalone: false, + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], +}) +export class AppComponent { + title = 'app'; +} diff --git a/modules/testing/builder/projects/hello-world-app/src/app/app.module.server.ts b/modules/testing/builder/projects/hello-world-app/src/app/app.module.server.ts new file mode 100644 index 000000000000..76fd51151562 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/app/app.module.server.ts @@ -0,0 +1,22 @@ +/** + * @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.dev/license + */ + +import { NgModule } from '@angular/core'; +import { ServerModule } from '@angular/platform-server'; + +import { AppModule } from './app.module'; +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + AppModule, + ServerModule, + ], + bootstrap: [AppComponent], +}) +export class AppServerModule {} diff --git a/modules/testing/builder/projects/hello-world-app/src/app/app.module.ts b/modules/testing/builder/projects/hello-world-app/src/app/app.module.ts new file mode 100644 index 000000000000..d8060d5ac4c9 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/app/app.module.ts @@ -0,0 +1,26 @@ +/** + * @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.dev/license + */ + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + + +import { AppComponent } from './app.component'; + + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.css b/modules/testing/builder/projects/hello-world-app/src/assets/.gitkeep similarity index 100% rename from addon/ng2/blueprints/route/files/src/app/__name__/__name__-detail.component.css rename to modules/testing/builder/projects/hello-world-app/src/assets/.gitkeep diff --git a/addon/ng2/blueprints/ng2/files/src/favicon.ico b/modules/testing/builder/projects/hello-world-app/src/favicon.ico similarity index 100% rename from addon/ng2/blueprints/ng2/files/src/favicon.ico rename to modules/testing/builder/projects/hello-world-app/src/favicon.ico diff --git a/modules/testing/builder/projects/hello-world-app/src/index.html b/modules/testing/builder/projects/hello-world-app/src/index.html new file mode 100644 index 000000000000..189ff561c88c --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/index.html @@ -0,0 +1,14 @@ + + + + + HelloWorldApp + + + + + + + + + diff --git a/modules/testing/builder/projects/hello-world-app/src/locale/messages.xlf b/modules/testing/builder/projects/hello-world-app/src/locale/messages.xlf new file mode 100644 index 000000000000..5ba84a9b3570 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/locale/messages.xlf @@ -0,0 +1,18 @@ + + + + + + i18n test + + app/app.component.ts + 21 + + + app/app.component.ts + 23 + + + + + diff --git a/modules/testing/builder/projects/hello-world-app/src/main.server.ts b/modules/testing/builder/projects/hello-world-app/src/main.server.ts new file mode 100644 index 000000000000..0c26cf60e980 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/main.server.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.dev/license + */ + +import { AppServerModule } from './app/app.module.server'; +export default AppServerModule; +export { AppServerModule }; diff --git a/modules/testing/builder/projects/hello-world-app/src/main.ts b/modules/testing/builder/projects/hello-world-app/src/main.ts new file mode 100644 index 000000000000..d975b48d7808 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/main.ts @@ -0,0 +1,15 @@ +/** + * @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.dev/license + */ + +import { platformBrowser } from '@angular/platform-browser'; + +import { AppModule } from './app/app.module'; + +platformBrowser() + .bootstrapModule(AppModule) + .catch(err => console.log(err)); diff --git a/modules/testing/builder/projects/hello-world-app/src/ngsw-config.json b/modules/testing/builder/projects/hello-world-app/src/ngsw-config.json new file mode 100644 index 000000000000..789b03bdf7ce --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/ngsw-config.json @@ -0,0 +1,30 @@ +{ + "$schema": "../node_modules/@angular/service-worker/config/schema.json", + "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/**", + "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" + ] + } + } + ] +} diff --git a/modules/testing/builder/projects/hello-world-app/src/polyfills.ts b/modules/testing/builder/projects/hello-world-app/src/polyfills.ts new file mode 100644 index 000000000000..eb77e392ee4e --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/polyfills.ts @@ -0,0 +1,70 @@ +/** + * @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.dev/license + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.dev/reference/versions#browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/modules/testing/builder/projects/hello-world-app/src/spectrum.png b/modules/testing/builder/projects/hello-world-app/src/spectrum.png new file mode 100644 index 000000000000..2a5f123afc84 Binary files /dev/null and b/modules/testing/builder/projects/hello-world-app/src/spectrum.png differ diff --git a/modules/testing/builder/projects/hello-world-app/src/styles.css b/modules/testing/builder/projects/hello-world-app/src/styles.css new file mode 100644 index 000000000000..90d4ee0072ce --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/modules/testing/builder/projects/hello-world-app/src/tsconfig.app.json b/modules/testing/builder/projects/hello-world-app/src/tsconfig.app.json new file mode 100644 index 000000000000..a76843b689fb --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": ["@angular/localize"] + }, + "files": [ + "main.ts", + "polyfills.ts" + ], + "include": [ + "**/*.d.ts" + ] +} diff --git a/modules/testing/builder/projects/hello-world-app/src/tsconfig.server.json b/modules/testing/builder/projects/hello-world-app/src/tsconfig.server.json new file mode 100644 index 000000000000..9a492c781eb1 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/tsconfig.server.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "outDir": "../dist-server", + "baseUrl": "./", + "types": ["@angular/localize", "node"] + }, + "files": [ + "main.server.ts" + ] +} diff --git a/modules/testing/builder/projects/hello-world-app/src/tsconfig.spec.json b/modules/testing/builder/projects/hello-world-app/src/tsconfig.spec.json new file mode 100644 index 000000000000..a42459f71db5 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "types": [ + "jasmine", + "@angular/localize" + ] + }, + "files": [ + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/modules/testing/builder/projects/hello-world-app/src/typings.d.ts b/modules/testing/builder/projects/hello-world-app/src/typings.d.ts new file mode 100644 index 000000000000..161056781dc0 --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/src/typings.d.ts @@ -0,0 +1,13 @@ +/** + * @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.dev/license + */ + +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/modules/testing/builder/projects/hello-world-app/tsconfig.json b/modules/testing/builder/projects/hello-world-app/tsconfig.json new file mode 100644 index 000000000000..8f8859a21d4f --- /dev/null +++ b/modules/testing/builder/projects/hello-world-app/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "bundler", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "skipLibCheck": true, + "target": "es2022", + "module": "es2022", + "lib": [ + "es2022", + "dom" + ] + }, + "angularCompilerOptions": { + "enableIvy": true, + "disableTypeScriptVersionCheck": true, + "strictTemplates": true + } +} diff --git a/modules/testing/builder/src/builder-harness.ts b/modules/testing/builder/src/builder-harness.ts new file mode 100644 index 000000000000..67b5f760d148 --- /dev/null +++ b/modules/testing/builder/src/builder-harness.ts @@ -0,0 +1,550 @@ +/** + * @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.dev/license + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +import { + BuilderContext, + BuilderHandlerFn, + BuilderInfo, + BuilderOutput, + BuilderOutputLike, + BuilderProgressReport, + BuilderRun, + ScheduleOptions, + Target, + fromAsyncIterable, + isBuilderOutput, +} from '@angular-devkit/architect'; +import { WorkspaceHost } from '@angular-devkit/architect/node'; +import { TestProjectHost } from '@angular-devkit/architect/testing'; +import { getSystemPath, json, logging } from '@angular-devkit/core'; +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; +import fs from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { + Observable, + Subject, + catchError, + finalize, + firstValueFrom, + lastValueFrom, + map, + mergeMap, + from as observableFrom, + of as observableOf, + shareReplay, +} from 'rxjs'; +import { BuilderWatcherFactory, WatcherNotifier } from './file-watching'; + +export interface BuilderHarnessExecutionResult { + result?: T; + error?: Error; + logs: readonly logging.LogEntry[]; +} + +export interface BuilderHarnessExecutionOptions { + configuration: string; + outputLogsOnFailure: boolean; + outputLogsOnException: boolean; + useNativeFileWatching: boolean; + signal: AbortSignal; + additionalExecuteArguments: unknown[]; +} + +interface BuilderHandlerFnWithVarArgs extends BuilderHandlerFn { + (input: T, context: BuilderContext, ...args: unknown[]): BuilderOutputLike; +} + +/** + * 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 = DEFAULT_PROJECT_METADATA; + private targetName?: string; + private options = new Map(); + private builderTargets = new Map< + string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { handler: BuilderHandlerFn; info: BuilderInfo; options: json.JsonObject } + >(); + private watcherNotifier?: WatcherNotifier; + + constructor( + private readonly builderHandler: BuilderHandlerFn, + private readonly host: TestProjectHost, + builderInfo?: Partial, + ) { + // Generate default pseudo builder info for test purposes + this.builderInfo = { + builderName: builderHandler.name, + description: '', + optionSchema: true, + ...builderInfo, + }; + + if (builderInfo?.builderName?.startsWith('@angular/build:')) { + this.schemaRegistry.addPostTransform(json.schema.transforms.addUndefinedObjectDefaults); + } else { + this.schemaRegistry.addPostTransform(json.schema.transforms.addUndefinedDefaults); + } + } + + private resolvePath(path: string): string { + return join(getSystemPath(this.host.root()), path); + } + + resetProjectMetadata(): void { + this.projectMetadata = DEFAULT_PROJECT_METADATA; + } + + useProject(name: string, metadata: Record = {}): this { + if (!name) { + throw new Error('Project name cannot be an empty string.'); + } + + this.projectName = name; + this.projectMetadata = metadata; + + return this; + } + + useTarget(name: string, baseOptions: T): this { + if (!name) { + throw new Error('Target name cannot be an empty string.'); + } + + this.targetName = name; + this.options.set(null, baseOptions); + + return this; + } + + withConfiguration(configuration: string, options: T): this { + this.options.set(configuration, options); + + return this; + } + + withBuilderTarget( + target: string, + handler: BuilderHandlerFn, + options?: O, + info?: Partial, + ): this { + this.builderTargets.set(target, { + handler, + options: options || {}, + info: { builderName: handler.name, description: '', optionSchema: true, ...info }, + }); + + return this; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + modifyTarget( + targetName: string, + modifier: (options: O) => O | void, + ): this { + const target = this.builderTargets.get(targetName); + if (!target) { + throw new Error(`Target "${targetName}" not found.`); + } + + const newOptions = modifier(target.options as O); + if (newOptions) { + target.options = newOptions as json.JsonObject; + } + + return this; + } + + execute( + options: Partial = {}, + ): Observable { + const { + configuration, + outputLogsOnException = true, + outputLogsOnFailure = true, + useNativeFileWatching = false, + } = options; + + const targetOptions = { + ...this.options.get(null), + ...((configuration && this.options.get(configuration)) ?? {}), + }; + + if (!useNativeFileWatching) { + if (this.watcherNotifier) { + throw new Error('Only one harness execution at a time is supported.'); + } + this.watcherNotifier = new WatcherNotifier(); + } + + const contextHost: ContextHost = { + findBuilderByTarget: async (project, target) => { + this.validateProjectName(project); + if (target === this.targetName) { + return { + info: this.builderInfo, + handler: this.builderHandler as BuilderHandlerFn, + }; + } + + const builderTarget = this.builderTargets.get(target); + if (builderTarget) { + return { info: builderTarget.info, handler: builderTarget.handler }; + } + + throw new Error('Project target does not exist.'); + }, + async getBuilderName(project, target) { + return (await this.findBuilderByTarget(project, target)).info.builderName; + }, + getMetadata: async (project) => { + this.validateProjectName(project); + + return this.projectMetadata as json.JsonObject; + }, + getOptions: async (project, target, configuration) => { + this.validateProjectName(project); + if (target === this.targetName) { + 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 || {}; + } + }, + hasTarget: async (project, target) => { + this.validateProjectName(project); + + return this.targetName === target || this.builderTargets.has(target); + }, + getDefaultConfigurationName: async (_project, _target) => { + return undefined; + }, + validate: async (options, builderName) => { + let schema; + if (builderName === this.builderInfo.builderName) { + schema = this.builderInfo.optionSchema; + } else { + for (const [, value] of this.builderTargets) { + if (value.info.builderName === builderName) { + schema = value.info.optionSchema; + break; + } + } + } + + const validator = await this.schemaRegistry.compile(schema ?? true); + const { data } = await validator(options); + + return data as json.JsonObject; + }, + }; + const context = new HarnessBuilderContext( + this.builderInfo, + this.resolvePath('.'), + contextHost, + options.signal, + useNativeFileWatching ? undefined : this.watcherNotifier, + ); + if (this.targetName !== undefined) { + context.target = { + project: this.projectName, + target: this.targetName, + configuration: configuration as string, + }; + } + + const logs: logging.LogEntry[] = []; + const logger$ = context.logger.subscribe((e) => logs.push(e)); + + return observableFrom(this.schemaRegistry.compile(this.builderInfo.optionSchema)).pipe( + mergeMap((validator) => validator(targetOptions)), + map((validationResult) => validationResult.data), + mergeMap((data) => + convertBuilderOutputToObservable( + (this.builderHandler as BuilderHandlerFnWithVarArgs)( + data as T & json.JsonObject, + context, + ...(options.additionalExecuteArguments ?? []), + ), + ), + ), + map((buildResult) => ({ result: buildResult, error: undefined })), + catchError((error) => { + if (outputLogsOnException) { + // eslint-disable-next-line no-console + console.error(logs.map((entry) => entry.message).join('\n')); + // eslint-disable-next-line no-console + console.error(error); + } + + return observableOf({ result: undefined, error }); + }), + map(({ result, error }) => { + if (outputLogsOnFailure && result?.success === false && logs.length > 0) { + // eslint-disable-next-line no-console + console.error(logs.map((entry) => entry.message).join('\n')); + } + + // Capture current logs and clear for next + const currentLogs = logs.slice(); + logs.length = 0; + + return { result, error, logs: currentLogs }; + }), + finalize(() => { + this.watcherNotifier = undefined; + logger$.unsubscribe(); + + for (const teardown of context.teardowns) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + teardown(); + } + }), + ); + } + + async executeOnce( + options?: Partial, + ): Promise { + // Return the first result + return firstValueFrom(this.execute(options)); + } + + async appendToFile(path: string, content: string): Promise { + await this.writeFile(path, this.readFile(path).concat(content)); + } + + async writeFile(path: string, content: string | Buffer): Promise { + const fullPath = this.resolvePath(path); + + await fs.mkdir(dirname(fullPath), { recursive: true }); + await fs.writeFile(fullPath, content, 'utf-8'); + + this.watcherNotifier?.notify([{ path: fullPath, type: 'modified' }]); + } + + async writeFiles(files: Record): Promise { + const watchEvents = this.watcherNotifier + ? ([] as { path: string; type: 'modified' | 'deleted' }[]) + : undefined; + + for (const [path, content] of Object.entries(files)) { + const fullPath = this.resolvePath(path); + + await fs.mkdir(dirname(fullPath), { recursive: true }); + await fs.writeFile(fullPath, content, 'utf-8'); + + watchEvents?.push({ path: fullPath, type: 'modified' }); + } + + if (watchEvents) { + this.watcherNotifier?.notify(watchEvents); + } + } + + async removeFile(path: string): Promise { + const fullPath = this.resolvePath(path); + + await fs.unlink(fullPath); + + this.watcherNotifier?.notify([{ path: fullPath, type: 'deleted' }]); + } + + async modifyFile( + path: string, + modifier: (content: string) => string | Promise, + ): Promise { + const content = this.readFile(path); + await this.writeFile(path, await modifier(content)); + } + + hasFile(path: string): boolean { + const fullPath = this.resolvePath(path); + + return existsSync(fullPath); + } + + hasDirectory(path: string): boolean { + const fullPath = this.resolvePath(path); + + return statSync(fullPath, { throwIfNoEntry: false })?.isDirectory() ?? false; + } + + hasFileMatch(directory: string, pattern: RegExp): boolean { + const fullPath = this.resolvePath(directory); + + return readdirSync(fullPath).some((name) => pattern.test(name)); + } + + readFile(path: string): string { + const fullPath = this.resolvePath(path); + + return readFileSync(fullPath, 'utf-8'); + } + + private validateProjectName(name: string): void { + if (name !== this.projectName) { + throw new Error(`Project "${name}" does not exist.`); + } + } +} + +interface ContextHost extends WorkspaceHost { + findBuilderByTarget( + project: string, + target: string, + ): Promise<{ info: BuilderInfo; handler: BuilderHandlerFn }>; + validate(options: json.JsonObject, builderName: string): Promise; +} + +class HarnessBuilderContext implements BuilderContext { + id = Math.trunc(Math.random() * 1000000); + logger = new logging.Logger(`builder-harness-${this.id}`); + workspaceRoot: string; + currentDirectory: string; + target?: Target; + + teardowns: (() => Promise | void)[] = []; + + constructor( + public builder: BuilderInfo, + basePath: string, + private readonly contextHost: ContextHost, + public readonly signal: AbortSignal | undefined, + public readonly watcherFactory: BuilderWatcherFactory | undefined, + ) { + this.workspaceRoot = this.currentDirectory = basePath; + } + + addTeardown(teardown: () => Promise | void): void { + this.teardowns.push(teardown); + } + + async getBuilderNameForTarget(target: Target): Promise { + return this.contextHost.getBuilderName(target.project, target.target); + } + + async getProjectMetadata(targetOrName: Target | string): Promise { + const project = typeof targetOrName === 'string' ? targetOrName : targetOrName.project; + + return this.contextHost.getMetadata(project); + } + + async getTargetOptions(target: Target): Promise { + return this.contextHost.getOptions(target.project, target.target, target.configuration); + } + + // Unused by builders in this package + async scheduleBuilder( + builderName: string, + options?: json.JsonObject, + scheduleOptions?: ScheduleOptions, + ): Promise { + throw new Error('Not Implemented.'); + } + + async scheduleTarget( + target: Target, + overrides?: json.JsonObject, + scheduleOptions?: ScheduleOptions, + ): Promise { + const { info, handler } = await this.contextHost.findBuilderByTarget( + target.project, + target.target, + ); + const targetOptions = await this.validateOptions( + { + ...(await this.getTargetOptions(target)), + ...overrides, + }, + info.builderName, + ); + + const context = new HarnessBuilderContext( + info, + this.workspaceRoot, + this.contextHost, + this.signal, + this.watcherFactory, + ); + context.target = target; + context.logger = scheduleOptions?.logger || this.logger.createChild(''); + + const progressSubject = new Subject(); + const output = convertBuilderOutputToObservable(handler(targetOptions, context)); + + const run: BuilderRun = { + id: context.id, + info, + progress: progressSubject.asObservable(), + async stop() { + for (const teardown of context.teardowns) { + await teardown(); + } + progressSubject.complete(); + }, + output: output.pipe(shareReplay()), + get result() { + return firstValueFrom(this.output); + }, + get lastOutput() { + return lastValueFrom(this.output); + }, + }; + + return run; + } + + async validateOptions( + options: json.JsonObject, + builderName: string, + ): Promise { + return this.contextHost.validate(options, builderName) as unknown as T; + } + + // Unused report methods + reportRunning(): void {} + reportStatus(): void {} + reportProgress(): void {} +} + +function isAsyncIterable(obj: unknown): obj is AsyncIterable { + return !!obj && typeof (obj as AsyncIterable)[Symbol.asyncIterator] === 'function'; +} + +function convertBuilderOutputToObservable(output: BuilderOutputLike): Observable { + if (isBuilderOutput(output)) { + return observableOf(output); + } else if (isAsyncIterable(output)) { + return fromAsyncIterable(output); + } else { + return observableFrom(output); + } +} diff --git a/modules/testing/builder/src/builder-harness_spec.ts b/modules/testing/builder/src/builder-harness_spec.ts new file mode 100644 index 000000000000..860764f567f8 --- /dev/null +++ b/modules/testing/builder/src/builder-harness_spec.ts @@ -0,0 +1,137 @@ +/** + * @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.dev/license + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +import { TestProjectHost } from '@angular-devkit/architect/testing'; +import { BuilderHarness } from './builder-harness'; + +describe('BuilderHarness', () => { + let mockHost: TestProjectHost; + + beforeEach(() => { + mockHost = jasmine.createSpyObj('TestProjectHost', ['root']); + (mockHost.root as jasmine.Spy).and.returnValue('.'); + }); + + it('uses the provided builder handler', async () => { + const mockHandler = jasmine.createSpy().and.returnValue({ success: true }); + + const harness = new BuilderHarness(mockHandler, mockHost); + + await harness.executeOnce(); + + expect(mockHandler).toHaveBeenCalled(); + }); + + it('provides the builder output result when executing', async () => { + const mockHandler = jasmine.createSpy().and.returnValue({ success: false, property: 'value' }); + + const harness = new BuilderHarness(mockHandler, mockHost); + const { result } = await harness.executeOnce(); + + expect(result).toBeDefined(); + expect(result?.success).toBeFalse(); + expect(result?.property).toBe('value'); + }); + + it('does not show builder logs on console when a builder succeeds', async () => { + const consoleErrorMock = spyOn(console, 'error'); + + const harness = new BuilderHarness(async (_, context) => { + context.logger.warn('TEST WARNING'); + + return { success: true }; + }, mockHost); + + const { result } = await harness.executeOnce(); + + expect(result).toBeDefined(); + expect(result?.success).toBeTrue(); + + expect(consoleErrorMock).not.toHaveBeenCalledWith(jasmine.stringMatching('TEST WARNING')); + }); + + it('shows builder logs on console when a builder fails', async () => { + const consoleErrorMock = spyOn(console, 'error'); + + const harness = new BuilderHarness(async (_, context) => { + context.logger.warn('TEST WARNING'); + + return { success: false }; + }, mockHost); + + const { result } = await harness.executeOnce(); + + expect(result).toBeDefined(); + expect(result?.success).toBeFalse(); + + expect(consoleErrorMock).toHaveBeenCalledWith(jasmine.stringMatching('TEST WARNING')); + }); + + it('does not show builder logs on console when a builder fails and outputLogsOnFailure: false', async () => { + const consoleErrorMock = spyOn(console, 'error'); + + const harness = new BuilderHarness(async (_, context) => { + context.logger.warn('TEST WARNING'); + + return { success: false }; + }, mockHost); + + const { result } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result).toBeDefined(); + expect(result?.success).toBeFalse(); + + expect(consoleErrorMock).not.toHaveBeenCalledWith(jasmine.stringMatching('TEST WARNING')); + }); + + it('provides and logs the builder output exception when builder throws', async () => { + const mockHandler = jasmine.createSpy().and.throwError(new Error('Builder Error')); + const consoleErrorMock = spyOn(console, 'error'); + + const harness = new BuilderHarness(mockHandler, mockHost); + const { result, error } = await harness.executeOnce(); + + expect(result).toBeUndefined(); + expect(error).toEqual(jasmine.objectContaining({ message: 'Builder Error' })); + expect(consoleErrorMock).toHaveBeenCalledWith(jasmine.stringMatching('Builder Error')); + }); + + it('does not log exception with outputLogsOnException false when builder throws', async () => { + const mockHandler = jasmine.createSpy().and.throwError(new Error('Builder Error')); + const consoleErrorMock = spyOn(console, 'error'); + + const harness = new BuilderHarness(mockHandler, mockHost); + const { result, error } = await harness.executeOnce({ outputLogsOnException: false }); + + expect(result).toBeUndefined(); + expect(error).toEqual(jasmine.objectContaining({ message: 'Builder Error' })); + expect(consoleErrorMock).not.toHaveBeenCalledWith(jasmine.stringMatching('Builder Error')); + }); + + it('supports executing a target from within a builder', async () => { + const mockHandler = jasmine.createSpy().and.returnValue({ success: true }); + + const harness = new BuilderHarness(async (_, context) => { + const run = await context.scheduleTarget({ project: 'test', target: 'another' }); + expect(await run.result).toEqual(jasmine.objectContaining({ success: true })); + await run.stop(); + + return { success: true }; + }, mockHost); + harness.withBuilderTarget('another', mockHandler); + + const { result } = await harness.executeOnce(); + + expect(result).toBeDefined(); + expect(result?.success).toBeTrue(); + + expect(mockHandler).toHaveBeenCalled(); + }); +}); diff --git a/modules/testing/builder/src/dev_prod_mode.ts b/modules/testing/builder/src/dev_prod_mode.ts new file mode 100644 index 000000000000..520de82d771b --- /dev/null +++ b/modules/testing/builder/src/dev_prod_mode.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.dev/license + */ + +import { BuilderHarness } from './builder-harness'; + +export const GOOD_TARGET = './src/good.js'; +export const BAD_TARGET = './src/bad.js'; + +/** Setup project for use of conditional imports. */ +export async function setupConditionImport(harness: BuilderHarness) { + // Files that can be used as targets for the conditional import. + await harness.writeFile('src/good.ts', `export const VALUE = 'good-value';`); + await harness.writeFile('src/bad.ts', `export const VALUE = 'bad-value';`); + await harness.writeFile('src/wrong.ts', `export const VALUE = 1;`); + + // Simple application file that accesses conditional code. + await harness.writeFile( + 'src/main.ts', + `import {VALUE} from '#target'; +console.log(VALUE); +console.log(VALUE.length); +export default 42 as any; +`, + ); + + // Ensure that good/bad can be resolved from tsconfig. + const tsconfig = JSON.parse(harness.readFile('src/tsconfig.app.json')) as TypeScriptConfig; + tsconfig.compilerOptions.moduleResolution = 'bundler'; + tsconfig.files.push('good.ts', 'bad.ts', 'wrong.ts'); + await harness.writeFile('src/tsconfig.app.json', JSON.stringify(tsconfig)); +} + +/** Update package.json with the given mapping for #target. */ +export async function setTargetMapping(harness: BuilderHarness, mapping: unknown) { + await harness.writeFile( + 'package.json', + JSON.stringify({ + name: 'ng-test-app', + imports: { + '#target': mapping, + }, + }), + ); +} + +interface TypeScriptConfig { + compilerOptions: { + moduleResolution: string; + }; + files: string[]; +} diff --git a/modules/testing/builder/src/file-watching.ts b/modules/testing/builder/src/file-watching.ts new file mode 100644 index 000000000000..6d10706d6919 --- /dev/null +++ b/modules/testing/builder/src/file-watching.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.dev/license + */ + +type BuilderWatcherCallback = ( + events: Array<{ path: string; type: 'created' | 'modified' | 'deleted'; time?: number }>, +) => void; + +interface BuilderWatcherFactory { + watch( + files: Iterable, + directories: Iterable, + callback: BuilderWatcherCallback, + ): { close(): void }; +} + +class WatcherDescriptor { + constructor( + readonly files: ReadonlySet, + readonly directories: ReadonlySet, + readonly callback: BuilderWatcherCallback, + ) {} + + shouldNotify(path: string): boolean { + return true; + } +} + +export class WatcherNotifier implements BuilderWatcherFactory { + private readonly descriptors = new Set(); + + notify(events: Iterable<{ path: string; type: 'modified' | 'deleted' }>): void { + for (const descriptor of this.descriptors) { + for (const { path } of events) { + if (descriptor.shouldNotify(path)) { + descriptor.callback([...events]); + break; + } + } + } + } + + watch( + files: Iterable, + directories: Iterable, + callback: BuilderWatcherCallback, + ): { close(): void } { + const descriptor = new WatcherDescriptor(new Set(files), new Set(directories), callback); + this.descriptors.add(descriptor); + + return { close: () => this.descriptors.delete(descriptor) }; + } +} + +export type { BuilderWatcherFactory }; diff --git a/modules/testing/builder/src/index.ts b/modules/testing/builder/src/index.ts new file mode 100644 index 000000000000..3ee4220781d9 --- /dev/null +++ b/modules/testing/builder/src/index.ts @@ -0,0 +1,21 @@ +/** + * @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.dev/license + */ + +export { + BuilderHarness, + type BuilderHarnessExecutionOptions, + type BuilderHarnessExecutionResult, +} from './builder-harness'; +export { + type HarnessFileMatchers, + JasmineBuilderHarness, + describeBuilder, + expectLog, + expectNoLog, +} from './jasmine-helpers'; +export * from './test-utils'; diff --git a/modules/testing/builder/src/jasmine-helpers.ts b/modules/testing/builder/src/jasmine-helpers.ts new file mode 100644 index 000000000000..b31f2e273333 --- /dev/null +++ b/modules/testing/builder/src/jasmine-helpers.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.dev/license + */ + +import { BuilderHandlerFn } from '@angular-devkit/architect'; +import { json, logging } from '@angular-devkit/core'; +import { readFileSync } from 'node:fs'; +import { concatMap, count, debounceTime, firstValueFrom, take, timeout } from 'rxjs'; +import { + BuilderHarness, + BuilderHarnessExecutionOptions, + BuilderHarnessExecutionResult, +} from './builder-harness'; +import { host } from './test-utils'; + +/** + * Maximum time for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 30_000; + +const optionSchemaCache = new Map(); + +export function describeBuilder( + builderHandler: BuilderHandlerFn, + options: { name?: string; schemaPath: string }, + specDefinitions: (harness: JasmineBuilderHarness) => void, +): void { + let optionSchema = optionSchemaCache.get(options.schemaPath); + if (optionSchema === undefined) { + optionSchema = JSON.parse(readFileSync(options.schemaPath, 'utf8')) as json.schema.JsonSchema; + optionSchemaCache.set(options.schemaPath, optionSchema); + } + const harness = new JasmineBuilderHarness(builderHandler, host, { + builderName: options.name, + optionSchema, + }); + + describe(options.name || builderHandler.name, () => { + beforeEach(async () => { + harness.resetProjectMetadata(); + + await host.initialize().toPromise(); + }); + + afterEach(() => host.restore().toPromise()); + + specDefinitions(harness); + }); +} + +export class JasmineBuilderHarness extends BuilderHarness { + expectFile(path: string): HarnessFileMatchers { + return expectFile(path, this); + } + expectDirectory(path: string): HarnessDirectoryMatchers { + return expectDirectory(path, this); + } + + async executeWithCases( + cases: (( + executionResult: BuilderHarnessExecutionResult, + index: number, + ) => void | Promise)[], + options?: Partial & { timeout?: number }, + ): Promise { + const executionCount = await firstValueFrom( + this.execute(options).pipe( + timeout(options?.timeout ?? BUILD_TIMEOUT), + debounceTime(100), // This is needed as sometimes 2 events for the same change fire with webpack. + concatMap(async (result, index) => await cases[index](result, index)), + take(cases.length), + count(), + ), + ); + + expect(executionCount).toBe(cases.length); + } +} + +export interface HarnessFileMatchers { + toExist(): boolean; + toNotExist(): boolean; + readonly content: jasmine.ArrayLikeMatchers; + readonly size: jasmine.Matchers; +} + +export interface HarnessDirectoryMatchers { + toExist(): boolean; + toNotExist(): boolean; +} + +/** + * Add a Jasmine expectation filter to an expectation that always fails with a message. + * @param base The base expectation (`expect(...)`) to use. + * @param message The message to provide in the expectation failure. + */ +function createFailureExpectation(base: T, message: string): T { + // Needed typings are not included in the Jasmine types + const expectation = base as T & { + expector: { + addFilter(filter: { + selectComparisonFunc(): () => { pass: boolean; message: string }; + }): typeof expectation.expector; + }; + }; + expectation.expector = expectation.expector.addFilter({ + selectComparisonFunc() { + return () => ({ + pass: false, + message, + }); + }, + }); + + return expectation; +} + +export function expectFile(path: string, harness: BuilderHarness): HarnessFileMatchers { + return { + toExist() { + const exists = harness.hasFile(path); + expect(exists) + .withContext('Expected file to exist: ' + path) + .toBeTrue(); + + return exists; + }, + toNotExist() { + const exists = harness.hasFile(path); + expect(exists) + .withContext('Expected file to exist: ' + path) + .toBeFalse(); + + return !exists; + }, + get content() { + try { + return expect(harness.readFile(path)).withContext(`With file content for '${path}'`); + } catch (e) { + if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { + throw e; + } + + // File does not exist so always fail the expectation + return createFailureExpectation( + expect(''), + `Expected file content but file does not exist: '${path}'`, + ); + } + }, + get size() { + try { + return expect(Buffer.byteLength(harness.readFile(path))).withContext( + `With file size for '${path}'`, + ); + } catch (e) { + if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { + throw e; + } + + // File does not exist so always fail the expectation + return createFailureExpectation( + expect(0), + `Expected file size but file does not exist: '${path}'`, + ); + } + }, + }; +} + +export function expectDirectory( + path: string, + harness: BuilderHarness, +): HarnessDirectoryMatchers { + return { + toExist() { + const exists = harness.hasDirectory(path); + expect(exists) + .withContext('Expected directory to exist: ' + path) + .toBeTrue(); + + return exists; + }, + toNotExist() { + const exists = harness.hasDirectory(path); + expect(exists) + .withContext('Expected directory to not exist: ' + path) + .toBeFalse(); + + return !exists; + }, + }; +} + +export function expectLog(logs: readonly logging.LogEntry[], message: string | RegExp) { + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(message), + }), + ); +} + +export function expectNoLog( + logs: readonly logging.LogEntry[], + message: string | RegExp, + failureMessage?: string, +) { + expect(logs) + .withContext(failureMessage ?? '') + .not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(message), + }), + ); +} diff --git a/modules/testing/builder/src/test-utils.ts b/modules/testing/builder/src/test-utils.ts new file mode 100644 index 000000000000..f41cb42d1ea6 --- /dev/null +++ b/modules/testing/builder/src/test-utils.ts @@ -0,0 +1,184 @@ +/** + * @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.dev/license + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +import { Architect, BuilderOutput, ScheduleOptions, Target } from '@angular-devkit/architect'; +import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node'; +import { TestProjectHost, TestingArchitectHost } from '@angular-devkit/architect/testing'; +import { + Path, + getSystemPath, + join, + json, + normalize, + schema, + virtualFs, + workspaces, +} from '@angular-devkit/core'; +import path from 'node:path'; +import { firstValueFrom } from 'rxjs'; + +// Default timeout for large specs is 60s. +jasmine.DEFAULT_TIMEOUT_INTERVAL = 60_000; + +export const workspaceRoot = join(normalize(__dirname), `../projects/hello-world-app/`); +export const host = new TestProjectHost(workspaceRoot); +export const outputPath: Path = normalize('dist'); + +export const browserTargetSpec = { project: 'app', target: 'build' }; +export const devServerTargetSpec = { project: 'app', target: 'serve' }; +export const extractI18nTargetSpec = { project: 'app', target: 'extract-i18n' }; +export const karmaTargetSpec = { project: 'app', target: 'test' }; +export const tslintTargetSpec = { project: 'app', target: 'lint' }; +export const protractorTargetSpec = { project: 'app-e2e', target: 'e2e' }; + +export async function createArchitect(workspaceRoot: Path) { + const registry = new schema.CoreSchemaRegistry(); + registry.addPostTransform(schema.transforms.addUndefinedDefaults); + const workspaceSysPath = getSystemPath(workspaceRoot); + + // The download path is relative (set from Starlark), so before potentially + // changing directories, or executing inside a temporary directory, ensure + // the path is absolute. + if (process.env['PUPPETEER_DOWNLOAD_PATH']) { + process.env.PUPPETEER_DOWNLOAD_PATH = path.resolve(process.env['PUPPETEER_DOWNLOAD_PATH']); + } + + const { workspace } = await workspaces.readWorkspace( + workspaceSysPath, + workspaces.createWorkspaceHost(host), + ); + const architectHost = new TestingArchitectHost( + workspaceSysPath, + workspaceSysPath, + new WorkspaceNodeModulesArchitectHost(workspace, workspaceSysPath), + ); + const architect = new Architect(architectHost, registry); + + return { + workspace, + architectHost, + architect, + }; +} + +export interface BrowserBuildOutput { + output: BuilderOutput; + files: { [file: string]: Promise }; +} + +export async function browserBuild( + architect: Architect, + host: virtualFs.Host, + target: Target, + overrides?: json.JsonObject, + scheduleOptions?: ScheduleOptions, +): Promise { + const run = await architect.scheduleTarget(target, overrides, scheduleOptions); + const output = (await run.result) as BuilderOutput & { outputs: { path: string }[] }; + expect(output.success).toBe(true); + + if (!output.success) { + await run.stop(); + + return { + output, + files: {}, + }; + } + + const [{ path }] = output.outputs; + expect(path).toBeTruthy(); + const outputPath = normalize(path); + + const fileNames = await firstValueFrom(host.list(outputPath)); + const files = fileNames.reduce((acc: { [name: string]: Promise }, path) => { + let cache: Promise | null = null; + Object.defineProperty(acc, path, { + enumerable: true, + get() { + if (cache) { + return cache; + } + if (!fileNames.includes(path)) { + return Promise.reject('No file named ' + path); + } + cache = firstValueFrom(host.read(join(outputPath, path))).then((content) => + virtualFs.fileBufferToString(content), + ); + + return cache; + }, + }); + + return acc; + }, {}); + + await run.stop(); + + return { + output, + files, + }; +} + +export const lazyModuleFiles: { [path: string]: string } = { + 'src/app/lazy/lazy-routing.module.ts': ` + import { NgModule } from '@angular/core'; + import { Routes, RouterModule } from '@angular/router'; + + const routes: Routes = []; + + @NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] + }) + export class LazyRoutingModule { } + `, + 'src/app/lazy/lazy.module.ts': ` + import { NgModule } from '@angular/core'; + import { CommonModule } from '@angular/common'; + + import { LazyRoutingModule } from './lazy-routing.module'; + + @NgModule({ + imports: [ + CommonModule, + LazyRoutingModule + ], + declarations: [] + }) + export class LazyModule { } + `, +}; + +export const lazyModuleFnImport: { [path: string]: string } = { + 'src/app/app.module.ts': ` + import { BrowserModule } from '@angular/platform-browser'; + import { NgModule } from '@angular/core'; + + import { AppComponent } from './app.component'; + import { RouterModule } from '@angular/router'; + + @NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + RouterModule.forRoot([ + { path: 'lazy', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) } + ]) + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } +`, +}; diff --git a/package.json b/package.json index 28141461b424..5f862d813789 100644 --- a/package.json +++ b/package.json @@ -1,68 +1,167 @@ { - "name": "angular-cli", - "version": "0.0.21", - "description": "", - "main": "lib/cli/index.js", - "trackingCode": "UA-8594346-19", - "bin": { - "ng": "./bin/ng" - }, - "keywords": [], + "name": "@angular/devkit-repo", + "version": "21.1.0-next.3", + "private": true, + "description": "Software Development Kit for Angular", + "keywords": [ + "angular", + "Angular CLI", + "devkit", + "sdk", + "Angular DevKit" + ], "scripts": { - "test": "node tests/runner" + "admin": "node --no-warnings=ExperimentalWarning --experimental-transform-types ./scripts/devkit-admin.mts", + "bazel": "bazelisk", + "test": "bazel test //packages/...", + "build": "pnpm -s admin build", + "build-schema": "bazel build //... --build_tag_filters schema --symlink_prefix dist-schema/", + "lint": "eslint --cache --max-warnings=0 \"**/*.@(ts|mts|cts)\"", + "templates": "pnpm -s admin templates", + "validate": "pnpm -s admin validate", + "postinstall": "husky", + "ts-circular-deps": "ng-dev ts-circular-deps --config ./scripts/circular-deps-test.conf.mjs", + "check-tooling-setup": "tsc --project .ng-dev/tsconfig.json", + "diff-release-package": "node scripts/diff-release-package.mts" }, "repository": { "type": "git", "url": "https://github.com/angular/angular-cli.git" }, + "packageManager": "pnpm@10.26.1", "engines": { - "node": ">= 4.1.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "Please use pnpm instead of NPM to install dependencies", + "yarn": "Please use pnpm instead of Yarn to install dependencies", + "pnpm": "10.26.1" }, "author": "Angular Authors", - "contributors": [ - "Rody Haddad (http://rodyhaddad.com/)", - "Igor Minar " - ], - "license": "Apache-2.0", + "license": "MIT", "bugs": { "url": "https://github.com/angular/angular-cli/issues" }, "homepage": "https://github.com/angular/angular-cli", - "dependencies": { - "broccoli": "^1.0.0-beta.6", - "broccoli-concat": "2.1.0", - "broccoli-funnel": "^1.0.1", - "broccoli-merge-trees": "^1.0.1", - "broccoli-writer": "^0.1.1", - "chalk": "^1.1.0", - "ember-cli": "1.13.15", - "exit": "^0.1.2", - "fs-extra": "^0.18.4", - "leek": "0.0.19", - "lodash": "^3.10.0", - "multidep": "^2.0.0", - "node-notifier": "^4.4.0", - "resolve": "^1.0.0", - "shelljs": "^0.5.3", - "silent-error": "^1.0.0", - "symlink-or-copy": "^1.0.1", - "typescript": "~1.7.3", - "typings": "^0.6.2" + "devDependencies": { + "@angular/animations": "21.1.0-next.4", + "@angular/cdk": "21.1.0-next.3", + "@angular/common": "21.1.0-next.4", + "@angular/compiler": "21.1.0-next.4", + "@angular/compiler-cli": "21.1.0-next.4", + "@angular/core": "21.1.0-next.4", + "@angular/forms": "21.1.0-next.4", + "@angular/localize": "21.1.0-next.4", + "@angular/material": "21.1.0-next.3", + "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#ddc3809c1993612732eaae62d28e828b2ed789e5", + "@angular/platform-browser": "21.1.0-next.4", + "@angular/platform-server": "21.1.0-next.4", + "@angular/router": "21.1.0-next.4", + "@angular/service-worker": "21.1.0-next.4", + "@babel/core": "7.28.5", + "@bazel/bazelisk": "1.26.0", + "@bazel/buildifier": "8.2.1", + "@eslint/compat": "2.0.0", + "@eslint/eslintrc": "3.3.3", + "@eslint/js": "9.39.2", + "@rollup/plugin-alias": "^6.0.0", + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "16.0.3", + "@stylistic/eslint-plugin": "^5.0.0", + "@types/babel__core": "7.20.5", + "@types/babel__generator": "^7.6.8", + "@types/browser-sync": "^2.27.0", + "@types/express": "~5.0.1", + "@types/http-proxy": "^1.17.4", + "@types/ini": "^4.0.0", + "@types/jasmine": "~5.1.0", + "@types/jasmine-reporters": "^2", + "@types/karma": "^6.3.0", + "@types/less": "^3.0.3", + "@types/loader-utils": "^3.0.0", + "@types/lodash": "^4.17.0", + "@types/node": "^22.12.0", + "@types/npm-package-arg": "^6.1.0", + "@types/pacote": "^11.1.3", + "@types/picomatch": "^4.0.0", + "@types/progress": "^2.0.3", + "@types/resolve": "^1.17.1", + "@types/semver": "^7.3.12", + "@types/watchpack": "^2.4.4", + "@types/yargs": "^17.0.20", + "@types/yargs-parser": "^21.0.0", + "@types/yarnpkg__lockfile": "^1.1.5", + "@typescript-eslint/eslint-plugin": "8.50.0", + "@typescript-eslint/parser": "8.50.0", + "ajv": "8.17.1", + "buffer": "6.0.3", + "esbuild": "0.27.1", + "esbuild-wasm": "0.27.1", + "eslint": "9.39.2", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-header": "3.1.1", + "eslint-plugin-import": "2.32.0", + "express": "5.2.1", + "fast-glob": "3.3.3", + "globals": "16.5.0", + "http-proxy": "^1.18.1", + "http-proxy-middleware": "3.0.5", + "husky": "9.1.7", + "jasmine": "~5.13.0", + "jasmine-core": "~5.13.0", + "jasmine-reporters": "^2.5.2", + "jasmine-spec-reporter": "~7.0.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "karma-source-map-support": "1.4.0", + "lodash": "^4.17.21", + "magic-string": "0.30.21", + "prettier": "^3.0.0", + "protractor": "~7.0.0", + "puppeteer": "18.2.1", + "quicktype-core": "23.2.6", + "rollup": "4.53.5", + "rollup-license-plugin": "~3.1.0", + "rollup-plugin-dts": "6.3.0", + "rollup-plugin-sourcemaps2": "0.5.4", + "semver": "7.7.3", + "source-map-support": "0.5.21", + "ts-node": "^10.9.1", + "tslib": "2.8.1", + "typescript": "5.9.3", + "undici": "7.16.0", + "unenv": "^1.10.0", + "verdaccio": "6.2.4", + "verdaccio-auth-memory": "^10.0.0", + "zone.js": "^0.16.0" }, - "ember-addon": { - "paths": [ - "./addon/ng2/" - ] + "dependenciesMeta": { + "esbuild": { + "built": true + }, + "puppeteer": { + "built": true + } }, - "devDependencies": { - "broccoli-stew": "^0.3.6", - "chai": "^3.2.0", - "exists-sync": "0.0.3", - "glob": "^5.0.14", - "minimatch": "^2.0.10", - "mocha": "^2.2.5", - "rewire": "^2.3.4", - "through": "^2.3.8", - "walk-sync": "^0.2.6" + "pnpm": { + "onlyBuiltDependencies": [ + "puppeteer", + "webdriver-manager" + ], + "overrides": { + "@angular/build": "workspace:*" + }, + "packageExtensions": { + "grpc-gcp": { + "peerDependencies": { + "protobufjs": "*" + } + } + } + }, + "resolutions": { + "undici-types": "^7.16.0" } } diff --git a/packages/README.md b/packages/README.md new file mode 100644 index 000000000000..aab7eadc1c92 --- /dev/null +++ b/packages/README.md @@ -0,0 +1,8 @@ +# `/packages` Folder + +This folder is the root of all defined packages in this repository. + +Packages that are marked as `private: true` will not be published to NPM. + +This folder includes a directory for every scope in NPM, without the `@` sign. Then one folder +per package, which contains the `package.json`. diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel new file mode 100644 index 000000000000..2f74117ff850 --- /dev/null +++ b/packages/angular/build/BUILD.bazel @@ -0,0 +1,359 @@ +load("@devinfra//bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@npm//:defs.bzl", "npm_link_all_packages") +load("//tools:defaults.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") +load("//tools:ts_json_schema.bzl", "ts_json_schema") + +licenses(["notice"]) + +package(default_visibility = ["//visibility:public"]) + +npm_link_all_packages() + +ts_json_schema( + name = "application_schema", + src = "src/builders/application/schema.json", +) + +ts_json_schema( + name = "dev-server_schema", + src = "src/builders/dev-server/schema.json", +) + +ts_json_schema( + name = "extract_i18n_schema", + src = "src/builders/extract-i18n/schema.json", +) + +ts_json_schema( + name = "ng_karma_schema", + src = "src/builders/karma/schema.json", +) + +ts_json_schema( + name = "ng_packagr_schema", + src = "src/builders/ng-packagr/schema.json", +) + +ts_json_schema( + name = "unit_test_schema", + src = "src/builders/unit-test/schema.json", +) + +copy_to_bin( + name = "schemas", + srcs = glob(["**/schema.json"]), +) + +RUNTIME_ASSETS = glob( + include = [ + "src/**/schema.json", + "src/**/*.js", + ], +) + [ + "builders.json", + "package.json", +] + +ts_project( + name = "build", + srcs = glob( + include = [ + "src/**/*.ts", + ], + exclude = [ + "src/test-utils.ts", + "src/**/*_spec.ts", + "src/**/tests/**/*.ts", + "src/testing/**/*.ts", + ], + ) + [ + "index.ts", + "//packages/angular/build:src/builders/application/schema.ts", + "//packages/angular/build:src/builders/dev-server/schema.ts", + "//packages/angular/build:src/builders/extract-i18n/schema.ts", + "//packages/angular/build:src/builders/karma/schema.ts", + "//packages/angular/build:src/builders/ng-packagr/schema.ts", + "//packages/angular/build:src/builders/unit-test/schema.ts", + ], + data = RUNTIME_ASSETS, + deps = [ + ":node_modules/@ampproject/remapping", + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + ":node_modules/@angular/ssr", + ":node_modules/@babel/core", + ":node_modules/@babel/helper-annotate-as-pure", + ":node_modules/@babel/helper-split-export-declaration", + ":node_modules/@inquirer/confirm", + ":node_modules/@vitejs/plugin-basic-ssl", + ":node_modules/beasties", + ":node_modules/browserslist", + ":node_modules/https-proxy-agent", + ":node_modules/istanbul-lib-instrument", + ":node_modules/jsonc-parser", + ":node_modules/less", + ":node_modules/listr2", + ":node_modules/lmdb", + ":node_modules/magic-string", + ":node_modules/mrmime", + ":node_modules/ng-packagr", + ":node_modules/parse5-html-rewriting-stream", + ":node_modules/picomatch", + ":node_modules/piscina", + ":node_modules/postcss", + ":node_modules/rolldown", + ":node_modules/sass", + ":node_modules/source-map-support", + ":node_modules/tinyglobby", + ":node_modules/undici", + ":node_modules/vite", + ":node_modules/vitest", + ":node_modules/watchpack", + "//:node_modules/@angular/common", + "//:node_modules/@angular/compiler", + "//:node_modules/@angular/compiler-cli", + "//:node_modules/@angular/core", + "//:node_modules/@angular/localize", + "//:node_modules/@angular/platform-browser", + "//:node_modules/@angular/platform-server", + "//:node_modules/@angular/service-worker", + "//:node_modules/@types/babel__core", + "//:node_modules/@types/karma", + "//:node_modules/@types/less", + "//:node_modules/@types/node", + "//:node_modules/@types/picomatch", + "//:node_modules/@types/semver", + "//:node_modules/@types/watchpack", + "//:node_modules/esbuild", + "//:node_modules/esbuild-wasm", + "//:node_modules/karma", + "//:node_modules/semver", + "//:node_modules/tslib", + "//:node_modules/typescript", + ], +) + +ts_project( + name = "unit_test_lib", + testonly = True, + srcs = glob( + include = ["src/**/*_spec.ts"], + exclude = ["src/builders/**/tests/**"], + ), + deps = [ + ":build", + ":node_modules/@angular-devkit/core", + ":node_modules/@babel/core", + "//:node_modules/@angular/compiler-cli", + "//:node_modules/@types/jasmine", + "//:node_modules/prettier", + "//:node_modules/typescript", + "//packages/angular/build/private", + ], +) + +jasmine_test( + name = "test", + data = [":unit_test_lib"], +) + +ts_project( + name = "application_integration_test_lib", + testonly = True, + srcs = glob(include = ["src/builders/application/tests/**/*.ts"]), + deps = [ + ":build", + "//packages/angular/build/private", + "//modules/testing/builder", + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + + # Base dependencies for the application in hello-world-app. + "//:node_modules/@angular/common", + "//:node_modules/@angular/compiler", + "//:node_modules/@angular/compiler-cli", + "//:node_modules/@angular/core", + "//:node_modules/@angular/platform-browser", + "//:node_modules/@angular/router", + ":node_modules/rxjs", + "//:node_modules/tslib", + "//:node_modules/typescript", + "//:node_modules/zone.js", + "//:node_modules/buffer", + ], +) + +ts_project( + name = "dev-server_integration_test_lib", + testonly = True, + srcs = glob(include = ["src/builders/dev-server/tests/**/*.ts"]), + deps = [ + ":build", + "//packages/angular/build/private", + "//modules/testing/builder", + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + + # dev server only test deps + "//:node_modules/@types/http-proxy", + "//:node_modules/@types/node", + "//:node_modules/http-proxy", + "//:node_modules/puppeteer", + + # Base dependencies for the application in hello-world-app. + "//:node_modules/@angular/common", + "//:node_modules/@angular/compiler", + "//:node_modules/@angular/compiler-cli", + "//:node_modules/@angular/core", + "//:node_modules/@angular/platform-browser", + "//:node_modules/@angular/router", + ":node_modules/ng-packagr", + ":node_modules/rxjs", + "//:node_modules/tslib", + "//:node_modules/typescript", + "//:node_modules/zone.js", + "//:node_modules/buffer", + ], +) + +ts_project( + name = "karma_integration_test_lib", + testonly = True, + srcs = glob(include = ["src/builders/karma/tests/**/*.ts"]), + deps = [ + ":build", + "//packages/angular/build/private", + "//modules/testing/builder", + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + "//:node_modules/@types/node", + + # karma specific test deps + "//:node_modules/karma-chrome-launcher", + "//:node_modules/karma-coverage", + "//:node_modules/karma-jasmine", + "//:node_modules/karma-jasmine-html-reporter", + "//:node_modules/puppeteer", + + # Base dependencies for the karma in hello-world-app. + "//:node_modules/@angular/common", + "//:node_modules/@angular/compiler", + "//:node_modules/@angular/compiler-cli", + "//:node_modules/@angular/core", + "//:node_modules/@angular/platform-browser", + "//:node_modules/@angular/router", + ":node_modules/rxjs", + "//:node_modules/tslib", + "//:node_modules/typescript", + "//:node_modules/zone.js", + "//:node_modules/buffer", + ], +) + +ts_project( + name = "unit-test_integration_test_lib", + testonly = True, + srcs = glob(include = ["src/builders/unit-test/tests/**/*.ts"]), + deps = [ + ":build", + "//packages/angular/build/private", + "//modules/testing/builder", + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + "//:node_modules/@types/node", + + # unit test specific test deps + ":node_modules/vitest", + ":node_modules/jsdom", + + # Base dependencies for the hello-world-app. + "//:node_modules/@angular/common", + "//:node_modules/@angular/compiler", + "//:node_modules/@angular/compiler-cli", + "//:node_modules/@angular/core", + "//:node_modules/@angular/platform-browser", + "//:node_modules/@angular/router", + ":node_modules/rxjs", + "//:node_modules/tslib", + "//:node_modules/typescript", + "//:node_modules/zone.js", + ], +) + +jasmine_test( + name = "application_integration_tests", + size = "medium", + data = [":application_integration_test_lib"], + flaky = True, + shard_count = 25, +) + +jasmine_test( + name = "dev-server_integration_tests", + size = "medium", + data = [":dev-server_integration_test_lib"], + env = { + # Force IPv4 to resolve RBE resolution issues + "NODE_OPTIONS": "--dns-result-order=ipv4first", + }, + flaky = True, + shard_count = 10, +) + +jasmine_test( + name = "karma_integration_tests", + size = "medium", + data = [":karma_integration_test_lib"], + env = { + # TODO: Replace Puppeteer downloaded browsers with Bazel-managed browsers, + # or standardize to avoid complex configuration like this! + "PUPPETEER_DOWNLOAD_PATH": "../../../node_modules/puppeteer/downloads", + }, + flaky = True, + shard_count = 10, +) + +jasmine_test( + name = "unit-test_integration_tests", + size = "medium", + data = [":unit-test_integration_test_lib"], + flaky = True, + shard_count = 5, +) + +genrule( + name = "license", + srcs = ["//:LICENSE"], + outs = ["LICENSE"], + cmd = "cp $(execpath //:LICENSE) $@", +) + +npm_package( + name = "pkg", + pkg_deps = [ + "//packages/angular_devkit/architect:package.json", + ], + stamp_files = [ + "src/utils/version.js", + "src/tools/esbuild/utils.js", + "src/utils/normalize-cache.js", + "src/utils/supported-browsers.js", + ], + tags = ["release-package"], + deps = RUNTIME_ASSETS + [ + ":README.md", + ":build", + ":license", + "//packages/angular/build/private", + ], +) + +api_golden_test_npm_package( + name = "api", + data = [ + ":npm_package", + "//goldens:public-api", + ], + golden_dir = "goldens/public-api/angular/build", + npm_package = "packages/angular/build/npm_package", +) diff --git a/packages/angular/build/README.md b/packages/angular/build/README.md new file mode 100644 index 000000000000..62249f7fe422 --- /dev/null +++ b/packages/angular/build/README.md @@ -0,0 +1,5 @@ +# Angular Build System for Applications and Libraries + +The sources for this package are in the [Angular CLI](https://github.com/angular/angular-cli) repository. Please file issues and pull requests against that repository. + +Usage information and reference details can be found in repository [README](https://github.com/angular/angular-cli/blob/main/README.md) file. diff --git a/packages/angular/build/builders.json b/packages/angular/build/builders.json new file mode 100644 index 000000000000..4dc9c9c245a6 --- /dev/null +++ b/packages/angular/build/builders.json @@ -0,0 +1,34 @@ +{ + "builders": { + "application": { + "implementation": "./src/builders/application/index", + "schema": "./src/builders/application/schema.json", + "description": "Build an application." + }, + "dev-server": { + "implementation": "./src/builders/dev-server/index", + "schema": "./src/builders/dev-server/schema.json", + "description": "Execute a development server for an application." + }, + "extract-i18n": { + "implementation": "./src/builders/extract-i18n/index", + "schema": "./src/builders/extract-i18n/schema.json", + "description": "Extract i18n messages from an application." + }, + "karma": { + "implementation": "./src/builders/karma", + "schema": "./src/builders/karma/schema.json", + "description": "Run Karma unit tests." + }, + "ng-packagr": { + "implementation": "./src/builders/ng-packagr/index", + "schema": "./src/builders/ng-packagr/schema.json", + "description": "Build a library with ng-packagr." + }, + "unit-test": { + "implementation": "./src/builders/unit-test", + "schema": "./src/builders/unit-test/schema.json", + "description": "[EXPERIMENTAL] Run application unit tests." + } + } +} diff --git a/packages/angular/build/index.ts b/packages/angular/build/index.ts new file mode 100644 index 000000000000..e6da94cc7ded --- /dev/null +++ b/packages/angular/build/index.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.dev/license + */ + +export * from './src/index'; diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json new file mode 100644 index 000000000000..22e5e6b4b1be --- /dev/null +++ b/packages/angular/build/package.json @@ -0,0 +1,118 @@ +{ + "name": "@angular/build", + "version": "0.0.0-PLACEHOLDER", + "description": "Official build system for Angular", + "keywords": [ + "angular", + "angular-cli" + ], + "exports": { + ".": { + "types": "./src/index.d.ts", + "default": "./src/index.js" + }, + "./private": { + "default": "./src/private.js" + }, + "./package.json": "./package.json" + }, + "builders": "builders.json", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "workspace:0.0.0-EXPERIMENTAL-PLACEHOLDER", + "@babel/core": "7.28.5", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.21", + "@vitejs/plugin-basic-ssl": "2.1.0", + "beasties": "0.3.5", + "browserslist": "^4.26.0", + "esbuild": "0.27.1", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "magic-string": "0.30.21", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.3", + "piscina": "5.1.4", + "rolldown": "1.0.0-beta.54", + "sass": "1.97.0", + "semver": "7.7.3", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.15", + "undici": "7.16.0", + "vite": "7.3.0", + "watchpack": "2.4.4" + }, + "optionalDependencies": { + "lmdb": "3.4.4" + }, + "devDependencies": { + "@angular-devkit/core": "workspace:*", + "@angular/ssr": "workspace:*", + "jsdom": "27.3.0", + "less": "4.4.2", + "ng-packagr": "21.1.0-next.0", + "postcss": "8.5.6", + "rxjs": "7.8.2", + "vitest": "4.0.15" + }, + "peerDependencies": { + "@angular/compiler": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/compiler-cli": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/core": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/localize": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/platform-browser": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/platform-server": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/service-worker": "0.0.0-ANGULAR-FW-PEER-DEP", + "@angular/ssr": "^0.0.0-PLACEHOLDER", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "0.0.0-NG-PACKAGR-PEER-DEP", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.9 <6.0", + "vitest": "^4.0.8" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } +} diff --git a/packages/angular/build/private/BUILD.bazel b/packages/angular/build/private/BUILD.bazel new file mode 100644 index 000000000000..c3a100de897f --- /dev/null +++ b/packages/angular/build/private/BUILD.bazel @@ -0,0 +1,11 @@ +load("//tools:defaults.bzl", "ts_project") + +package(default_visibility = ["//visibility:public"]) + +ts_project( + name = "private", + srcs = ["index.ts"], + deps = [ + "//packages/angular/build", + ], +) diff --git a/packages/angular/build/private/index.ts b/packages/angular/build/private/index.ts new file mode 100644 index 000000000000..1c2b76656baf --- /dev/null +++ b/packages/angular/build/private/index.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.dev/license + */ + +export * from '../src/private'; diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts new file mode 100644 index 000000000000..afc59785be7d --- /dev/null +++ b/packages/angular/build/src/builders/application/build-action.ts @@ -0,0 +1,436 @@ +/** + * @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.dev/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import { existsSync } from 'node:fs'; +import path from 'node:path'; +import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context'; +import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result'; +import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language'; +import { logMessages, withNoProgress, withSpinner } from '../../tools/esbuild/utils'; +import { ChangedFiles } from '../../tools/esbuild/watcher'; +import { shouldWatchRoot } from '../../utils/environment-options'; +import { NormalizedCachedOptions } from '../../utils/normalize-cache'; +import { toPosixPath } from '../../utils/path'; +import { NormalizedApplicationBuildOptions, NormalizedOutputOptions } from './options'; +import { + ComponentUpdateResult, + FullResult, + IncrementalResult, + Result, + ResultKind, + ResultMessage, +} from './results'; + +// Watch workspace for package manager changes +const packageWatchFiles = [ + // manifest can affect module resolution + 'package.json', + // npm lock file + 'package-lock.json', + // pnpm lock file + 'pnpm-lock.yaml', + // yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/) + 'yarn.lock', + '.pnp.cjs', + '.pnp.data.json', +]; + +export async function* runEsBuildBuildAction( + action: (rebuildState?: RebuildState) => Promise, + options: { + workspaceRoot: string; + projectRoot: string; + outputOptions: NormalizedOutputOptions; + logger: BuilderContext['logger']; + cacheOptions: NormalizedCachedOptions; + watch?: boolean; + verbose?: boolean; + progress?: boolean; + poll?: number; + signal?: AbortSignal; + preserveSymlinks?: boolean; + clearScreen?: boolean; + colors?: boolean; + jsonLogs?: boolean; + incrementalResults?: boolean; + }, +): AsyncIterable { + const { + watch, + poll, + clearScreen, + logger, + cacheOptions, + outputOptions, + verbose, + projectRoot, + workspaceRoot, + progress, + preserveSymlinks, + colors, + jsonLogs, + incrementalResults, + } = options; + + const withProgress: typeof withSpinner = progress ? withSpinner : withNoProgress; + + // Initial build + let result: ExecutionResult; + try { + // Perform the build action + result = await withProgress('Building...', () => action()); + + // Log all diagnostic (error/warning/logs) messages + await logMessages(logger, result, colors, jsonLogs); + } finally { + // Ensure Sass workers are shutdown if not watching + if (!watch) { + shutdownSassWorkerPool(); + } + } + + // Setup watcher if watch mode enabled + let watcher: import('../../tools/esbuild/watcher').BuildWatcher | undefined; + if (watch) { + if (progress) { + logger.info('Watch mode enabled. Watching for file changes...'); + } + + const ignored: string[] = [ + // Ignore the output and cache paths to avoid infinite rebuild cycles + outputOptions.base, + cacheOptions.basePath, + `${toPosixPath(workspaceRoot)}/**/.*/**`, + ]; + + // Setup a watcher + const { createWatcher } = await import('../../tools/esbuild/watcher'); + watcher = createWatcher({ + polling: typeof poll === 'number', + interval: poll, + followSymlinks: preserveSymlinks, + ignored, + }); + + // Setup abort support + options.signal?.addEventListener('abort', () => void watcher?.close()); + + // Watch the entire project root if 'NG_BUILD_WATCH_ROOT' environment variable is set + if (shouldWatchRoot) { + if (!preserveSymlinks) { + // Ignore all node modules directories to avoid excessive file watchers. + // Package changes are handled below by watching manifest and lock files. + // NOTE: this is not enable when preserveSymlinks is true as this would break `npm link` usages. + ignored.push('**/node_modules/**'); + + watcher.add( + packageWatchFiles + .map((file) => path.join(workspaceRoot, file)) + .filter((file) => existsSync(file)), + ); + } + + watcher.add(projectRoot); + } + + // Watch locations provided by the initial build result + watcher.add(result.watchFiles); + } + + // Output the first build results after setting up the watcher to ensure that any code executed + // higher in the iterator call stack will trigger the watcher. This is particularly relevant for + // unit tests which execute the builder and modify the file system programmatically. + yield* emitOutputResults(result, outputOptions); + + // Finish if watch mode is not enabled + if (!watcher) { + return; + } + + // Used to force a full result on next rebuild if there were initial errors. + // This ensures at least one full result is emitted. + let hasInitialErrors = result.errors.length > 0; + + // Wait for changes and rebuild as needed + const currentWatchFiles = new Set(result.watchFiles); + try { + for await (const changes of watcher) { + if (options.signal?.aborted) { + break; + } + + if (clearScreen) { + // eslint-disable-next-line no-console + console.clear(); + } + + if (verbose) { + logger.info(changes.toDebugString()); + } + + // Clear removed files from current watch files + changes.removed.forEach((removedPath) => currentWatchFiles.delete(removedPath)); + + const rebuildState = result.createRebuildState(changes); + result = await withProgress('Changes detected. Rebuilding...', () => action(rebuildState)); + + // Log all diagnostic (error/warning/logs) messages + await logMessages(logger, result, colors, jsonLogs); + + // Update watched locations provided by the new build result. + // Keep watching all previous files if there are any errors; otherwise consider all + // files stale until confirmed present in the new result's watch files. + const staleWatchFiles = result.errors.length > 0 ? undefined : new Set(currentWatchFiles); + for (const watchFile of result.watchFiles) { + if (!currentWatchFiles.has(watchFile)) { + // Add new watch location + watcher.add(watchFile); + currentWatchFiles.add(watchFile); + } + + // Present so remove from stale locations + staleWatchFiles?.delete(watchFile); + } + // Remove any stale locations if the build was successful + if (staleWatchFiles?.size) { + watcher.remove([...staleWatchFiles]); + } + + for (const outputResult of emitOutputResults( + result, + outputOptions, + changes, + incrementalResults && !hasInitialErrors ? rebuildState : undefined, + )) { + yield outputResult; + } + + // Clear initial build errors flag if no errors are now present + hasInitialErrors &&= result.errors.length > 0; + } + } finally { + // Stop the watcher and cleanup incremental rebuild state + await Promise.allSettled([watcher.close(), result.dispose()]); + + shutdownSassWorkerPool(); + } +} + +function* emitOutputResults( + { + outputFiles, + assetFiles, + errors, + warnings, + externalMetadata, + htmlIndexPath, + htmlBaseHref, + templateUpdates, + }: ExecutionResult, + outputOptions: NormalizedApplicationBuildOptions['outputOptions'], + changes?: ChangedFiles, + rebuildState?: RebuildState, +): Iterable { + if (errors.length > 0) { + yield { + kind: ResultKind.Failure, + errors: errors as ResultMessage[], + warnings: warnings as ResultMessage[], + detail: { + outputOptions, + }, + }; + + return; + } + + // Use a full result if there is no rebuild state (no prior build result) + if (!rebuildState || !changes) { + const result: FullResult = { + kind: ResultKind.Full, + warnings: warnings as ResultMessage[], + files: {}, + detail: { + externalMetadata, + htmlIndexPath, + htmlBaseHref, + outputOptions, + }, + }; + for (const file of assetFiles) { + result.files[file.destination] = { + type: BuildOutputFileType.Browser, + inputPath: file.source, + origin: 'disk', + }; + } + for (const file of outputFiles) { + result.files[file.path] = { + type: file.type, + contents: file.contents, + origin: 'memory', + hash: file.hash, + }; + } + + yield result; + + return; + } + + // Template updates only exist if no other JS changes have occurred. + // A full page reload may be required based on the following incremental output change analysis. + const hasTemplateUpdates = !!templateUpdates?.size; + + // Use an incremental result if previous output information is available + const { previousAssetsInfo, previousOutputInfo } = rebuildState; + + const incrementalResult: IncrementalResult = { + kind: ResultKind.Incremental, + warnings: warnings as ResultMessage[], + // Initially attempt to use a background update of files to support component updates. + background: hasTemplateUpdates, + added: [], + removed: [], + modified: [], + files: {}, + detail: { + externalMetadata, + htmlIndexPath, + htmlBaseHref, + outputOptions, + }, + }; + + let hasCssUpdates = false; + + // Initially assume all previous output files have been removed + const removedOutputFiles = new Map(previousOutputInfo); + for (const file of outputFiles) { + removedOutputFiles.delete(file.path); + + const previousHash = previousOutputInfo.get(file.path)?.hash; + let needFile = false; + if (previousHash === undefined) { + needFile = true; + incrementalResult.added.push(file.path); + } else if (previousHash !== file.hash) { + needFile = true; + incrementalResult.modified.push(file.path); + } + + if (needFile) { + if (file.path.endsWith('.css')) { + hasCssUpdates = true; + } else if (!canBackgroundUpdate(file)) { + incrementalResult.background = false; + } + + incrementalResult.files[file.path] = { + type: file.type, + contents: file.contents, + origin: 'memory', + hash: file.hash, + }; + } + } + + // Initially assume all previous assets files have been removed + const removedAssetFiles = new Map(previousAssetsInfo); + for (const { source, destination } of assetFiles) { + removedAssetFiles.delete(source); + + if (!previousAssetsInfo.has(source)) { + incrementalResult.added.push(destination); + incrementalResult.background = false; + } else if (changes.modified.has(source)) { + incrementalResult.modified.push(destination); + incrementalResult.background = false; + } else { + continue; + } + + hasCssUpdates ||= destination.endsWith('.css'); + + incrementalResult.files[destination] = { + type: BuildOutputFileType.Browser, + inputPath: source, + origin: 'disk', + }; + } + + // Do not remove stale files yet if there are template updates. + // Component chunk files may still be referenced in running browser code. + // Module evaluation time component updates will update any of these files. + // This typically occurs when a lazy component is changed that has not yet + // been accessed at runtime. + if (hasTemplateUpdates && incrementalResult.background) { + removedOutputFiles.clear(); + } + + // Include the removed output and asset files + incrementalResult.removed.push( + ...Array.from(removedOutputFiles, ([file, { type }]) => ({ + path: file, + type, + })), + ...Array.from(removedAssetFiles.values(), (file) => ({ + path: file, + type: BuildOutputFileType.Browser, + })), + ); + + yield incrementalResult; + + // If there are template updates and the incremental update was background only, a component + // update is possible. + if (hasTemplateUpdates && incrementalResult.background) { + // Template changes may be accompanied by stylesheet changes and these should also be updated hot when possible. + if (hasCssUpdates) { + const styleResult: IncrementalResult = { + kind: ResultKind.Incremental, + added: incrementalResult.added.filter(isCssFilePath), + removed: incrementalResult.removed.filter(({ path }) => isCssFilePath(path)), + modified: incrementalResult.modified.filter(isCssFilePath), + files: Object.fromEntries( + Object.entries(incrementalResult.files).filter(([path]) => isCssFilePath(path)), + ), + }; + + yield styleResult; + } + + const updateResult: ComponentUpdateResult = { + kind: ResultKind.ComponentUpdate, + updates: Array.from(templateUpdates, ([id, content]) => ({ + type: 'template', + id, + content, + })), + }; + + yield updateResult; + } +} + +function isCssFilePath(filePath: string): boolean { + return /\.css(?:\.map)?$/i.test(filePath); +} + +function canBackgroundUpdate(file: BuildOutputFile): boolean { + // Files in the output root are not served and do not affect the + // application available with the development server. + if (file.type === BuildOutputFileType.Root) { + return true; + } + + // Updates to non-JS files must signal an update with the dev server + // except the service worker configuration which is special cased. + return /(?:\.m?js|\.map)$/.test(file.path) || file.path === 'ngsw.json'; +} diff --git a/packages/angular/build/src/builders/application/chunk-optimizer.ts b/packages/angular/build/src/builders/application/chunk-optimizer.ts new file mode 100644 index 000000000000..e6827479b784 --- /dev/null +++ b/packages/angular/build/src/builders/application/chunk-optimizer.ts @@ -0,0 +1,355 @@ +/** + * @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.dev/license + */ + +/** + * @fileoverview This file provides a function to optimize JavaScript chunks using rolldown. + * It is designed to be used after an esbuild build to further optimize the output. + * The main function, `optimizeChunks`, takes the result of an esbuild build, + * identifies the main browser entry point, and then uses rolldown to rebundle + * and optimize the chunks. This process can result in smaller and more efficient + * code by combining and restructuring the original chunks. The file also includes + * helper functions to convert rolldown's output into an esbuild-compatible + * metafile, allowing for consistent analysis and reporting of the build output. + */ + +import type { Message, Metafile } from 'esbuild'; +import assert from 'node:assert'; +import { type OutputAsset, type OutputChunk, rolldown } from 'rolldown'; +import { + BuildOutputFile, + BuildOutputFileType, + BundleContextResult, + InitialFileRecord, +} from '../../tools/esbuild/bundler-context'; +import { createOutputFile } from '../../tools/esbuild/utils'; +import { assertIsError } from '../../utils/error'; + +/** + * Converts the output of a rolldown build into an esbuild-compatible metafile. + * @param rolldownOutput The output of a rolldown build. + * @param originalMetafile The original esbuild metafile from the build. + * @returns An esbuild-compatible metafile. + */ +function rolldownToEsbuildMetafile( + rolldownOutput: (OutputChunk | OutputAsset)[], + originalMetafile: Metafile, +): Metafile { + const newMetafile: Metafile = { + inputs: originalMetafile.inputs, + outputs: {}, + }; + + const intermediateChunkSizes: Record = {}; + for (const [path, output] of Object.entries(originalMetafile.outputs)) { + intermediateChunkSizes[path] = Object.values(output.inputs).reduce( + (s, i) => s + i.bytesInOutput, + 0, + ); + } + + for (const chunk of rolldownOutput) { + if (chunk.type === 'asset') { + newMetafile.outputs[chunk.fileName] = { + bytes: + typeof chunk.source === 'string' + ? Buffer.byteLength(chunk.source, 'utf8') + : chunk.source.length, + inputs: {}, + imports: [], + exports: [], + }; + continue; + } + + const newOutputInputs: Record = {}; + if (chunk.modules) { + for (const [moduleId, renderedModule] of Object.entries(chunk.modules)) { + const originalOutputEntry = originalMetafile.outputs[moduleId]; + if (!originalOutputEntry?.inputs) { + continue; + } + + const totalOriginalBytesInModule = intermediateChunkSizes[moduleId]; + if (totalOriginalBytesInModule === 0) { + continue; + } + + for (const [originalInputPath, originalInputInfo] of Object.entries( + originalOutputEntry.inputs, + )) { + const proportion = originalInputInfo.bytesInOutput / totalOriginalBytesInModule; + const newBytesInOutput = Math.floor(renderedModule.renderedLength * proportion); + + const existing = newOutputInputs[originalInputPath]; + if (existing) { + existing.bytesInOutput += newBytesInOutput; + } else { + newOutputInputs[originalInputPath] = { bytesInOutput: newBytesInOutput }; + } + + if (!newMetafile.inputs[originalInputPath]) { + newMetafile.inputs[originalInputPath] = originalMetafile.inputs[originalInputPath]; + } + } + } + } + + const imports = [ + ...chunk.imports.map((path) => ({ path, kind: 'import-statement' as const })), + ...(chunk.dynamicImports?.map((path) => ({ path, kind: 'dynamic-import' as const })) ?? []), + ]; + + newMetafile.outputs[chunk.fileName] = { + bytes: Buffer.byteLength(chunk.code, 'utf8'), + inputs: newOutputInputs, + imports, + exports: chunk.exports ?? [], + entryPoint: + chunk.isEntry && chunk.facadeModuleId + ? originalMetafile.outputs[chunk.facadeModuleId]?.entryPoint + : undefined, + }; + } + + return newMetafile; +} + +/** + * Creates an InitialFileRecord object with a specified depth. + * @param depth The depth of the file in the dependency graph. + * @returns An InitialFileRecord object. + */ +function createInitialFileRecord(depth: number): InitialFileRecord { + return { + type: 'script', + entrypoint: false, + external: false, + serverFile: false, + depth, + }; +} + +/** + * Creates an esbuild message object for a chunk optimization failure. + * @param message The error message detailing the cause of the failure. + * @returns A partial esbuild message object. + */ +function createChunkOptimizationFailureMessage(message: string): Message { + // Most of these fields are not actually needed for printing the error + return { + id: '', + text: 'Chunk optimization failed', + detail: undefined, + pluginName: '', + location: null, + notes: [ + { + text: message, + location: null, + }, + ], + }; +} + +/** + * Optimizes the chunks of a build result using rolldown. + * + * This function takes the output of an esbuild build, identifies the main browser entry point, + * and uses rolldown to bundle and optimize the JavaScript chunks. The optimized chunks + * replace the original ones in the build result, and the metafile is updated to reflect + * the changes. + * + * @param original The original build result from esbuild. + * @param sourcemap A boolean or 'hidden' to control sourcemap generation. + * @returns A promise that resolves to the updated build result with optimized chunks. + */ +export async function optimizeChunks( + original: BundleContextResult, + sourcemap: boolean | 'hidden', +): Promise { + // Failed builds cannot be optimized + if (original.errors) { + return original; + } + + // Find the main browser entrypoint + let mainFile; + for (const [file, record] of original.initialFiles) { + if ( + record.name === 'main' && + record.entrypoint && + !record.serverFile && + record.type === 'script' + ) { + mainFile = file; + break; + } + } + + // No action required if no browser main entrypoint or metafile for stats + if (!mainFile || !original.metafile) { + return original; + } + + const chunks: Record = {}; + const maps: Record = {}; + for (const originalFile of original.outputFiles) { + if (originalFile.type !== BuildOutputFileType.Browser) { + continue; + } + + if (originalFile.path.endsWith('.js')) { + chunks[originalFile.path] = originalFile; + } else if (originalFile.path.endsWith('.js.map')) { + // Create mapping of JS file to sourcemap content + maps[originalFile.path.slice(0, -4)] = originalFile; + } + } + + const usedChunks = new Set(); + + let bundle; + let optimizedOutput; + try { + bundle = await rolldown({ + input: mainFile, + plugins: [ + { + name: 'angular-bundle', + resolveId(source) { + // Remove leading `./` if present + const file = source[0] === '.' && source[1] === '/' ? source.slice(2) : source; + + if (chunks[file]) { + return file; + } + + // All other identifiers are considered external to maintain behavior + return { id: source, external: true }; + }, + load(id) { + assert( + chunks[id], + `Angular chunk content should always be present in chunk optimizer [${id}].`, + ); + + usedChunks.add(id); + + const result = { + code: chunks[id].text, + map: maps[id]?.text, + }; + + return result; + }, + }, + ], + }); + + const result = await bundle.generate({ + minify: { mangle: false, compress: false }, + sourcemap, + chunkFileNames: (chunkInfo) => `${chunkInfo.name.replace(/-[a-zA-Z0-9]{8}$/, '')}-[hash].js`, + }); + optimizedOutput = result.output; + } catch (e) { + assertIsError(e); + + return { + errors: [createChunkOptimizationFailureMessage(e.message)], + warnings: original.warnings, + }; + } finally { + await bundle?.close(); + } + + // Update metafile + const newMetafile = rolldownToEsbuildMetafile(optimizedOutput, original.metafile); + // Add back the outputs that were not part of the optimization + for (const [path, output] of Object.entries(original.metafile.outputs)) { + if (usedChunks.has(path)) { + continue; + } + + newMetafile.outputs[path] = output; + for (const inputPath of Object.keys(output.inputs)) { + if (!newMetafile.inputs[inputPath]) { + newMetafile.inputs[inputPath] = original.metafile.inputs[inputPath]; + } + } + } + original.metafile = newMetafile; + + // Remove used chunks and associated sourcemaps from the original result + original.outputFiles = original.outputFiles.filter( + (file) => + !usedChunks.has(file.path) && + !(file.path.endsWith('.map') && usedChunks.has(file.path.slice(0, -4))), + ); + + // Add new optimized chunks + const importsPerFile: Record = {}; + for (const optimizedFile of optimizedOutput) { + if (optimizedFile.type !== 'chunk') { + continue; + } + + importsPerFile[optimizedFile.fileName] = optimizedFile.imports; + + original.outputFiles.push( + createOutputFile(optimizedFile.fileName, optimizedFile.code, BuildOutputFileType.Browser), + ); + if (optimizedFile.map && optimizedFile.sourcemapFileName) { + original.outputFiles.push( + createOutputFile( + optimizedFile.sourcemapFileName, + optimizedFile.map.toString(), + BuildOutputFileType.Browser, + ), + ); + } + } + + // Update initial files to reflect optimized chunks + const entriesToAnalyze: [string, InitialFileRecord][] = []; + for (const usedFile of usedChunks) { + // Leave the main file since its information did not change + if (usedFile === mainFile) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + entriesToAnalyze.push([mainFile, original.initialFiles.get(mainFile)!]); + continue; + } + + // Remove all other used chunks + original.initialFiles.delete(usedFile); + } + + // Analyze for transitive initial files + let currentEntry; + while ((currentEntry = entriesToAnalyze.pop())) { + const [entryPath, entryRecord] = currentEntry; + + for (const importPath of importsPerFile[entryPath]) { + const existingRecord = original.initialFiles.get(importPath); + if (existingRecord) { + // Store the smallest value depth + if (existingRecord.depth > entryRecord.depth + 1) { + existingRecord.depth = entryRecord.depth + 1; + } + + continue; + } + + const record = createInitialFileRecord(entryRecord.depth + 1); + + entriesToAnalyze.push([importPath, record]); + } + } + + return original; +} diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts new file mode 100644 index 000000000000..0654cd965558 --- /dev/null +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -0,0 +1,331 @@ +/** + * @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.dev/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import { createAngularCompilation } from '../../tools/angular/compilation'; +import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache'; +import { generateBudgetStats } from '../../tools/esbuild/budget-stats'; +import { + BuildOutputFileType, + BundleContextResult, + BundlerContext, +} from '../../tools/esbuild/bundler-context'; +import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result'; +import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker'; +import { extractLicenses } from '../../tools/esbuild/license-extractor'; +import { profileAsync } from '../../tools/esbuild/profiling'; +import { + calculateEstimatedTransferSizes, + logBuildStats, + transformSupportedBrowsersToTargets, +} from '../../tools/esbuild/utils'; +import { BudgetCalculatorResult, checkBudgets } from '../../utils/bundle-calculator'; +import { shouldOptimizeChunks } from '../../utils/environment-options'; +import { resolveAssets } from '../../utils/resolve-assets'; +import { + SERVER_APP_ENGINE_MANIFEST_FILENAME, + generateAngularServerAppEngineManifest, +} from '../../utils/server-rendering/manifest'; +import { getSupportedBrowsers } from '../../utils/supported-browsers'; +import { executePostBundleSteps } from './execute-post-bundle'; +import { inlineI18n, loadActiveTranslations } from './i18n'; +import { NormalizedApplicationBuildOptions } from './options'; +import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling'; + +// eslint-disable-next-line max-lines-per-function +export async function executeBuild( + options: NormalizedApplicationBuildOptions, + context: BuilderContext, + rebuildState?: RebuildState, +): Promise { + const { + projectRoot, + workspaceRoot, + i18nOptions, + optimizationOptions, + assets, + cacheOptions, + serverEntryPoint, + baseHref, + ssrOptions, + verbose, + colors, + jsonLogs, + } = options; + + // TODO: Consider integrating into watch mode. Would require full rebuild on target changes. + const browsers = getSupportedBrowsers(projectRoot, context.logger); + + // Load active translations if inlining + // TODO: Integrate into watch mode and only load changed translations + if (i18nOptions.shouldInline) { + await loadActiveTranslations(context, i18nOptions); + } + + // Reuse rebuild state or create new bundle contexts for code and global stylesheets + let bundlerContexts; + let componentStyleBundler; + let codeBundleCache; + let bundlingResult: BundleContextResult; + let templateUpdates: Map | undefined; + if (rebuildState) { + bundlerContexts = rebuildState.rebuildContexts; + componentStyleBundler = rebuildState.componentStyleBundler; + codeBundleCache = rebuildState.codeBundleCache; + templateUpdates = rebuildState.templateUpdates; + // Reset template updates for new rebuild + templateUpdates?.clear(); + + const allFileChanges = rebuildState.fileChanges.all; + + // Bundle all contexts that do not require TypeScript changed file checks. + // These will automatically use cached results based on the changed files. + bundlingResult = await BundlerContext.bundleAll(bundlerContexts.otherContexts, allFileChanges); + + // Check the TypeScript code bundling cache for changes. If invalid, force a rebundle of + // all TypeScript related contexts. + const forceTypeScriptRebuild = codeBundleCache?.invalidate(allFileChanges); + const typescriptResults: BundleContextResult[] = []; + for (const typescriptContext of bundlerContexts.typescriptContexts) { + typescriptContext.invalidate(allFileChanges); + const result = await typescriptContext.bundle(forceTypeScriptRebuild); + typescriptResults.push(result); + } + bundlingResult = BundlerContext.mergeResults([bundlingResult, ...typescriptResults]); + } else { + const target = transformSupportedBrowsersToTargets(browsers); + codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined); + componentStyleBundler = createComponentStyleBundler(options, target); + if (options.templateUpdates) { + templateUpdates = new Map(); + } + bundlerContexts = setupBundlerContexts( + options, + target, + codeBundleCache, + componentStyleBundler, + // Create new reusable compilation for the appropriate mode based on the `jit` plugin option + await createAngularCompilation(!!options.jit, !options.serverEntryPoint), + templateUpdates, + ); + + // Bundle everything on initial build + bundlingResult = await BundlerContext.bundleAll([ + ...bundlerContexts.typescriptContexts, + ...bundlerContexts.otherContexts, + ]); + } + + // Update any external component styles if enabled and rebuilding. + // TODO: Only attempt rebundling of invalidated styles once incremental build results are supported. + if (rebuildState && options.externalRuntimeStyles) { + componentStyleBundler.invalidate(rebuildState.fileChanges.all); + + const componentResults = await componentStyleBundler.bundleAllFiles(true, true); + bundlingResult = BundlerContext.mergeResults([bundlingResult, ...componentResults]); + } + + if (options.optimizationOptions.scripts && shouldOptimizeChunks) { + const { optimizeChunks } = await import('./chunk-optimizer'); + bundlingResult = await profileAsync('OPTIMIZE_CHUNKS', () => + optimizeChunks( + bundlingResult, + options.sourcemapOptions.scripts ? !options.sourcemapOptions.hidden || 'hidden' : false, + ), + ); + } + + const executionResult = new ExecutionResult( + bundlerContexts, + componentStyleBundler, + codeBundleCache, + templateUpdates, + ); + executionResult.addWarnings(bundlingResult.warnings); + + // Add used external component style referenced files to be watched + if (options.externalRuntimeStyles) { + executionResult.extraWatchFiles.push(...componentStyleBundler.collectReferencedFiles()); + } + + // Return if the bundling has errors + if (bundlingResult.errors) { + executionResult.addErrors(bundlingResult.errors); + + return executionResult; + } + + // Analyze external imports if external options are enabled + if (options.externalPackages || bundlingResult.externalConfiguration) { + const { + externalConfiguration = [], + externalImports: { browser = [], server = [] }, + } = bundlingResult; + // Similar to esbuild, --external:@foo/bar automatically implies --external:@foo/bar/*, + // which matches import paths like @foo/bar/baz. + // This means all paths within the @foo/bar package are also marked as external. + const exclusionsPrefixes = externalConfiguration.map((exclusion) => exclusion + '/'); + const exclusions = new Set(externalConfiguration); + const explicitExternal = new Set(); + + const isExplicitExternal = (dep: string): boolean => { + if (exclusions.has(dep)) { + return true; + } + + for (const prefix of exclusionsPrefixes) { + if (dep.startsWith(prefix)) { + return true; + } + } + + return false; + }; + + const implicitBrowser: string[] = []; + for (const dep of browser) { + if (isExplicitExternal(dep)) { + explicitExternal.add(dep); + } else { + implicitBrowser.push(dep); + } + } + + const implicitServer: string[] = []; + for (const dep of server) { + if (isExplicitExternal(dep)) { + explicitExternal.add(dep); + } else { + implicitServer.push(dep); + } + } + + executionResult.setExternalMetadata(implicitBrowser, implicitServer, [...explicitExternal]); + } + + const { metafile, initialFiles, outputFiles } = bundlingResult; + + executionResult.outputFiles.push(...outputFiles); + + // Analyze files for bundle budget failures if present + let budgetFailures: BudgetCalculatorResult[] | undefined; + if (options.budgets) { + const compatStats = generateBudgetStats(metafile, outputFiles, initialFiles); + budgetFailures = [...checkBudgets(options.budgets, compatStats, true)]; + for (const { message, severity } of budgetFailures) { + if (severity === 'error') { + executionResult.addError(message); + } else { + executionResult.addWarning(message); + } + } + } + + // Calculate estimated transfer size if scripts are optimized + let estimatedTransferSizes; + if (optimizationOptions.scripts || optimizationOptions.styles.minify) { + estimatedTransferSizes = await calculateEstimatedTransferSizes(executionResult.outputFiles); + } + + // Check metafile for CommonJS module usage if optimizing scripts + if (optimizationOptions.scripts) { + const messages = checkCommonJSModules(metafile, options.allowedCommonJsDependencies); + executionResult.addWarnings(messages); + } + + // Copy assets + if (assets) { + executionResult.addAssets(await resolveAssets(assets, workspaceRoot)); + } + + // Extract and write licenses for used packages + if (options.extractLicenses) { + executionResult.addOutputFile( + '3rdpartylicenses.txt', + await extractLicenses(metafile, workspaceRoot), + BuildOutputFileType.Root, + ); + } + + // Watch input index HTML file if configured + if (options.indexHtmlOptions) { + executionResult.extraWatchFiles.push(options.indexHtmlOptions.input); + executionResult.htmlIndexPath = options.indexHtmlOptions.output; + executionResult.htmlBaseHref = options.baseHref; + } + + // Create server app engine manifest + if (serverEntryPoint) { + executionResult.addOutputFile( + SERVER_APP_ENGINE_MANIFEST_FILENAME, + generateAngularServerAppEngineManifest(i18nOptions, baseHref), + BuildOutputFileType.ServerRoot, + ); + } + + // Perform i18n translation inlining if enabled + if (i18nOptions.shouldInline) { + const result = await inlineI18n(metafile, options, executionResult, initialFiles); + executionResult.addErrors(result.errors); + executionResult.addWarnings(result.warnings); + executionResult.addPrerenderedRoutes(result.prerenderedRoutes); + } else { + const result = await executePostBundleSteps( + metafile, + options, + executionResult.outputFiles, + executionResult.assetFiles, + initialFiles, + // Set lang attribute to the defined source locale if present + i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined, + ); + + // Deduplicate and add errors and warnings + executionResult.addErrors([...new Set(result.errors)]); + executionResult.addWarnings([...new Set(result.warnings)]); + + executionResult.addPrerenderedRoutes(result.prerenderedRoutes); + executionResult.outputFiles.push(...result.additionalOutputFiles); + executionResult.assetFiles.push(...result.additionalAssets); + } + + executionResult.addOutputFile( + 'prerendered-routes.json', + JSON.stringify({ routes: executionResult.prerenderedRoutes }, null, 2), + BuildOutputFileType.Root, + ); + + // Write metafile if stats option is enabled + if (options.stats) { + executionResult.addOutputFile( + 'stats.json', + JSON.stringify(metafile, null, 2), + BuildOutputFileType.Root, + ); + } + + if (!jsonLogs) { + const changedFiles = + rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo); + executionResult.addLog( + logBuildStats( + metafile, + outputFiles, + initialFiles, + budgetFailures, + colors, + changedFiles, + estimatedTransferSizes, + !!ssrOptions, + verbose, + ), + ); + } + + return executionResult; +} diff --git a/packages/angular/build/src/builders/application/execute-post-bundle.ts b/packages/angular/build/src/builders/application/execute-post-bundle.ts new file mode 100644 index 000000000000..5171ca254d5d --- /dev/null +++ b/packages/angular/build/src/builders/application/execute-post-bundle.ts @@ -0,0 +1,257 @@ +/** + * @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.dev/license + */ + +import type { Metafile } from 'esbuild'; +import assert from 'node:assert'; +import { + BuildOutputFile, + BuildOutputFileType, + InitialFileRecord, +} from '../../tools/esbuild/bundler-context'; +import { + BuildOutputAsset, + PrerenderedRoutesRecord, +} from '../../tools/esbuild/bundler-execution-result'; +import { generateIndexHtml } from '../../tools/esbuild/index-html-generator'; +import { createOutputFile } from '../../tools/esbuild/utils'; +import { maxWorkers } from '../../utils/environment-options'; +import { + SERVER_APP_MANIFEST_FILENAME, + generateAngularServerAppManifest, +} from '../../utils/server-rendering/manifest'; +import { + RouteRenderMode, + WritableSerializableRouteTreeNode, +} from '../../utils/server-rendering/models'; +import { prerenderPages } from '../../utils/server-rendering/prerender'; +import { augmentAppWithServiceWorkerEsbuild } from '../../utils/service-worker'; +import { INDEX_HTML_CSR, INDEX_HTML_SERVER, NormalizedApplicationBuildOptions } from './options'; +import { OutputMode } from './schema'; + +/** + * Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation. + * @param metafile An esbuild metafile object. + * @param options The normalized application builder options used to create the build. + * @param outputFiles The output files of an executed build. + * @param assetFiles The assets of an executed build. + * @param initialFiles A map containing initial file information for the executed build. + * @param locale A language locale to insert in the index.html. + */ +// eslint-disable-next-line max-lines-per-function +export async function executePostBundleSteps( + metafile: Metafile, + options: NormalizedApplicationBuildOptions, + outputFiles: BuildOutputFile[], + assetFiles: BuildOutputAsset[], + initialFiles: Map, + locale: string | undefined, +): Promise<{ + errors: string[]; + warnings: string[]; + additionalOutputFiles: BuildOutputFile[]; + additionalAssets: BuildOutputAsset[]; + prerenderedRoutes: PrerenderedRoutesRecord; +}> { + const additionalAssets: BuildOutputAsset[] = []; + const additionalOutputFiles: BuildOutputFile[] = []; + const allErrors: string[] = []; + const allWarnings: string[] = []; + const prerenderedRoutes: PrerenderedRoutesRecord = {}; + + const { + baseHref = '/', + serviceWorker, + ssrOptions, + indexHtmlOptions, + optimizationOptions, + sourcemapOptions, + outputMode, + serverEntryPoint, + prerenderOptions, + appShellOptions, + publicPath, + workspaceRoot, + partialSSRBuild, + } = options; + + // Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR). + // NOTE: Critical CSS inlining is deliberately omitted here, as it will be handled during server rendering. + // Additionally, when using prerendering or AppShell, the index HTML file may be regenerated. + // To prevent generating duplicate files with the same filename, a `Map` is used to store and manage the files. + const additionalHtmlOutputFiles = new Map(); + + // Generate index HTML file + // If localization is enabled, index generation is handled in the inlining process. + if (indexHtmlOptions) { + const { csrContent, ssrContent, errors, warnings } = await generateIndexHtml( + initialFiles, + outputFiles, + options, + locale, + ); + + allErrors.push(...errors); + allWarnings.push(...warnings); + + additionalHtmlOutputFiles.set( + indexHtmlOptions.output, + createOutputFile(indexHtmlOptions.output, csrContent, BuildOutputFileType.Browser), + ); + + if (ssrContent) { + additionalHtmlOutputFiles.set( + INDEX_HTML_SERVER, + createOutputFile(INDEX_HTML_SERVER, ssrContent, BuildOutputFileType.ServerApplication), + ); + } + } + + // Create server manifest + const initialFilesPaths = new Set(initialFiles.keys()); + if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) { + const { manifestContent, serverAssetsChunks } = generateAngularServerAppManifest( + additionalHtmlOutputFiles, + outputFiles, + optimizationOptions.styles.inlineCritical ?? false, + undefined, + locale, + baseHref, + initialFilesPaths, + metafile, + publicPath, + ); + + additionalOutputFiles.push( + ...serverAssetsChunks, + createOutputFile( + SERVER_APP_MANIFEST_FILENAME, + manifestContent, + BuildOutputFileType.ServerApplication, + ), + ); + } + + // Pre-render (SSG) and App-shell + // If localization is enabled, prerendering is handled in the inlining process. + if ( + !partialSSRBuild && + (prerenderOptions || appShellOptions || (outputMode && serverEntryPoint)) && + !allErrors.length + ) { + assert( + indexHtmlOptions, + 'The "index" option is required when using the "ssg" or "appShell" options.', + ); + + const { output, warnings, errors, serializableRouteTreeNode } = await prerenderPages( + workspaceRoot, + baseHref, + appShellOptions, + prerenderOptions, + [...outputFiles, ...additionalOutputFiles], + assetFiles, + outputMode, + sourcemapOptions.scripts, + maxWorkers, + ); + + allErrors.push(...errors); + allWarnings.push(...warnings); + + const indexHasBeenPrerendered = output[indexHtmlOptions.output]; + for (const [path, { content, appShellRoute }] of Object.entries(output)) { + // Update the index contents with the app shell under these conditions: + // - Replace 'index.html' with the app shell only if it hasn't been prerendered yet. + // - Always replace 'index.csr.html' with the app shell. + let filePath = path; + if (appShellRoute && !indexHasBeenPrerendered) { + if (outputMode !== OutputMode.Server && indexHtmlOptions.output === INDEX_HTML_CSR) { + filePath = 'index.html'; + } else { + filePath = indexHtmlOptions.output; + } + } + + additionalHtmlOutputFiles.set( + filePath, + createOutputFile(filePath, content, BuildOutputFileType.Browser), + ); + } + + const serializableRouteTreeNodeForManifest: WritableSerializableRouteTreeNode = []; + for (const metadata of serializableRouteTreeNode) { + serializableRouteTreeNodeForManifest.push(metadata); + + if (metadata.renderMode === RouteRenderMode.Prerender && !metadata.route.includes('*')) { + prerenderedRoutes[metadata.route] = { headers: metadata.headers }; + } + } + + if (outputMode === OutputMode.Server) { + // Regenerate the manifest to append route tree. This is only needed if SSR is enabled. + const manifest = additionalOutputFiles.find((f) => f.path === SERVER_APP_MANIFEST_FILENAME); + assert(manifest, `${SERVER_APP_MANIFEST_FILENAME} was not found in output files.`); + + const { manifestContent, serverAssetsChunks } = generateAngularServerAppManifest( + additionalHtmlOutputFiles, + outputFiles, + optimizationOptions.styles.inlineCritical ?? false, + serializableRouteTreeNodeForManifest, + locale, + baseHref, + initialFilesPaths, + metafile, + publicPath, + ); + + for (const chunk of serverAssetsChunks) { + const idx = additionalOutputFiles.findIndex(({ path }) => path === chunk.path); + if (idx === -1) { + additionalOutputFiles.push(chunk); + } else { + additionalOutputFiles[idx] = chunk; + } + } + + manifest.contents = new TextEncoder().encode(manifestContent); + } + } + + additionalOutputFiles.push(...additionalHtmlOutputFiles.values()); + + // Augment the application with service worker support + // If localization is enabled, service worker is handled in the inlining process. + if (serviceWorker) { + try { + const serviceWorkerResult = await augmentAppWithServiceWorkerEsbuild( + workspaceRoot, + serviceWorker, + baseHref, + options.indexHtmlOptions?.output, + // Ensure additional files recently added are used + [...outputFiles, ...additionalOutputFiles], + assetFiles, + ); + + additionalOutputFiles.push( + createOutputFile('ngsw.json', serviceWorkerResult.manifest, BuildOutputFileType.Browser), + ); + additionalAssets.push(...serviceWorkerResult.assetFiles); + } catch (error) { + allErrors.push(error instanceof Error ? error.message : `${error}`); + } + } + + return { + errors: allErrors, + warnings: allWarnings, + additionalAssets, + prerenderedRoutes, + additionalOutputFiles, + }; +} diff --git a/packages/angular/build/src/builders/application/i18n.ts b/packages/angular/build/src/builders/application/i18n.ts new file mode 100644 index 000000000000..ae37efa674e4 --- /dev/null +++ b/packages/angular/build/src/builders/application/i18n.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.dev/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import type { Metafile } from 'esbuild'; +import { join } from 'node:path'; +import { BuildOutputFileType, InitialFileRecord } from '../../tools/esbuild/bundler-context'; +import { + ExecutionResult, + PrerenderedRoutesRecord, +} from '../../tools/esbuild/bundler-execution-result'; +import { I18nInliner } from '../../tools/esbuild/i18n-inliner'; +import { maxWorkers } from '../../utils/environment-options'; +import { loadTranslations } from '../../utils/i18n-options'; +import { createTranslationLoader } from '../../utils/load-translations'; +import { executePostBundleSteps } from './execute-post-bundle'; +import { NormalizedApplicationBuildOptions, getLocaleBaseHref } from './options'; + +/** + * Inlines all active locales as specified by the application build options into all + * application JavaScript files created during the build. + * @param metafile An esbuild metafile object. + * @param options The normalized application builder options used to create the build. + * @param executionResult The result of an executed build. + * @param initialFiles A map containing initial file information for the executed build. + */ +export async function inlineI18n( + metafile: Metafile, + options: NormalizedApplicationBuildOptions, + executionResult: ExecutionResult, + initialFiles: Map, +): Promise<{ + errors: string[]; + warnings: string[]; + prerenderedRoutes: PrerenderedRoutesRecord; +}> { + const { i18nOptions, optimizationOptions, baseHref, cacheOptions } = options; + + // Create the multi-threaded inliner with common options and the files generated from the build. + const inliner = new I18nInliner( + { + missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning', + outputFiles: executionResult.outputFiles, + shouldOptimize: optimizationOptions.scripts, + persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined, + }, + maxWorkers, + ); + + const inlineResult: { + errors: string[]; + warnings: string[]; + prerenderedRoutes: PrerenderedRoutesRecord; + } = { + errors: [], + warnings: [], + prerenderedRoutes: {}, + }; + + // For each active locale, use the inliner to process the output files of the build. + const updatedOutputFiles = []; + const updatedAssetFiles = []; + // Root and SSR entry files are not modified. + const unModifiedOutputFiles = executionResult.outputFiles.filter( + ({ type }) => type === BuildOutputFileType.Root || type === BuildOutputFileType.ServerRoot, + ); + + try { + for (const locale of i18nOptions.inlineLocales) { + // A locale specific set of files is returned from the inliner. + const localeInlineResult = await inliner.inlineForLocale( + locale, + i18nOptions.locales[locale].translation, + ); + const localeOutputFiles = localeInlineResult.outputFiles; + inlineResult.errors.push(...localeInlineResult.errors); + inlineResult.warnings.push(...localeInlineResult.warnings); + + const { + errors, + warnings, + additionalAssets, + additionalOutputFiles, + prerenderedRoutes: generatedRoutes, + } = await executePostBundleSteps( + metafile, + { + ...options, + baseHref: getLocaleBaseHref(baseHref, i18nOptions, locale) ?? baseHref, + }, + [...unModifiedOutputFiles, ...localeOutputFiles], + executionResult.assetFiles, + initialFiles, + locale, + ); + + localeOutputFiles.push(...additionalOutputFiles); + inlineResult.errors.push(...errors); + inlineResult.warnings.push(...warnings); + + // Update directory with locale base or subPath + const subPath = i18nOptions.locales[locale].subPath; + if (i18nOptions.flatOutput !== true) { + localeOutputFiles.forEach((file) => { + file.path = join(subPath, file.path); + }); + + for (const assetFile of [...executionResult.assetFiles, ...additionalAssets]) { + updatedAssetFiles.push({ + source: assetFile.source, + destination: join(subPath, assetFile.destination), + }); + } + } else { + executionResult.assetFiles.push(...additionalAssets); + } + + inlineResult.prerenderedRoutes = { ...inlineResult.prerenderedRoutes, ...generatedRoutes }; + updatedOutputFiles.push(...localeOutputFiles); + } + } finally { + await inliner.close(); + } + + // Update the result with all localized files. + executionResult.outputFiles = [ + // Root and SSR entry files are not modified. + ...unModifiedOutputFiles, + // Updated files for each locale. + ...updatedOutputFiles, + ]; + + // Assets are only changed if not using the flat output option + if (!i18nOptions.flatOutput) { + executionResult.assetFiles = updatedAssetFiles; + } + + // Inline any template updates if present + if (executionResult.templateUpdates?.size) { + // The development server only allows a single locale but issue a warning if used programmatically (experimental) + // with multiple locales and template HMR. + if (i18nOptions.inlineLocales.size > 1) { + inlineResult.warnings.push( + `Component HMR updates can only be inlined with a single locale. The first locale will be used.`, + ); + } + const firstLocale = [...i18nOptions.inlineLocales][0]; + + for (const [id, content] of executionResult.templateUpdates) { + const templateUpdateResult = await inliner.inlineTemplateUpdate( + firstLocale, + i18nOptions.locales[firstLocale].translation, + content, + id, + ); + executionResult.templateUpdates.set(id, templateUpdateResult.code); + inlineResult.errors.push(...templateUpdateResult.errors); + inlineResult.warnings.push(...templateUpdateResult.warnings); + } + } + + return inlineResult; +} + +/** + * Loads all active translations using the translation loaders from the `@angular/localize` package. + * @param context The architect builder context for the current build. + * @param i18n The normalized i18n options to use. + */ +export async function loadActiveTranslations( + context: BuilderContext, + i18n: NormalizedApplicationBuildOptions['i18nOptions'], +) { + // Load locale data and translations (if present) + let loader; + for (const [locale, desc] of Object.entries(i18n.locales)) { + if (!i18n.inlineLocales.has(locale) && locale !== i18n.sourceLocale) { + continue; + } + + if (!desc.files.length) { + continue; + } + + loader ??= await createTranslationLoader(); + + loadTranslations( + locale, + desc, + context.workspaceRoot, + loader, + { + warn(message) { + context.logger.warn(message); + }, + error(message) { + throw new Error(message); + }, + }, + undefined, + i18n.duplicateTranslationBehavior, + ); + } +} diff --git a/packages/angular/build/src/builders/application/index.ts b/packages/angular/build/src/builders/application/index.ts new file mode 100644 index 000000000000..b83e3b48f270 --- /dev/null +++ b/packages/angular/build/src/builders/application/index.ts @@ -0,0 +1,285 @@ +/** + * @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.dev/license + */ + +import { Builder, BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import assert from 'node:assert'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { BuildOutputFileType } from '../../tools/esbuild/bundler-context'; +import { createJsonBuildManifest, emitFilesToDisk } from '../../tools/esbuild/utils'; +import { colors as ansiColors } from '../../utils/color'; +import { deleteOutputDir } from '../../utils/delete-output-dir'; +import { bazelEsbuildPluginPath, useJSONBuildLogs } from '../../utils/environment-options'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import { assertCompatibleAngularVersion } from '../../utils/version'; +import { runEsBuildBuildAction } from './build-action'; +import { executeBuild } from './execute-build'; +import { + ApplicationBuilderExtensions, + ApplicationBuilderInternalOptions, + NormalizedOutputOptions, + normalizeOptions, +} from './options'; +import { Result, ResultKind } from './results'; +import { Schema as ApplicationBuilderOptions } from './schema'; + +const isNodeV22orHigher = Number(process.versions.node.split('.', 1)[0]) >= 22; + +export type { ApplicationBuilderOptions }; + +export async function* buildApplicationInternal( + options: ApplicationBuilderInternalOptions, + // TODO: Integrate abort signal support into builder system + context: BuilderContext & { signal?: AbortSignal }, + extensions?: ApplicationBuilderExtensions, +): AsyncIterable { + const { workspaceRoot, logger, target } = context; + + // Check Angular version. + assertCompatibleAngularVersion(workspaceRoot); + + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + // Determine project name from builder context target + const projectName = target?.project; + if (!projectName) { + context.logger.error(`The 'application' builder requires a target to be specified.`); + // Only the vite-based dev server current uses the errors value + yield { kind: ResultKind.Failure, errors: [] }; + + return; + } + + if (bazelEsbuildPluginPath) { + extensions ??= {}; + extensions.codePlugins ??= []; + + const { default: bazelEsbuildPlugin } = await import(bazelEsbuildPluginPath); + extensions.codePlugins.push(bazelEsbuildPlugin); + } + + const normalizedOptions = await normalizeOptions(context, projectName, options, extensions); + + if (!normalizedOptions.outputOptions.ignoreServer) { + const { browser, server } = normalizedOptions.outputOptions; + if (browser === '') { + context.logger.error( + `'outputPath.browser' cannot be configured to an empty string when SSR is enabled.`, + ); + yield { kind: ResultKind.Failure, errors: [] }; + + return; + } + + if (browser === server) { + context.logger.error( + `'outputPath.browser' and 'outputPath.server' cannot be configured to the same value.`, + ); + yield { kind: ResultKind.Failure, errors: [] }; + + return; + } + } + + // Setup an abort controller with a builder teardown if no signal is present + let signal = context.signal; + if (!signal) { + const controller = new AbortController(); + signal = controller.signal; + context.addTeardown(() => controller.abort('builder-teardown')); + } + + yield* runEsBuildBuildAction( + async (rebuildState) => { + const { serverEntryPoint, jsonLogs, partialSSRBuild } = normalizedOptions; + + const startTime = process.hrtime.bigint(); + const result = await executeBuild(normalizedOptions, context, rebuildState); + + if (jsonLogs) { + result.addLog(await createJsonBuildManifest(result, normalizedOptions)); + } else { + if (serverEntryPoint && !partialSSRBuild) { + const prerenderedRoutesLength = Object.keys(result.prerenderedRoutes).length; + let prerenderMsg = `Prerendered ${prerenderedRoutesLength} static route`; + prerenderMsg += prerenderedRoutesLength !== 1 ? 's.' : '.'; + + result.addLog(ansiColors.magenta(prerenderMsg)); + } + + const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9; + const hasError = result.errors.length > 0; + + result.addLog( + `Application bundle generation ${hasError ? 'failed' : 'complete'}.` + + ` [${buildTime.toFixed(3)} seconds] - ${new Date().toISOString()}\n`, + ); + } + + return result; + }, + { + watch: normalizedOptions.watch, + preserveSymlinks: normalizedOptions.preserveSymlinks, + poll: normalizedOptions.poll, + cacheOptions: normalizedOptions.cacheOptions, + outputOptions: normalizedOptions.outputOptions, + verbose: normalizedOptions.verbose, + projectRoot: normalizedOptions.projectRoot, + workspaceRoot: normalizedOptions.workspaceRoot, + progress: normalizedOptions.progress, + clearScreen: normalizedOptions.clearScreen, + colors: normalizedOptions.colors, + jsonLogs: normalizedOptions.jsonLogs, + incrementalResults: normalizedOptions.incrementalResults, + logger, + signal, + }, + ); +} + +/** + * Builds an application using the `application` builder with the provided + * options. + * + * Usage of the `extensions` parameter is NOT supported and may cause unexpected + * build output or build failures. + * + * @experimental Direct usage of this function is considered experimental. + * + * @param options The options defined by the builder's schema to use. + * @param context An Architect builder context instance. + * @param extensions An object contain extension points for the build. + * @returns The build output results of the build. + */ +export async function* buildApplication( + options: ApplicationBuilderOptions, + context: BuilderContext, + extensions?: ApplicationBuilderExtensions, +): AsyncIterable { + let initial = true; + const internalOptions = { ...options, incrementalResults: true }; + for await (const result of buildApplicationInternal(internalOptions, context, extensions)) { + const outputOptions = result.detail?.['outputOptions'] as NormalizedOutputOptions | undefined; + + if (initial) { + initial = false; + + // Clean the output location if requested. + // Output options may not be present if the build failed. + if (outputOptions?.clean) { + await deleteOutputDir(context.workspaceRoot, outputOptions.base, [ + outputOptions.browser, + outputOptions.server, + ]); + } + } + + if (result.kind === ResultKind.Failure) { + yield { success: false }; + continue; + } + + assert(outputOptions, 'Application output options are required for builder usage.'); + assert( + result.kind === ResultKind.Full || result.kind === ResultKind.Incremental, + 'Application build did not provide a file result output.', + ); + + // TODO: Restructure output logging to better handle stdout JSON piping + if (!useJSONBuildLogs) { + context.logger.info(`Output location: ${outputOptions.base}\n`); + } + + // Writes the output files to disk and ensures the containing directories are present + const directoryExists = new Set(); + await emitFilesToDisk(Object.entries(result.files), async ([filePath, file]) => { + if ( + outputOptions.ignoreServer && + (file.type === BuildOutputFileType.ServerApplication || + file.type === BuildOutputFileType.ServerRoot) + ) { + return; + } + + const fullFilePath = generateFullPath(filePath, file.type, outputOptions); + + // Ensure output subdirectories exist + const fileBasePath = path.dirname(fullFilePath); + if (fileBasePath && !directoryExists.has(fileBasePath)) { + await fs.mkdir(fileBasePath, { recursive: true }); + directoryExists.add(fileBasePath); + } + + if (file.origin === 'memory') { + // Write file contents + await fs.writeFile(fullFilePath, file.contents); + } else { + // Copy file contents + if (isNodeV22orHigher) { + // Use newer `cp` API on Node.js 22+ (minimum v22 for CLI is 22.11) + await fs.cp(file.inputPath, fullFilePath, { + mode: fs.constants.COPYFILE_FICLONE, + preserveTimestamps: true, + }); + } else { + // For Node.js 20 use `copyFile` (`cp` is not stable for v20) + // TODO: Remove when Node.js 20 is no longer supported + await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE); + } + } + }); + + // Delete any removed files if incremental + if (result.kind === ResultKind.Incremental && result.removed?.length) { + await Promise.all( + result.removed.map((file) => { + const fullFilePath = generateFullPath(file.path, file.type, outputOptions); + + return fs.rm(fullFilePath, { force: true, maxRetries: 3 }); + }), + ); + } + + yield { success: true }; + } +} + +function generateFullPath( + filePath: string, + type: BuildOutputFileType, + outputOptions: NormalizedOutputOptions, +) { + let typeDirectory: string; + switch (type) { + case BuildOutputFileType.Browser: + case BuildOutputFileType.Media: + typeDirectory = outputOptions.browser; + break; + case BuildOutputFileType.ServerApplication: + case BuildOutputFileType.ServerRoot: + typeDirectory = outputOptions.server; + break; + case BuildOutputFileType.Root: + typeDirectory = ''; + break; + default: + throw new Error( + `Unhandled write for file "${filePath}" with type "${BuildOutputFileType[type]}".`, + ); + } + // NOTE: 'base' is a fully resolved path at this point + const fullFilePath = path.join(outputOptions.base, typeDirectory, filePath); + + return fullFilePath; +} + +const builder: Builder = createBuilder(buildApplication); + +export default builder; diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts new file mode 100644 index 000000000000..83b7ea428f35 --- /dev/null +++ b/packages/angular/build/src/builders/application/options.ts @@ -0,0 +1,740 @@ +/** + * @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.dev/license + */ + +import type { BuilderContext } from '@angular-devkit/architect'; +import type { Plugin } from 'esbuild'; +import { realpathSync } from 'node:fs'; +import { access, constants, readFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils'; +import { supportColor } from '../../utils/color'; +import { useJSONBuildLogs, usePartialSsrBuild } from '../../utils/environment-options'; +import { I18nOptions, createI18nOptions } from '../../utils/i18n-options'; +import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; +import { + SearchDirectory, + findTailwindConfiguration, + generateSearchDirectories, + loadPostcssConfiguration, +} from '../../utils/postcss-configuration'; +import { getProjectRootPaths, normalizeDirectoryPath } from '../../utils/project-metadata'; +import { addTrailingSlash, joinUrlParts, stripLeadingSlash } from '../../utils/url'; +import { + Schema as ApplicationBuilderOptions, + ExperimentalPlatform, + I18NTranslation, + OutputHashing, + OutputMode, + OutputPathClass, +} from './schema'; + +/** + * The filename for the client-side rendered HTML template. + * This template is used for client-side rendering (CSR) in a web application. + */ +export const INDEX_HTML_CSR = 'index.csr.html'; + +/** + * The filename for the server-side rendered HTML template. + * This template is used for server-side rendering (SSR) in a web application. + */ +export const INDEX_HTML_SERVER = 'index.server.html'; + +export type NormalizedOutputOptions = Required & { + clean: boolean; + ignoreServer: boolean; +}; +export type NormalizedApplicationBuildOptions = Awaited>; + +export interface ApplicationBuilderExtensions { + codePlugins?: Plugin[]; + indexHtmlTransformer?: IndexHtmlTransform; +} + +/** Internal options hidden from builder schema but available when invoked programmatically. */ +interface InternalOptions { + /** + * Entry points to use for the compilation. Incompatible with `browser`, which must not be provided. May be relative or absolute paths. + * If given a relative path, it is resolved relative to the current workspace and will generate an output at the same relative location + * in the output directory. If given an absolute path, the output will be generated in the root of the output directory with the same base + * name. + * + * If provided a Map, the key is the name of the output bundle and the value is the entry point file. + */ + entryPoints?: Set | Map; + + /** File extension to use for the generated output files. */ + outExtension?: 'js' | 'mjs'; + + /** + * Indicates whether all node packages should be marked as external. + * Currently used by the dev-server to support prebundling. + */ + externalPackages?: boolean | { exclude: string[] }; + + /** + * Forces the output from the localize post-processing to not create nested directories per locale output. + * This is only used by the development server which currently only supports a single locale per build. + */ + forceI18nFlatOutput?: boolean; + + /** + * When set to `true`, enables fast SSR in development mode by disabling the full manifest generation and prerendering. + * + * This option is intended to optimize performance during development by skipping prerendering and route extraction when not required. + * @default false + */ + partialSSRBuild?: boolean; + + /** + * Enables the use of AOT compiler emitted external runtime styles. + * External runtime styles use `link` elements instead of embedded style content in the output JavaScript. + * This option is only intended to be used with a development server that can process and serve component + * styles. + */ + externalRuntimeStyles?: boolean; + + /** + * Enables the AOT compiler to generate template component update functions. + * This option is only intended to be used with a development server that can process and serve component + * template updates. + */ + templateUpdates?: boolean; + + /** + * Enables emitting incremental build results when in watch mode. A full build result will only be emitted + * for the initial build. This option also requires watch to be enabled to have an effect. + */ + incrementalResults?: boolean; + + /** + * Enables instrumentation to collect code coverage data for specific files. + * + * Used exclusively for tests and shouldn't be used for other kinds of builds. + */ + instrumentForCoverage?: (filename: string) => boolean; +} + +/** Full set of options for `application` builder. */ +export type ApplicationBuilderInternalOptions = Omit< + ApplicationBuilderOptions & InternalOptions, + 'browser' +> & { + // `browser` can be `undefined` if `entryPoints` is used. + browser?: string; +}; + +/** + * 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. + * @param plugins An optional array of programmatically supplied build plugins. + * @returns An object containing normalized options required to perform the build. + */ +// eslint-disable-next-line max-lines-per-function +export async function normalizeOptions( + context: BuilderContext, + projectName: string, + options: ApplicationBuilderInternalOptions, + extensions?: ApplicationBuilderExtensions, +) { + // If not explicitly set, default to the Node.js process argument + const preserveSymlinks = + options.preserveSymlinks ?? process.execArgv.includes('--preserve-symlinks'); + + // Setup base paths based on workspace root and project information + const workspaceRoot = preserveSymlinks + ? context.workspaceRoot + : // NOTE: promises.realpath should not be used here since it uses realpath.native which + // can cause case conversion and other undesirable behavior on Windows systems. + // ref: https://github.com/nodejs/node/issues/7726 + realpathSync(context.workspaceRoot); + const projectMetadata = await context.getProjectMetadata(projectName); + const { projectRoot, projectSourceRoot } = getProjectRootPaths(workspaceRoot, projectMetadata); + + // Gather persistent caching option and provide a project specific cache location + const cacheOptions = normalizeCacheOptions(projectMetadata, workspaceRoot); + cacheOptions.path = path.join(cacheOptions.path, projectName); + + const i18nOptions: I18nOptions & { + duplicateTranslationBehavior?: I18NTranslation; + missingTranslationBehavior?: I18NTranslation; + } = createI18nOptions(projectMetadata, options.localize, context.logger, !!options.ssr); + i18nOptions.duplicateTranslationBehavior = options.i18nDuplicateTranslation; + i18nOptions.missingTranslationBehavior = options.i18nMissingTranslation; + if (options.forceI18nFlatOutput) { + i18nOptions.flatOutput = true; + } + + const entryPoints = normalizeEntryPoints( + workspaceRoot, + projectSourceRoot, + options.browser, + options.entryPoints, + ); + const tsconfig = path.join(workspaceRoot, options.tsConfig); + const optimizationOptions = normalizeOptimization(options.optimization); + const sourcemapOptions = normalizeSourceMaps(options.sourceMap ?? false); + const assets = options.assets?.length + ? normalizeAssetPatterns(options.assets, workspaceRoot, projectRoot, projectSourceRoot) + : undefined; + + let fileReplacements: Record | undefined; + if (options.fileReplacements) { + for (const replacement of options.fileReplacements) { + const fileReplaceWith = path.join(workspaceRoot, replacement.with); + + try { + await access(fileReplaceWith, constants.F_OK); + } catch { + throw new Error(`The ${fileReplaceWith} path in file replacements does not exist.`); + } + + fileReplacements ??= {}; + fileReplacements[path.join(workspaceRoot, replacement.replace)] = fileReplaceWith; + } + } + + let loaderExtensions: + | Record + | undefined; + if (options.loader) { + for (const [extension, value] of Object.entries(options.loader)) { + if (extension[0] !== '.' || /\.[cm]?[jt]sx?$/.test(extension)) { + continue; + } + if ( + value !== 'text' && + value !== 'binary' && + value !== 'file' && + value !== 'dataurl' && + value !== 'base64' && + value !== 'empty' + ) { + continue; + } + loaderExtensions ??= {}; + loaderExtensions[extension] = value; + } + } + + // Validate prerender and ssr options when using the outputMode + if (options.outputMode === OutputMode.Server) { + if (!options.server) { + throw new Error('The "server" option is required when "outputMode" is set to "server".'); + } + + if (typeof options.ssr === 'boolean' || !options.ssr?.entry) { + throw new Error('The "ssr.entry" option is required when "outputMode" is set to "server".'); + } + } + + if (options.outputMode) { + if (!options.server) { + options.ssr = false; + } + + if (options.prerender !== undefined) { + context.logger.warn( + 'The "prerender" option is not considered when "outputMode" is specified.', + ); + } + + options.prerender = !!options.server; + + if (options.appShell !== undefined) { + context.logger.warn( + 'The "appShell" option is not considered when "outputMode" is specified.', + ); + } + } + + // A configuration file can exist in the project or workspace root + const searchDirectories = await generateSearchDirectories([projectRoot, workspaceRoot]); + const postcssConfiguration = await loadPostcssConfiguration(searchDirectories); + // Skip tailwind configuration if postcss is customized + const tailwindConfiguration = postcssConfiguration + ? undefined + : await getTailwindConfig(searchDirectories, workspaceRoot, context); + + let serverEntryPoint: string | undefined; + if (typeof options.server === 'string') { + if (options.server === '') { + throw new Error('The "server" option cannot be an empty string.'); + } + + serverEntryPoint = path.join(workspaceRoot, options.server); + } + + let prerenderOptions; + if (options.prerender) { + const { discoverRoutes = true, routesFile = undefined } = + options.prerender === true ? {} : options.prerender; + + prerenderOptions = { + discoverRoutes, + routesFile: routesFile && path.join(workspaceRoot, routesFile), + }; + } + + let ssrOptions; + if (options.ssr === true) { + ssrOptions = {}; + } else if (typeof options.ssr === 'object') { + const { entry, experimentalPlatform = ExperimentalPlatform.Node } = options.ssr; + + ssrOptions = { + entry: entry && path.join(workspaceRoot, entry), + platform: experimentalPlatform, + }; + } + + let appShellOptions; + if (options.appShell) { + appShellOptions = { + route: 'shell', + }; + } + + const outputPath = options.outputPath ?? path.join(workspaceRoot, 'dist', projectName); + const outputOptions: NormalizedOutputOptions = { + browser: 'browser', + server: 'server', + media: 'media', + ...(typeof outputPath === 'string' ? undefined : outputPath), + base: normalizeDirectoryPath( + path.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base), + ), + clean: options.deleteOutputPath ?? true, + // For app-shell and SSG server files are not required by users. + // Omit these when SSR is not enabled. + ignoreServer: + ((ssrOptions === undefined || serverEntryPoint === undefined) && + options.outputMode === undefined) || + options.outputMode === OutputMode.Static, + }; + + const outputNames = { + bundles: + options.outputHashing === OutputHashing.All || options.outputHashing === OutputHashing.Bundles + ? '[name]-[hash]' + : '[name]', + media: + outputOptions.media + + (options.outputHashing === OutputHashing.All || options.outputHashing === OutputHashing.Media + ? '/[name]-[hash]' + : '/[name]'), + }; + + const globalStyles = normalizeGlobalEntries(options.styles, 'styles'); + const globalScripts = normalizeGlobalEntries(options.scripts, 'scripts'); + let indexHtmlOptions; + // index can never have a value of `true` but in the schema it's of type `boolean`. + if (typeof options.index !== 'boolean') { + let indexInput: string; + let indexOutput: string; + // The output file will be created within the configured output path + if (typeof options.index === 'string') { + indexInput = indexOutput = path.join(workspaceRoot, options.index); + } else if (typeof options.index === 'undefined') { + indexInput = path.join(projectSourceRoot, 'index.html'); + indexOutput = 'index.html'; + } else { + indexInput = path.join(workspaceRoot, options.index.input); + indexOutput = options.index.output || 'index.html'; + } + + /** + * If SSR is activated, create a distinct entry file for the `index.html`. + * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file + * if it exists (handling SSG). + * + * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server. + * + * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`. + */ + const indexBaseName = path.basename(indexOutput); + indexOutput = + (ssrOptions || prerenderOptions) && indexBaseName === 'index.html' + ? INDEX_HTML_CSR + : indexBaseName; + + indexHtmlOptions = { + input: indexInput, + output: indexOutput, + insertionOrder: [ + ['polyfills', true], + ...globalStyles.filter((s) => s.initial).map((s) => [s.name, false]), + ...globalScripts.filter((s) => s.initial).map((s) => [s.name, false]), + ['main', true], + // [name, esm] + ] as [string, boolean][], + transformer: extensions?.indexHtmlTransformer, + // Preload initial defaults to true + preloadInitial: typeof options.index !== 'object' || (options.index.preloadInitial ?? true), + }; + } + + if (appShellOptions || ssrOptions || prerenderOptions) { + if (!serverEntryPoint) { + throw new Error( + 'The "server" option is required when enabling "ssr", "prerender" or "app-shell".', + ); + } + + if (!indexHtmlOptions) { + throw new Error( + 'The "index" option cannot be set to false when enabling "ssr", "prerender" or "app-shell".', + ); + } + } + + const autoCsp = options.security?.autoCsp; + const security = { + autoCsp: autoCsp + ? { + unsafeEval: autoCsp === true ? false : !!autoCsp.unsafeEval, + } + : undefined, + }; + + // Initial options to keep + const { + allowedCommonJsDependencies, + aot = true, + baseHref, + crossOrigin, + externalDependencies, + extractLicenses, + inlineStyleLanguage = 'css', + outExtension, + serviceWorker, + poll, + polyfills, + statsJson, + outputMode, + stylePreprocessorOptions, + subresourceIntegrity, + verbose, + watch, + progress = true, + externalPackages, + namedChunks, + budgets, + deployUrl, + clearScreen, + define, + partialSSRBuild = false, + externalRuntimeStyles, + instrumentForCoverage, + } = options; + + // Return all the normalized options + return { + advancedOptimizations: !!aot && optimizationOptions.scripts, + allowedCommonJsDependencies, + baseHref, + cacheOptions, + crossOrigin, + externalDependencies: normalizeExternals(externalDependencies), + externalPackages: + typeof externalPackages === 'object' + ? { + ...externalPackages, + exclude: normalizeExternals(externalPackages.exclude), + } + : externalPackages, + extractLicenses, + inlineStyleLanguage, + jit: !aot, + stats: !!statsJson, + polyfills: polyfills === undefined || Array.isArray(polyfills) ? polyfills : [polyfills], + poll, + progress, + preserveSymlinks, + stylePreprocessorOptions, + subresourceIntegrity, + serverEntryPoint, + prerenderOptions, + appShellOptions, + outputMode, + ssrOptions, + verbose, + watch, + workspaceRoot, + entryPoints, + optimizationOptions, + outputOptions, + outExtension, + sourcemapOptions, + tsconfig, + projectRoot, + assets, + outputNames, + fileReplacements, + globalStyles, + globalScripts, + serviceWorker: serviceWorker + ? path.join( + workspaceRoot, + typeof serviceWorker === 'string' ? serviceWorker : 'src/ngsw-config.json', + ) + : undefined, + indexHtmlOptions, + tailwindConfiguration, + postcssConfiguration, + i18nOptions, + namedChunks, + budgets: budgets?.length ? budgets : undefined, + publicPath: deployUrl, + plugins: extensions?.codePlugins?.length ? extensions?.codePlugins : undefined, + loaderExtensions, + jsonLogs: useJSONBuildLogs, + colors: supportColor(), + clearScreen, + define, + partialSSRBuild: usePartialSsrBuild || partialSSRBuild, + externalRuntimeStyles: aot && externalRuntimeStyles, + instrumentForCoverage, + security, + templateUpdates: !!options.templateUpdates, + incrementalResults: !!options.incrementalResults, + customConditions: options.conditions, + frameworkVersion: await findFrameworkVersion(projectRoot), + }; +} + +async function getTailwindConfig( + searchDirectories: SearchDirectory[], + workspaceRoot: string, + context: BuilderContext, +): Promise<{ file: string; package: string } | undefined> { + const tailwindConfigurationPath = findTailwindConfiguration(searchDirectories); + + if (!tailwindConfigurationPath) { + return undefined; + } + + // Create a node resolver from the configuration file + const resolver = createRequire(tailwindConfigurationPath); + try { + return { + file: tailwindConfigurationPath, + package: resolver.resolve('tailwindcss'), + }; + } catch { + const relativeTailwindConfigPath = path.relative(workspaceRoot, tailwindConfigurationPath); + context.logger.warn( + `Tailwind CSS configuration file found (${relativeTailwindConfigPath})` + + ` but the 'tailwindcss' package is not installed.` + + ` To enable Tailwind CSS, please install the 'tailwindcss' package.`, + ); + } + + return undefined; +} + +/** + * Normalize entry point options. To maintain compatibility with the legacy browser builder, we need a single `browser` + * option which defines a single entry point. However, we also want to support multiple entry points as an internal option. + * The two options are mutually exclusive and if `browser` is provided it will be used as the sole entry point. + * If `entryPoints` are provided, they will be used as the set of entry points. + * + * @param workspaceRoot Path to the root of the Angular workspace. + * @param browser The `browser` option pointing at the application entry point. While required per the schema file, it may be omitted by + * programmatic usages of `browser-esbuild`. + * @param entryPoints Set of entry points to use if provided. + * @returns An object mapping entry point names to their file paths. + */ +function normalizeEntryPoints( + workspaceRoot: string, + projectSourceRoot: string, + browser: string | undefined, + entryPoints: Set | Map | undefined, +): Record { + if (browser === '') { + throw new Error('`browser` option cannot be an empty string.'); + } + + // `browser` and `entryPoints` are mutually exclusive. + if (browser && entryPoints) { + throw new Error('Only one of `browser` or `entryPoints` may be provided.'); + } + + if (browser) { + // Use `browser` alone. + return { 'main': path.join(workspaceRoot, browser) }; + } else if (!entryPoints) { + // Default browser entry if no explicit entry points + return { 'main': path.join(projectSourceRoot, 'main.ts') }; + } else if (entryPoints instanceof Map) { + return Object.fromEntries( + Array.from(entryPoints.entries(), ([name, entryPoint]) => { + // Get the full file path to a relative entry point input. Leave bare specifiers alone so they are resolved as modules. + const isRelativePath = entryPoint.startsWith('.'); + const entryPointPath = isRelativePath ? path.join(workspaceRoot, entryPoint) : entryPoint; + + return [name, entryPointPath]; + }), + ); + } else { + // Use `entryPoints` alone. + const entryPointPaths: Record = {}; + for (const entryPoint of entryPoints) { + const parsedEntryPoint = path.parse(entryPoint); + + // Use the input file path without an extension as the "name" of the entry point dictating its output location. + // Relative entry points are generated at the same relative path in the output directory. + // Absolute entry points are always generated with the same file name in the root of the output directory. This includes absolute + // paths pointing at files actually within the workspace root. + const entryPointName = path.isAbsolute(entryPoint) + ? parsedEntryPoint.name + : path.join(parsedEntryPoint.dir, parsedEntryPoint.name); + + // Get the full file path to a relative entry point input. Leave bare specifiers alone so they are resolved as modules. + const isRelativePath = entryPoint.startsWith('.'); + const entryPointPath = isRelativePath ? path.join(workspaceRoot, entryPoint) : entryPoint; + + // Check for conflicts with previous entry points. + const existingEntryPointPath = entryPointPaths[entryPointName]; + if (existingEntryPointPath) { + throw new Error( + `\`${existingEntryPointPath}\` and \`${entryPointPath}\` both output to the same location \`${entryPointName}\`.` + + ' Rename or move one of the files to fix the conflict.', + ); + } + + entryPointPaths[entryPointName] = entryPointPath; + } + + return entryPointPaths; + } +} + +function normalizeGlobalEntries( + rawEntries: ({ bundleName?: string; input: string; inject?: boolean } | string)[] | undefined, + defaultName: string, +): { name: string; files: string[]; initial: boolean }[] { + if (!rawEntries?.length) { + return []; + } + + const bundles = new Map(); + + for (const rawEntry of rawEntries) { + let entry; + if (typeof rawEntry === 'string') { + // string entries use default bundle name and inject values + entry = { input: rawEntry }; + } else { + entry = rawEntry; + } + + const { bundleName, input, inject = true } = entry; + + // Non-injected entries default to the file name + const name = bundleName || (inject ? defaultName : path.basename(input, path.extname(input))); + + const existing = bundles.get(name); + if (!existing) { + bundles.set(name, { name, files: [input], initial: inject }); + continue; + } + + if (existing.initial !== inject) { + throw new Error( + `The "${name}" bundle is mixing injected and non-injected entries. ` + + 'Verify that the project options are correct.', + ); + } + + existing.files.push(input); + } + + return [...bundles.values()]; +} + +export function getLocaleBaseHref( + baseHref: string | undefined = '', + i18n: NormalizedApplicationBuildOptions['i18nOptions'], + locale: string, +): string | undefined { + if (i18n.flatOutput) { + return undefined; + } + + const localeData = i18n.locales[locale]; + if (!localeData) { + return undefined; + } + + const baseHrefSuffix = localeData.baseHref ?? localeData.subPath + '/'; + + let joinedBaseHref: string | undefined; + if (baseHrefSuffix !== '') { + joinedBaseHref = addTrailingSlash(joinUrlParts(baseHref, baseHrefSuffix)); + + if (baseHref && baseHref[0] !== '/') { + joinedBaseHref = stripLeadingSlash(joinedBaseHref); + } + } + + return joinedBaseHref; +} + +/** + * Normalizes an array of external dependency paths by ensuring that + * wildcard patterns (`/*`) are removed from package names. + * + * This avoids the need to handle this normalization repeatedly in our plugins, + * as esbuild already treats `--external:@foo/bar` as implicitly including + * `--external:@foo/bar/*`. By standardizing the input, we ensure consistency + * and reduce redundant checks across our plugins. + * + * @param value - An optional array of dependency paths to normalize. + * @returns A new array with wildcard patterns removed from package names, or `undefined` if input is `undefined`. + */ +function normalizeExternals(value: string[] | undefined): string[] | undefined { + if (!value) { + return undefined; + } + + return [ + ...new Set( + value.map((d) => + // remove "/*" wildcard in the end if provided string is not path-like + d.endsWith('/*') && !/^\.{0,2}\//.test(d) ? d.slice(0, -2) : d, + ), + ), + ]; +} + +async function findFrameworkVersion(projectRoot: string): Promise { + // Create a custom require function for ESM compliance. + // NOTE: The trailing slash is significant. + const projectResolve = createRequire(projectRoot + '/').resolve; + + try { + const manifestPath = projectResolve('@angular/core/package.json'); + const manifestData = await readFile(manifestPath, 'utf-8'); + const manifestObject = JSON.parse(manifestData) as { version: string }; + const version = manifestObject.version; + + return version; + } catch { + throw new Error( + 'Error: It appears that "@angular/core" is missing as a dependency. Please ensure it is included in your project.', + ); + } +} diff --git a/packages/angular/build/src/builders/application/options_spec.ts b/packages/angular/build/src/builders/application/options_spec.ts new file mode 100644 index 000000000000..ac6320905019 --- /dev/null +++ b/packages/angular/build/src/builders/application/options_spec.ts @@ -0,0 +1,106 @@ +/** + * @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.dev/license + */ + +import { NormalizedApplicationBuildOptions, getLocaleBaseHref } from './options'; + +describe('getLocaleBaseHref', () => { + const baseI18nOptions: NormalizedApplicationBuildOptions['i18nOptions'] = { + inlineLocales: new Set(), + sourceLocale: 'en-US', + locales: {}, + flatOutput: false, + shouldInline: false, + hasDefinedSourceLocale: false, + }; + + it('should return undefined if flatOutput is true', () => { + const result = getLocaleBaseHref(undefined, { ...baseI18nOptions, flatOutput: true }, 'fr'); + expect(result).toBeUndefined(); + }); + + it('should return undefined if locale is not found', () => { + const result = getLocaleBaseHref(undefined, baseI18nOptions, 'fr'); + expect(result).toBeUndefined(); + }); + + it('should return baseHref from locale data if present', () => { + const i18nOptions = { + ...baseI18nOptions, + locales: { + fr: { + files: [], + translation: {}, + subPath: 'fr', + baseHref: '/fr/', + }, + }, + }; + const result = getLocaleBaseHref(undefined, i18nOptions, 'fr'); + expect(result).toBe('/fr/'); + }); + + it('should join baseHref and locale subPath if baseHref is provided', () => { + const i18nOptions = { + ...baseI18nOptions, + locales: { + fr: { + files: [], + translation: {}, + subPath: 'fr', + }, + }, + }; + const result = getLocaleBaseHref('/app/', i18nOptions, 'fr'); + expect(result).toBe('/app/fr/'); + }); + + it('should handle missing baseHref (undefined) correctly', () => { + const i18nOptions = { + ...baseI18nOptions, + locales: { + fr: { + files: [], + translation: {}, + subPath: 'fr', + }, + }, + }; + const result = getLocaleBaseHref(undefined, i18nOptions, 'fr'); + expect(result).toBe('/fr/'); + }); + + it('should handle empty baseHref correctly', () => { + const i18nOptions = { + ...baseI18nOptions, + locales: { + fr: { + files: [], + translation: {}, + subPath: 'fr', + }, + }, + }; + const result = getLocaleBaseHref('', i18nOptions, 'fr'); + expect(result).toBe('/fr/'); + }); + + it('should strip leading slash if baseHref does not start with slash', () => { + const i18nOptions = { + ...baseI18nOptions, + locales: { + fr: { + files: [], + translation: {}, + subPath: 'fr', + }, + }, + }; + const result = getLocaleBaseHref('app/', i18nOptions, 'fr'); + expect(result).toBe('app/fr/'); + }); +}); diff --git a/packages/angular/build/src/builders/application/results.ts b/packages/angular/build/src/builders/application/results.ts new file mode 100644 index 000000000000..3e3f0dd99315 --- /dev/null +++ b/packages/angular/build/src/builders/application/results.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.dev/license + */ + +import { BuildOutputFileType } from '../../tools/esbuild/bundler-context'; + +export enum ResultKind { + Failure, + Full, + Incremental, + ComponentUpdate, +} + +export type Result = FailureResult | FullResult | IncrementalResult | ComponentUpdateResult; + +export interface BaseResult { + kind: ResultKind; + warnings?: ResultMessage[]; + duration?: number; + detail?: Record; +} + +export interface FailureResult extends BaseResult { + kind: ResultKind.Failure; + errors: ResultMessage[]; +} + +export interface FullResult extends BaseResult { + kind: ResultKind.Full; + files: Record; +} + +export interface IncrementalResult extends BaseResult { + kind: ResultKind.Incremental; + background?: boolean; + added: string[]; + removed: { path: string; type: BuildOutputFileType }[]; + modified: string[]; + files: Record; +} + +export type ResultFile = DiskFile | MemoryFile; + +export interface BaseResultFile { + origin: 'memory' | 'disk'; + type: BuildOutputFileType; +} + +export interface DiskFile extends BaseResultFile { + origin: 'disk'; + inputPath: string; +} + +export interface MemoryFile extends BaseResultFile { + origin: 'memory'; + hash: string; + contents: Uint8Array; +} + +export interface ResultMessage { + text: string; + location?: { file: string; line: number; column: number } | null; + notes?: { text: string }[]; +} + +export interface ComponentUpdateResult extends BaseResult { + kind: ResultKind.ComponentUpdate; + updates: { + id: string; + type: 'style' | 'template'; + content: string; + }[]; +} diff --git a/packages/angular/build/src/builders/application/schema.json b/packages/angular/build/src/builders/application/schema.json new file mode 100644 index 000000000000..8db4e6145b3f --- /dev/null +++ b/packages/angular/build/src/builders/application/schema.json @@ -0,0 +1,720 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "Application schema for Build Facade.", + "description": "Application builder target options", + "type": "object", + "properties": { + "assets": { + "type": "array", + "description": "Define the assets to be copied to the output directory. These assets are copied as-is without any further processing or hashing.", + "default": [], + "items": { + "$ref": "#/definitions/assetPattern" + } + }, + "browser": { + "type": "string", + "description": "The full path for the browser entry point to the application, relative to the current workspace." + }, + "server": { + "description": "The full path for the server entry point to the application, relative to the current workspace.", + "oneOf": [ + { + "type": "string", + "description": "The full path for the server entry point to the application, relative to the current workspace." + }, + { + "const": false, + "type": "boolean", + "description": "Indicates that a server entry point is not provided." + } + ] + }, + "polyfills": { + "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'.", + "type": "array", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, + "deployUrl": { + "type": "string", + "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations." + }, + "security": { + "description": "Security features to protect against XSS and other common attacks", + "type": "object", + "additionalProperties": false, + "properties": { + "autoCsp": { + "description": "Enables automatic generation of a hash-based Strict Content Security Policy (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will default to true once we are out of experimental/preview phases.", + "default": false, + "oneOf": [ + { + "type": "object", + "properties": { + "unsafeEval": { + "type": "boolean", + "description": "Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.", + "default": false + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + } + } + }, + "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 JavaScript/TypeScript file or package containing the file to include." + } + ] + } + }, + "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": [] + }, + "sass": { + "description": "Options to pass to the sass preprocessor.", + "type": "object", + "properties": { + "fatalDeprecations": { + "description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.", + "type": "array", + "items": { + "type": "string" + } + }, + "silenceDeprecations": { + "description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.", + "type": "array", + "items": { + "type": "string" + } + }, + "futureDeprecations": { + "description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "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. Note: `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths like `@foo/bar/baz`.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "clearScreen": { + "type": "boolean", + "default": false, + "description": "Automatically clear the terminal screen during rebuilds." + }, + "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.dev/reference/configs/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 + }, + "removeSpecialComments": { + "type": "boolean", + "description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.", + "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" + } + ] + }, + "loader": { + "description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `dataurl` inlines the content as a data URL with best guess of MIME type; `base64` inlines the content as a Base64-encoded string; `empty` considers the content to be empty and not include it in bundles.", + "type": "object", + "patternProperties": { + "^\\.\\S+$": { "enum": ["text", "binary", "file", "dataurl", "base64", "empty"] } + } + }, + "define": { + "description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "conditions": { + "description": "Custom package resolution conditions used to resolve conditional exports/imports. Defaults to ['module', 'development'/'production']. The following special conditions are always present if the requirements are satisfied: 'default', 'import', 'require', 'browser', 'node'.", + "type": "array", + "items": { + "type": "string" + } + }, + "fileReplacements": { + "description": "Replace compilation source files with other compilation source files in the build.", + "type": "array", + "items": { + "$ref": "#/definitions/fileReplacement" + }, + "default": [] + }, + "outputPath": { + "description": "Specify the output path relative to workspace root.", + "oneOf": [ + { + "type": "object", + "properties": { + "base": { + "type": "string", + "description": "Specify the output path relative to workspace root." + }, + "browser": { + "type": "string", + "pattern": "^[-\\w\\.]*$", + "default": "browser", + "description": "The output directory name of your browser build within the output path base. Defaults to 'browser'." + }, + "server": { + "type": "string", + "pattern": "^[-\\w\\.]*$", + "default": "server", + "description": "The output directory name of your server build within the output path base. Defaults to 'server'." + }, + "media": { + "type": "string", + "pattern": "^[-\\w\\.]+$", + "default": "media", + "description": "The output directory name of your media files within the output browser directory. Defaults to 'media'." + } + }, + "required": ["base"], + "additionalProperties": false + }, + { + "type": "string" + } + ] + }, + "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.dev/reference/configs/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 + }, + "sourcesContent": { + "type": "boolean", + "description": "Output original source content for files within the source map.", + "default": true + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ] + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + }, + "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.\n\n- `none`: No hashing.\n- `all`: Hash for all output bundles. \n- `media`: Hash for all output media (e.g., images, fonts, etc. that are referenced in CSS files).\n- `bundles`: Hash for output of lazy and main bundles.", + "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 + }, + "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": { + "description": "Generates a service worker configuration.", + "default": false, + "oneOf": [ + { + "type": "string", + "description": "Path to ngsw-config.json." + }, + { + "const": false, + "type": "boolean", + "description": "Does not generate a service worker configuration." + } + ] + }, + "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." + }, + "preloadInitial": { + "type": "boolean", + "default": true, + "description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources." + } + }, + "required": ["input"] + }, + { + "const": false, + "type": "boolean", + "description": "Does not generate an `index.html` file." + } + ] + }, + "statsJson": { + "type": "boolean", + "description": "Generates a 'stats.json' file which can be analyzed with https://esbuild.github.io/analyze/.", + "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 or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "prerender": { + "description": "Prerender (SSG) pages of your application during build time.", + "oneOf": [ + { + "type": "boolean", + "description": "Enable prerending of pages of your application during build time." + }, + { + "type": "object", + "properties": { + "routesFile": { + "type": "string", + "description": "The path to a file that contains a list of all routes to prerender, separated by newlines. This option is useful if you want to prerender routes with parameterized URLs." + }, + "discoverRoutes": { + "type": "boolean", + "description": "Whether the builder should process the Angular Router configuration to find all unparameterized routes and prerender them.", + "default": true + } + }, + "additionalProperties": false + } + ] + }, + "ssr": { + "description": "Server side render (SSR) pages of your application during runtime.", + "default": false, + "oneOf": [ + { + "type": "boolean", + "description": "Enable the server bundles to be written to disk." + }, + { + "type": "object", + "properties": { + "entry": { + "type": "string", + "description": "The server entry-point that when executed will spawn the web server." + }, + "experimentalPlatform": { + "description": "Specifies the platform for which the server bundle is generated. This affects the APIs and modules available in the server-side code. \n\n- `node`: (Default) Generates a bundle optimized for Node.js environments. \n- `neutral`: Generates a platform-neutral bundle suitable for environments like edge workers, and other serverless platforms. This option avoids using Node.js-specific APIs, making the bundle more portable. \n\nPlease note that this feature does not provide polyfills for Node.js modules. Additionally, it is experimental, and the schematics may undergo changes in future versions.", + "default": "node", + "enum": ["node", "neutral"] + } + }, + "additionalProperties": false + } + ] + }, + "appShell": { + "type": "boolean", + "description": "Generates an application shell during build time." + }, + "outputMode": { + "type": "string", + "description": "Defines the type of build output artifact. 'static': Generates a static site build artifact for deployment on any static hosting service. 'server': Generates a server application build artifact, required for applications using hybrid rendering or APIs.", + "enum": ["static", "server"] + } + }, + "additionalProperties": false, + "required": ["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", + "default": "", + "description": "Absolute path within the output." + } + }, + "additionalProperties": false, + "required": ["glob", "input"] + }, + { + "type": "string" + } + ] + }, + "fileReplacement": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "pattern": "\\.(([cm]?[jt])sx?|json)$" + }, + "with": { + "type": "string", + "pattern": "\\.(([cm]?[jt])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/build/src/builders/application/setup-bundling.ts b/packages/angular/build/src/builders/application/setup-bundling.ts new file mode 100644 index 000000000000..9b47bc67e49d --- /dev/null +++ b/packages/angular/build/src/builders/application/setup-bundling.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.dev/license + */ + +import { AngularCompilation } from '../../tools/angular/compilation'; +import { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets'; +import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache'; +import { + createBrowserCodeBundleOptions, + createBrowserPolyfillBundleOptions, + createServerMainCodeBundleOptions, + createServerPolyfillBundleOptions, + createSsrEntryCodeBundleOptions, +} from '../../tools/esbuild/application-code-bundle'; +import { BundlerContext } from '../../tools/esbuild/bundler-context'; +import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts'; +import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles'; +import { getSupportedNodeTargets } from '../../tools/esbuild/utils'; +import { NormalizedApplicationBuildOptions } from './options'; + +/** + * Generates one or more BundlerContext instances based on the builder provided + * configuration. + * @param options The normalized application builder options to use. + * @param browsers An string array of browserslist browsers to support. + * @param codeBundleCache An instance of the TypeScript source file cache. + * @returns An array of BundlerContext objects. + */ +export function setupBundlerContexts( + options: NormalizedApplicationBuildOptions, + target: string[], + codeBundleCache: SourceFileCache, + stylesheetBundler: ComponentStylesheetBundler, + angularCompilation: AngularCompilation, + templateUpdates: Map | undefined, +): { + typescriptContexts: BundlerContext[]; + otherContexts: BundlerContext[]; +} { + const { + outputMode, + serverEntryPoint, + appShellOptions, + prerenderOptions, + ssrOptions, + workspaceRoot, + watch = false, + } = options; + const typescriptContexts = []; + const otherContexts = []; + + // Browser application code + typescriptContexts.push( + new BundlerContext( + workspaceRoot, + watch, + createBrowserCodeBundleOptions( + options, + target, + codeBundleCache, + stylesheetBundler, + angularCompilation, + templateUpdates, + ), + ), + ); + + // Browser polyfills code + const browserPolyfillBundleOptions = createBrowserPolyfillBundleOptions( + options, + target, + codeBundleCache, + stylesheetBundler, + ); + if (browserPolyfillBundleOptions) { + const browserPolyfillContext = new BundlerContext( + workspaceRoot, + watch, + browserPolyfillBundleOptions, + ); + if (typeof browserPolyfillBundleOptions === 'function') { + otherContexts.push(browserPolyfillContext); + } else { + typescriptContexts.push(browserPolyfillContext); + } + } + + // Global Stylesheets + if (options.globalStyles.length > 0) { + for (const initial of [true, false]) { + const bundleOptions = createGlobalStylesBundleOptions(options, target, initial); + if (bundleOptions) { + otherContexts.push(new BundlerContext(workspaceRoot, watch, bundleOptions, () => initial)); + } + } + } + + // Global Scripts + if (options.globalScripts.length > 0) { + for (const initial of [true, false]) { + const bundleOptions = createGlobalScriptsBundleOptions(options, target, initial); + if (bundleOptions) { + otherContexts.push(new BundlerContext(workspaceRoot, watch, bundleOptions, () => initial)); + } + } + } + + // Skip server build when none of the features are enabled. + if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) { + const nodeTargets = [...target, ...getSupportedNodeTargets()]; + + typescriptContexts.push( + new BundlerContext( + workspaceRoot, + watch, + createServerMainCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler), + ), + ); + + if (outputMode && ssrOptions?.entry) { + // New behavior introduced: 'server.ts' is now bundled separately from 'main.server.ts'. + typescriptContexts.push( + new BundlerContext( + workspaceRoot, + watch, + createSsrEntryCodeBundleOptions(options, nodeTargets, codeBundleCache, stylesheetBundler), + ), + ); + } + + // Server polyfills code + const serverPolyfillBundleOptions = createServerPolyfillBundleOptions( + options, + nodeTargets, + codeBundleCache.loadResultCache, + ); + + if (serverPolyfillBundleOptions) { + otherContexts.push(new BundlerContext(workspaceRoot, watch, serverPolyfillBundleOptions)); + } + } + + return { typescriptContexts, otherContexts }; +} + +export function createComponentStyleBundler( + options: NormalizedApplicationBuildOptions, + target: string[], +): ComponentStylesheetBundler { + const { + workspaceRoot, + optimizationOptions, + sourcemapOptions, + outputNames, + externalDependencies, + preserveSymlinks, + stylePreprocessorOptions, + inlineStyleLanguage, + cacheOptions, + tailwindConfiguration, + postcssConfiguration, + publicPath, + } = options; + const incremental = !!options.watch; + + return new ComponentStylesheetBundler( + { + workspaceRoot, + inlineFonts: !!optimizationOptions.fonts.inline, + 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 ? 'linked' : false, + sourcesContent: sourcemapOptions.sourcesContent, + outputNames, + includePaths: stylePreprocessorOptions?.includePaths, + // string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sass: stylePreprocessorOptions?.sass as any, + externalDependencies, + target, + preserveSymlinks, + tailwindConfiguration, + postcssConfiguration, + cacheOptions, + publicPath, + }, + inlineStyleLanguage, + incremental, + ); +} diff --git a/packages/angular/build/src/builders/application/tests/behavior/angular-aot-metadata_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/angular-aot-metadata_spec.ts new file mode 100644 index 000000000000..8f5f4d18d781 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/angular-aot-metadata_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Angular metadata"', () => { + it('should not emit any AOT class metadata functions', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.not.toContain('setClassMetadata'); + }); + + it('should not emit any AOT NgModule scope metadata functions', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.not.toContain('setNgModuleScope'); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts new file mode 100644 index 000000000000..e281ca8caeb9 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/browser-support_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_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, + polyfills: ['zone.js'], + sourceMap: { + scripts: true, + }, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.not.toMatch(/\sasync\s+function\s/); + harness.expectFile('dist/browser/main.js.map').content.toContain('Promise'); + }); + + it('downlevels async functions when zone.js is included as a polyfill', async () => { + // Add an async function to the project + await harness.writeFile( + 'src/main.ts', + 'async function test(): Promise { console.log("from-async-function"); }\ntest();', + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: ['zone.js'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.not.toMatch(/\sasync\s/); + harness.expectFile('dist/browser/main.js').content.toContain('"from-async-function"'); + }); + + it('does not downlevels async functions when zone.js is not included as a polyfill', async () => { + // Add an async function to the project + await harness.writeFile( + 'src/main.ts', + 'async function test(): Promise { console.log("from-async-function"); }\ntest();', + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: [], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toMatch(/\sasync\s/); + harness.expectFile('dist/browser/main.js').content.toContain('"from-async-function"'); + }); + + it('warns when IE is present in browserslist', async () => { + await harness.writeFile( + '.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: jasmine.stringContaining('ES5 output is not supported'), + }), + ); + + // Don't duplicate the error. + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringContaining("fall outside Angular's browser support"), + }), + ); + }); + + it("warns when targeting a browser outside Angular's minimum support", async () => { + await harness.writeFile('.browserslistrc', 'Chrome >= 100'); + + harness.useTarget('build', BASE_OPTIONS); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringContaining("fall outside Angular's browser support"), + }), + ); + }); + + it('downlevels "for await...of" when zone.js is included as a polyfill', 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, + polyfills: ['zone.js'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.not.toMatch(/\sawait\s/); + harness.expectFile('dist/browser/main.js').content.toContain('"for await...of"'); + }); + + it('does not downlevel "for await...of" when zone.js is not included as a polyfill', 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, + polyfills: [], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toMatch(/\sawait\s/); + harness.expectFile('dist/browser/main.js').content.toContain('"for await...of"'); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/build-conditions_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/build-conditions_spec.ts new file mode 100644 index 000000000000..949588aa9d1e --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/build-conditions_spec.ts @@ -0,0 +1,116 @@ +/** + * @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.dev/license + */ + +import { + setupConditionImport, + setTargetMapping, +} from '../../../../../../../../modules/testing/builder/src/dev_prod_mode'; +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "conditional imports"', () => { + beforeEach(async () => { + await setupConditionImport(harness); + }); + + interface ImportsTestCase { + name: string; + mapping: unknown; + output?: string; + } + + const GOOD_TARGET = './src/good.js'; + const BAD_TARGET = './src/bad.js'; + + const testCases: ImportsTestCase[] = [ + { name: 'simple string', mapping: GOOD_TARGET }, + { + name: 'default fallback without matching condition', + mapping: { + 'never': BAD_TARGET, + 'default': GOOD_TARGET, + }, + }, + { + name: 'development condition', + mapping: { + 'development': BAD_TARGET, + 'default': GOOD_TARGET, + }, + }, + { + name: 'production condition', + mapping: { + 'production': GOOD_TARGET, + 'default': BAD_TARGET, + }, + }, + { + name: 'browser condition (in browser)', + mapping: { + 'browser': GOOD_TARGET, + 'default': BAD_TARGET, + }, + }, + { + name: 'browser condition (in server)', + output: 'server/main.server.mjs', + mapping: { + 'browser': BAD_TARGET, + 'default': GOOD_TARGET, + }, + }, + ]; + + for (const testCase of testCases) { + describe(testCase.name, () => { + beforeEach(async () => { + await setTargetMapping(harness, testCase.mapping); + }); + + it('resolves to expected target', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + ssr: true, + server: 'src/main.ts', + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + const outputFile = `dist/${testCase.output ?? 'browser/main.js'}`; + harness.expectFile(outputFile).content.toContain('"good-value"'); + harness.expectFile(outputFile).content.not.toContain('"bad-value"'); + }); + }); + } + + it('fails type-checking when import contains differing type', async () => { + await setTargetMapping(harness, { + 'development': './src/wrong.ts', + 'default': './src/good.ts', + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: false, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('TS2339'), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/component-stylesheets_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/component-stylesheets_spec.ts new file mode 100644 index 000000000000..ddae750a64a4 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/component-stylesheets_spec.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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Component Stylesheets"', () => { + it('should successfully compile with an empty inline style', async () => { + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('styleUrls', 'styles').replace('./app.component.css', ''); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + + it('should maintain optimized empty Sass stylesheet when original has content', async () => { + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('./app.component.css', './app.component.scss'); + }); + await harness.removeFile('src/app/app.component.css'); + await harness.writeFile('src/app/app.component.scss', '@import "variables";'); + await harness.writeFile('src/app/_variables.scss', '$value: blue;'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: { + styles: true, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').content.not.toContain('variables'); + }); + + it('should generate an error for a missing stylesheet with AOT', async () => { + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('./app.component.css', './not-present.css'); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringContaining(`Could not find stylesheet file './not-present.css'`), + }), + ); + }); + + it('should generate an error for a missing stylesheet with JIT', async () => { + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('./app.component.css', './not-present.css'); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + aot: false, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringContaining('Could not resolve'), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/component-templates_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/component-templates_spec.ts new file mode 100644 index 000000000000..687ed78dc74c --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/component-templates_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Component Templates"', () => { + it('should generate an error for a missing template', async () => { + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('./app.component.html', './not-present.html'); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringContaining(`Could not find template file './not-present.html'`), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/csp-nonce_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/csp-nonce_spec.ts new file mode 100644 index 000000000000..eaeff9d1249c --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/csp-nonce_spec.ts @@ -0,0 +1,35 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "CSP Nonce"', () => { + it('should add CSP nonce to scripts when optimization is disabled', async () => { + await harness.modifyFile('src/index.html', (content) => + content.replace(/', + ); + indexFileContent.toContain(' { + describe('Behavior: "index.csr.html"', () => { + beforeEach(async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files ??= []; + tsConfig.files.push('main.server.ts'); + + return JSON.stringify(tsConfig); + }); + }); + + it(`should generate 'index.csr.html' instead of 'index.html' when ssr is enabled.`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectDirectory('dist/server').toExist(); + harness.expectFile('dist/browser/index.csr.html').toExist(); + harness.expectFile('dist/browser/index.html').toNotExist(); + }); + + it(`should generate 'index.csr.html' instead of 'index.html' when 'output' is 'index.html' and ssr is enabled.`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + index: { + input: 'src/index.html', + output: 'index.html', + }, + server: 'src/main.server.ts', + ssr: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectDirectory('dist/server').toExist(); + harness.expectFile('dist/browser/index.csr.html').toExist(); + harness.expectFile('dist/browser/index.html').toNotExist(); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/index-preload-hints_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/index-preload-hints_spec.ts new file mode 100644 index 000000000000..7f6b9711790b --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/index-preload-hints_spec.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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Preload hints"', () => { + it('should add preload hints for transitive global style imports', async () => { + await harness.writeFile( + 'src/styles.css', + ` + @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap'); + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + + harness + .expectFile('dist/browser/index.html') + .content.toContain( + '', + ); + }); + + it('should not add preload hints for ssr files', async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files ??= []; + tsConfig.files.push('main.server.ts'); + + return JSON.stringify(tsConfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/server/main.server.mjs').toExist(); + + harness + .expectFile('dist/browser/index.csr.html') + .content.not.toMatch(//); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/loader-import-attribute_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/loader-import-attribute_spec.ts new file mode 100644 index 000000000000..91c4cafc571a --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/loader-import-attribute_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "loader import attribute"', () => { + beforeEach(async () => { + await harness.modifyFile('tsconfig.json', (content) => { + return content.replace('"module": "ES2022"', '"module": "esnext"'); + }); + }); + + it('should inline text content for loader attribute set to "text"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.unknown', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.unknown" with { loader: "text" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('ABC'); + }); + + it('should inline binary content for loader attribute set to "binary"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.unknown', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.unknown" with { loader: "binary" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + // Should contain the binary encoding used esbuild and not the text content + harness.expectFile('dist/browser/main.js').content.toContain('__toBinary("QUJD")'); + harness.expectFile('dist/browser/main.js').content.not.toContain('ABC'); + }); + + it('should inline base64 content for file extension set to "base64"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.unknown', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.unknown" with { loader: "base64" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + // Should contain the base64 encoding used esbuild and not the text content + harness.expectFile('dist/browser/main.js').content.toContain('QUJD'); + harness.expectFile('dist/browser/main.js').content.not.toContain('ABC'); + }); + + it('should inline dataurl content for file extension set to "dataurl"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.svg', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.svg" with { loader: "dataurl" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + // Should contain the dataurl encoding used esbuild and not the text content + harness.expectFile('dist/browser/main.js').content.toContain('data:image/svg+xml,ABC'); + }); + + it('should emit an output file for loader attribute set to "file"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.unknown', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.unknown" with { loader: "file" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('a.unknown'); + harness.expectFile('dist/browser/media/a.unknown').toExist(); + }); + + it('should emit an output file with hashing when enabled for loader attribute set to "file"', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + outputHashing: 'media' as any, + }); + + await harness.writeFile('./src/a.unknown', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.unknown" with { loader: "file" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('a.unknown'); + expect(harness.hasFileMatch('dist/browser/media', /a-[0-9A-Z]{8}\.unknown$/)).toBeTrue(); + }); + + it('should allow overriding default `.txt` extension behavior', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.txt', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.txt" with { loader: "file" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('a.txt'); + harness.expectFile('dist/browser/media/a.txt').toExist(); + }); + + it('should allow overriding default `.js` extension behavior', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.js', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.js" with { loader: "file" };\n console.log(contents);', + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('a.js'); + harness.expectFile('dist/browser/media/a.js').toExist(); + }); + + it('should fail with an error if an invalid loader attribute value is used', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + await harness.writeFile('./src/a.unknown', 'ABC'); + await harness.writeFile( + 'src/main.ts', + '// @ts-expect-error\nimport contents from "./a.unknown" with { loader: "invalid" };\n console.log(contents);', + ); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBe(false); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Unsupported loader import attribute'), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_spec.ts new file mode 100644 index 000000000000..7bfcca94d242 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-assets_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when input asset changes"', () => { + beforeEach(async () => { + // Application code is not needed for styles tests + await harness.writeFile('src/main.ts', 'console.log("TEST");'); + await harness.writeFile('public/asset.txt', 'foo'); + }); + + it('emits updated asset', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [ + { + glob: '**/*', + input: 'public', + }, + ], + watch: true, + }); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset.txt').content.toContain('foo'); + + await harness.writeFile('public/asset.txt', 'bar'); + }, + ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset.txt').content.toContain('bar'); + }, + ]); + }); + + it('remove deleted asset from output', async () => { + await Promise.all([ + harness.writeFile('public/asset-two.txt', 'bar'), + harness.writeFile('public/asset-one.txt', 'foo'), + ]); + + harness.useTarget('build', { + ...BASE_OPTIONS, + assets: [ + { + glob: '**/*', + input: 'public', + }, + ], + watch: true, + }); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset-one.txt').toExist(); + harness.expectFile('dist/browser/asset-two.txt').toExist(); + + await harness.removeFile('public/asset-two.txt'); + }, + + ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/asset-one.txt').toExist(); + harness.expectFile('dist/browser/asset-two.txt').toNotExist(); + }, + ]); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.ts new file mode 100644 index 000000000000..26ae35a8221f --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +/** + * Maximum time in milliseconds for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 30_000; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when component stylesheets change"', () => { + for (const aot of [true, false]) { + it(`updates component when imported sass changes with ${aot ? 'AOT' : 'JIT'}`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + aot, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('app.component.css', 'app.component.scss'), + ); + await harness.writeFile('src/app/app.component.scss', "@import './a';"); + await harness.writeFile('src/app/a.scss', '$primary: aqua;\\nh1 { color: $primary; }'); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.toContain('color: aqua'); + harness.expectFile('dist/browser/main.js').content.not.toContain('color: blue'); + + await harness.writeFile('src/app/a.scss', '$primary: blue;\\nh1 { color: $primary; }'); + }, + async ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/main.js').content.toContain('color: blue'); + + await harness.writeFile('src/app/a.scss', '$primary: green;\\nh1 { color: $primary; }'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/main.js').content.not.toContain('color: blue'); + harness.expectFile('dist/browser/main.js').content.toContain('color: green'); + }, + ]); + }); + } + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-errors_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-errors_spec.ts new file mode 100644 index 000000000000..fa384be88080 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-errors_spec.ts @@ -0,0 +1,247 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { + APPLICATION_BUILDER_INFO, + BASE_OPTIONS, + describeBuilder, + expectLog, + expectNoLog, +} from '../setup'; + +/** + * Maximum time in milliseconds for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 30_000; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuild Error Detection"', () => { + it('detects template errors with no AOT codegen or TS emit differences', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + const goodDirectiveContents = ` + import { Directive, Input } from '@angular/core'; + @Directive({ selector: 'dir', standalone: false }) + export class Dir { + @Input() foo: number; + } + `; + + const typeErrorText = `Type 'number' is not assignable to type 'string'.`; + + // Create a directive and add to application + await harness.writeFile('src/app/dir.ts', goodDirectiveContents); + await harness.writeFile( + 'src/app/app.module.ts', + ` + import { NgModule } from '@angular/core'; + import { BrowserModule } from '@angular/platform-browser'; + import { AppComponent } from './app.component'; + import { Dir } from './dir'; + @NgModule({ + declarations: [ + AppComponent, + Dir, + ], + imports: [ + BrowserModule + ], + providers: [], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + ); + + // Create app component that uses the directive + await harness.writeFile( + 'src/app/app.component.ts', + ` + import { Component } from '@angular/core' + @Component({ + selector: 'app-root', + standalone: false, + template: '', + }) + export class AppComponent { } + `, + ); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBeTrue(); + + // Update directive to use a different input type for 'foo' (number -> string) + // Should cause a template error + await harness.writeFile( + 'src/app/dir.ts', + ` + import { Directive, Input } from '@angular/core'; + @Directive({ selector: 'dir', standalone: false }) + export class Dir { + @Input() foo: string; + } + `, + ); + }, + async ({ result, logs }) => { + expect(result?.success).toBeFalse(); + expectLog(logs, typeErrorText); + + // Make an unrelated change to verify error cache was updated + // Should persist error in the next rebuild + await harness.modifyFile('src/main.ts', (content) => content + '\n'); + }, + async ({ result, logs }) => { + expect(result?.success).toBeFalse(); + expectLog(logs, typeErrorText); + + // Revert the directive change that caused the error + // Should remove the error + await harness.writeFile('src/app/dir.ts', goodDirectiveContents); + }, + async ({ result, logs }) => { + expect(result?.success).toBeTrue(); + expectNoLog(logs, typeErrorText); + + // Make an unrelated change to verify error cache was updated + // Should continue showing no error + await harness.modifyFile('src/main.ts', (content) => content + '\n'); + }, + ({ result, logs }) => { + expect(result?.success).toBeTrue(); + expectNoLog(logs, typeErrorText); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + + it('detects cumulative block syntax errors', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + await harness.executeWithCases( + [ + async () => { + // Add invalid block syntax + await harness.appendToFile('src/app/app.component.html', '@if-one'); + }, + async ({ logs }) => { + expectLog(logs, '@if-one'); + + // Make an unrelated change to verify error cache was updated + // Should persist error in the next rebuild + await harness.modifyFile('src/main.ts', (content) => content + '\n'); + }, + async ({ logs }) => { + expectLog(logs, '@if-one'); + + // Add more invalid block syntax + await harness.appendToFile('src/app/app.component.html', '@if-two'); + }, + async ({ logs }) => { + expectLog(logs, '@if-one'); + expectLog(logs, '@if-two'); + + // Add more invalid block syntax + await harness.appendToFile('src/app/app.component.html', '@if-three'); + }, + async ({ logs }) => { + expectLog(logs, '@if-one'); + expectLog(logs, '@if-two'); + expectLog(logs, '@if-three'); + + // Revert the changes that caused the error + // Should remove the error + await harness.writeFile('src/app/app.component.html', '

GOOD

'); + }, + ({ logs }) => { + expectNoLog(logs, '@if-one'); + expectNoLog(logs, '@if-two'); + expectNoLog(logs, '@if-three'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + + it('recovers from component stylesheet error', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + aot: false, + }); + + await harness.executeWithCases( + [ + async () => { + await harness.writeFile('src/app/app.component.css', 'invalid-css-content'); + }, + async ({ logs }) => { + expectLog(logs, 'invalid-css-content'); + + await harness.writeFile('src/app/app.component.css', 'p { color: green }'); + }, + ({ logs }) => { + expectNoLog(logs, 'invalid-css-content'); + + harness + .expectFile('dist/browser/main.js') + .content.toContain('p {\\n color: green;\\n}'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + + it('recovers from component template error', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + await harness.executeWithCases( + [ + async () => { + // Missing ending `>` on the div will cause an error + await harness.appendToFile('src/app/app.component.html', '
Hello, world! { + expectLog(logs, 'Unexpected character "EOF"'); + + await harness.appendToFile('src/app/app.component.html', '>'); + }, + async ({ logs }) => { + expectNoLog(logs, 'Unexpected character "EOF"'); + + harness.expectFile('dist/browser/main.js').content.toContain('Hello, world!'); + + // Make an additional valid change to ensure that rebuilds still trigger + await harness.appendToFile('src/app/app.component.html', '
Guten Tag
'); + }, + ({ logs }) => { + expectNoLog(logs, 'invalid-css-content'); + + harness.expectFile('dist/browser/main.js').content.toContain('Hello, world!'); + harness.expectFile('dist/browser/main.js').content.toContain('Guten Tag'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-general_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-general_spec.ts new file mode 100644 index 000000000000..d9ea8870f687 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-general_spec.ts @@ -0,0 +1,97 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +/** + * Maximum time in milliseconds for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 30_000; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuild updates in general cases"', () => { + it('detects changes after a file was deleted and recreated', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + const fileAContent = ` + console.log('FILE-A'); + export {}; + `; + + // Create a file and add to application + await harness.writeFile('src/app/file-a.ts', fileAContent); + await harness.writeFile( + 'src/app/app.component.ts', + ` + import { Component } from '@angular/core'; + import './file-a'; + @Component({ + selector: 'app-root', + standalone: false, + template: 'App component', + }) + export class AppComponent { } + `, + ); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser/main.js').content.toContain('FILE-A'); + + // Delete the imported file + await harness.removeFile('src/app/file-a.ts'); + }, + async ({ result }) => { + // Should fail from missing import + expect(result?.success).toBeFalse(); + + // Remove the failing import + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace(`import './file-a';`, ''), + ); + }, + async ({ result }) => { + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').content.not.toContain('FILE-A'); + + // Recreate the file and the import + await harness.writeFile('src/app/file-a.ts', fileAContent); + await harness.modifyFile( + 'src/app/app.component.ts', + (content) => `import './file-a';\n` + content, + ); + }, + async ({ result }) => { + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').content.toContain('FILE-A'); + + // Change the imported file + await harness.modifyFile('src/app/file-a.ts', (content) => + content.replace('FILE-A', 'FILE-B'), + ); + }, + ({ result }) => { + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').content.toContain('FILE-B'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_spec.ts new file mode 100644 index 000000000000..22c4c32202bd --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-global_styles_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when global stylesheets change"', () => { + beforeEach(async () => { + // Application code is not needed for styles tests + await harness.writeFile('src/main.ts', 'console.log("TEST");'); + }); + + it('rebuilds Sass stylesheet after error on rebuild from import', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + styles: ['src/styles.scss'], + }); + + await harness.writeFile('src/styles.scss', "@import './a';"); + await harness.writeFile('src/a.scss', '$primary: aqua;\\nh1 { color: $primary; }'); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: blue'); + + await harness.writeFile( + 'src/a.scss', + 'invalid-invalid-invalid\\nh1 { color: $primary; }', + ); + }, + async ({ result }) => { + expect(result?.success).toBe(false); + + await harness.writeFile('src/a.scss', '$primary: blue;\\nh1 { color: $primary; }'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.toContain('color: blue'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + + it('rebuilds Sass stylesheet after error on initial build from import', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + styles: ['src/styles.scss'], + }); + + await harness.writeFile('src/styles.scss', "@import './a';"); + await harness.writeFile('src/a.scss', 'invalid-invalid-invalid\\nh1 { color: $primary; }'); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBe(false); + + await harness.writeFile('src/a.scss', '$primary: aqua;\\nh1 { color: $primary; }'); + }, + async ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: blue'); + + await harness.writeFile('src/a.scss', '$primary: blue;\\nh1 { color: $primary; }'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.toContain('color: blue'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + + it('rebuilds dependent Sass stylesheets after error on initial build from import', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + styles: [ + { bundleName: 'styles', input: 'src/styles.scss' }, + { bundleName: 'other', input: 'src/other.scss' }, + ], + }); + + await harness.writeFile('src/styles.scss', "@import './a';"); + await harness.writeFile('src/other.scss', "@import './a'; h1 { color: green; }"); + await harness.writeFile('src/a.scss', 'invalid-invalid-invalid\\nh1 { color: $primary; }'); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBe(false); + + await harness.writeFile('src/a.scss', '$primary: aqua;\\nh1 { color: $primary; }'); + }, + async ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: blue'); + + harness.expectFile('dist/browser/other.css').content.toContain('color: green'); + harness.expectFile('dist/browser/other.css').content.toContain('color: aqua'); + harness.expectFile('dist/browser/other.css').content.not.toContain('color: blue'); + + await harness.writeFile('src/a.scss', '$primary: blue;\\nh1 { color: $primary; }'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/styles.css').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/styles.css').content.toContain('color: blue'); + + harness.expectFile('dist/browser/other.css').content.toContain('color: green'); + harness.expectFile('dist/browser/other.css').content.not.toContain('color: aqua'); + harness.expectFile('dist/browser/other.css').content.toContain('color: blue'); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-index-html_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-index-html_spec.ts new file mode 100644 index 000000000000..99603bc98cee --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-index-html_spec.ts @@ -0,0 +1,55 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +/** + * Maximum time in milliseconds for single build/rebuild + * This accounts for CI variability. + */ +export const BUILD_TIMEOUT = 30_000; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when input index HTML changes"', () => { + beforeEach(async () => { + // Application code is not needed for styles tests + await harness.writeFile('src/main.ts', 'console.log("TEST");'); + }); + + it('rebuilds output index HTML', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/index.html').content.toContain('charset="utf-8"'); + + await harness.modifyFile('src/index.html', (content) => + content.replace('charset="utf-8"', 'abc'), + ); + }, + async ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/index.html').content.not.toContain('charset="utf-8"'); + + await harness.modifyFile('src/index.html', (content) => + content.replace('abc', 'charset="utf-8"'), + ); + }, + ({ result }) => { + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/index.html').content.toContain('charset="utf-8"'); + }, + ]); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts new file mode 100644 index 000000000000..2fdad10f8d8d --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts @@ -0,0 +1,103 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { + APPLICATION_BUILDER_INFO, + BASE_OPTIONS, + describeBuilder, + expectLog, + expectNoLog, +} from '../setup'; + +/** + * A regular expression used to check if a built worker is correctly referenced in application code. + */ +const REFERENCED_WORKER_REGEXP = + /new Worker\(new URL\("worker-[A-Z0-9]{8}\.js", import\.meta\.url\)/; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when Web Worker files change"', () => { + it('Recovers from error when directly referenced worker file is changed', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + const workerCodeFile = ` + console.log('WORKER FILE'); + `; + + const errorText = `Expected ";" but found "~"`; + + // Create a worker file + await harness.writeFile('src/app/worker.ts', workerCodeFile); + + // Create app component that uses the directive + await harness.writeFile( + 'src/app/app.component.ts', + ` + import { Component } from '@angular/core' + @Component({ + selector: 'app-root', + standalone: false, + template: '

Worker Test

', + }) + export class AppComponent { + worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' }); + } + `, + ); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBeTrue(); + + // Ensure built worker is referenced in the application code + harness.expectFile('dist/browser/main.js').content.toMatch(REFERENCED_WORKER_REGEXP); + + // Update the worker file to be invalid syntax + await harness.writeFile('src/app/worker.ts', `asd;fj$3~kls;kd^(*fjlk;sdj---flk`); + }, + async ({ result, logs }) => { + expect(result?.success).toBeFalse(); + expectLog(logs, errorText); + + // Make an unrelated change to verify error cache was updated + // Should persist error in the next rebuild + await harness.modifyFile('src/main.ts', (content) => content + '\n'); + }, + async ({ logs }) => { + expectLog(logs, errorText); + + // Revert the change that caused the error + // Should remove the error + await harness.writeFile('src/app/worker.ts', workerCodeFile); + }, + async ({ result, logs }) => { + expect(result?.success).toBeTrue(); + expectNoLog(logs, errorText); + + // Make an unrelated change to verify error cache was updated + // Should continue showing no error + await harness.modifyFile('src/main.ts', (content) => content + '\n'); + }, + ({ result, logs }) => { + expect(result?.success).toBeTrue(); + expectNoLog(logs, errorText); + + // Ensure built worker is referenced in the application code + harness.expectFile('dist/browser/main.js').content.toMatch(REFERENCED_WORKER_REGEXP); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts new file mode 100644 index 000000000000..0adc77b5311a --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/stylesheet-url-resolution_spec.ts @@ -0,0 +1,458 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { OutputHashing } from '../../schema'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Stylesheet url() Resolution"', () => { + it('should show a note when using tilde prefix in a directly referenced stylesheet', async () => { + await harness.writeFile( + 'src/styles.css', + ` + .a { + background-image: url("~/image.jpg") + } + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBe(false); + + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('You can remove the tilde and'), + }), + ); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Preprocessor stylesheets may not show the exact'), + }), + ); + }); + + it('should show a note when using tilde prefix in an imported CSS stylesheet', async () => { + await harness.writeFile( + 'src/styles.css', + ` + @import "a.css"; + `, + ); + await harness.writeFile( + 'src/a.css', + ` + .a { + background-image: url("~/image.jpg") + } + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBe(false); + + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('You can remove the tilde and'), + }), + ); + }); + + it('should show a note when using tilde prefix in an imported Sass stylesheet', async () => { + await harness.writeFile( + 'src/styles.scss', + ` + @import "a"; + `, + ); + await harness.writeFile( + 'src/a.scss', + ` + .a { + background-image: url("~/image.jpg") + } + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.scss'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBe(false); + + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('You can remove the tilde and'), + }), + ); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Preprocessor stylesheets may not show the exact'), + }), + ); + }); + + it('should show a note when using caret prefix in a directly referenced stylesheet', async () => { + await harness.writeFile( + 'src/styles.css', + ` + .a { + background-image: url("^image.jpg") + } + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBe(false); + + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('You can remove the caret and'), + }), + ); + }); + + it('should show a note when using caret prefix in an imported Sass stylesheet', async () => { + await harness.writeFile( + 'src/styles.scss', + ` + @import "a"; + `, + ); + await harness.writeFile( + 'src/a.scss', + ` + .a { + background-image: url("^image.jpg") + } + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.scss'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBe(false); + + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('You can remove the caret and'), + }), + ); + }); + + it('should not rebase a URL with a namespaced Sass variable reference that points to an absolute asset', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @use './b' as named; + .a { + background-image: url(named.$my-var) + } + `, + 'src/theme/b.scss': `@forward './c.scss' show $my-var;`, + 'src/theme/c.scss': `$my-var: "https://example.com/example.png";`, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/styles.css') + .content.toContain('url(https://example.com/example.png)'); + }); + + it('should not rebase a URL with a Sass variable reference that points to an absolute asset', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + .a { + background-image: url($my-var) + } + `, + 'src/theme/b.scss': `$my-var: "https://example.com/example.png";`, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/styles.css') + .content.toContain('url(https://example.com/example.png)'); + }); + + it('should rebase a URL with a namespaced Sass variable referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @use './b' as named; + .a { + background-image: url(named.$my-var) + } + `, + 'src/theme/b.scss': `@forward './c.scss' show $my-var;`, + 'src/theme/c.scss': `$my-var: "./images/logo.svg";`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should rebase a URL with a hyphen-namespaced Sass variable referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @use './b' as named-hyphen; + .a { + background-image: url(named-hyphen.$my-var) + } + `, + 'src/theme/b.scss': `@forward './c.scss' show $my-var;`, + 'src/theme/c.scss': `$my-var: "./images/logo.svg";`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should rebase a URL with a underscore-namespaced Sass variable referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @use './b' as named_underscore; + .a { + background-image: url(named_underscore.$my-var) + } + `, + 'src/theme/b.scss': `@forward './c.scss' show $my-var;`, + 'src/theme/c.scss': `$my-var: "./images/logo.svg";`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should rebase a URL with a Sass variable referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + .a { + background-image: url($my-var) + } + `, + 'src/theme/b.scss': `$my-var: "./images/logo.svg";`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should rebase a URL with an leading interpolation referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + .a { + background-image: url(#{$my-var}logo.svg) + } + `, + 'src/theme/b.scss': `$my-var: "./images/";`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should rebase a URL with interpolation using concatenation referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + $extra-var: "2"; + $postfix-var: "xyz"; + .a { + background-image: url("#{$my-var}logo#{$extra-var+ "-" + $postfix-var}.svg") + } + `, + 'src/theme/b.scss': `$my-var: "./images/";`, + 'src/theme/images/logo2-xyz.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/styles.css') + .content.toContain(`url("./media/logo2-xyz.svg")`); + harness.expectFile('dist/browser/media/logo2-xyz.svg').toExist(); + }); + + it('should rebase a URL with an non-leading interpolation referencing a local resource', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + .a { + background-image: url(./#{$my-var}logo.svg) + } + `, + 'src/theme/b.scss': `$my-var: "./images/";`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should not rebase Sass function definition with name ending in "url"', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + @import './b'; + .a { + $asset: my-function-url('logo'); + background-image: url($asset) + } + `, + 'src/theme/b.scss': `@function my-function-url($name) { @return "./images/" + $name + ".svg"; }`, + 'src/theme/images/logo.svg': ``, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url("./media/logo.svg")`); + harness.expectFile('dist/browser/media/logo.svg').toExist(); + }); + + it('should not process a URL that has been marked as external', async () => { + await harness.writeFiles({ + 'src/styles.scss': `@use 'theme/a';`, + 'src/theme/a.scss': ` + .a { + background-image: url("assets/logo.svg") + } + `, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + outputHashing: OutputHashing.None, + externalDependencies: ['assets/*'], + styles: ['src/styles.scss'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/styles.css').content.toContain(`url(assets/logo.svg)`); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts new file mode 100644 index 000000000000..41ae225e2d3d --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/stylesheet_autoprefixer_spec.ts @@ -0,0 +1,259 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +const styleBaseContent: Record = Object.freeze({ + 'css': ` + @import url(imported-styles.css); + div { hyphens: none; } + `, +}); + +const styleImportedContent: Record = Object.freeze({ + 'css': 'section { hyphens: none; }', +}); + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Stylesheet autoprefixer"', () => { + for (const ext of ['css'] /* ['css', 'sass', 'scss', 'less'] */) { + it(`should add prefixes for listed browsers in global styles [${ext}]`, async () => { + await harness.writeFile( + '.browserslistrc', + ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + ); + + await harness.writeFiles({ + [`src/styles.${ext}`]: styleBaseContent[ext], + [`src/imported-styles.${ext}`]: styleImportedContent[ext], + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [`src/styles.${ext}`], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/styles.css') + .content.toMatch(/section\s*{\s*-webkit-hyphens:\s*none;\s*hyphens:\s*none;\s*}/); + harness + .expectFile('dist/browser/styles.css') + .content.toMatch(/div\s*{\s*-webkit-hyphens:\s*none;\s*hyphens:\s*none;\s*}/); + }); + + it(`should not add prefixes if not required by browsers in global styles [${ext}]`, async () => { + await harness.writeFile( + '.browserslistrc', + ` + Edge 110 + `, + ); + + await harness.writeFiles({ + [`src/styles.${ext}`]: styleBaseContent[ext], + [`src/imported-styles.${ext}`]: styleImportedContent[ext], + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [`src/styles.${ext}`], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/styles.css') + .content.toMatch(/section\s*{\s*hyphens:\s*none;\s*}/); + harness + .expectFile('dist/browser/styles.css') + .content.toMatch(/div\s*{\s*hyphens:\s*none;\s*}/); + }); + + it(`should add prefixes for listed browsers in external component styles [${ext}]`, async () => { + await harness.writeFile( + '.browserslistrc', + ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + ); + + await harness.writeFiles({ + [`src/app/app.component.${ext}`]: styleBaseContent[ext], + [`src/app/imported-styles.${ext}`]: styleImportedContent[ext], + }); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('./app.component.css', `./app.component.${ext}`), + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/main.js') + .content.toMatch(/{\\n\s*-webkit-hyphens:\s*none;\\n\s*hyphens:\s*none;\\n\s*}/); + harness + .expectFile('dist/browser/main.js') + .content.toMatch(/{\\n\s*-webkit-hyphens:\s*none;\\n\s*hyphens:\s*none;\\n\s*}/); + }); + + it(`should not add prefixes if not required by browsers in external component styles [${ext}]`, async () => { + await harness.writeFile( + '.browserslistrc', + ` + Edge 110 + `, + ); + + await harness.writeFiles({ + [`src/app/app.component.${ext}`]: styleBaseContent[ext], + [`src/app/imported-styles.${ext}`]: styleImportedContent[ext], + }); + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('./app.component.css', `./app.component.${ext}`), + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/main.js') + .content.toMatch(/{\\n\s*hyphens:\s*none;\\n\s*}/); + harness + .expectFile('dist/browser/main.js') + .content.toMatch(/{\\n\s*hyphens:\s*none;\\n\s*}/); + }); + } + + it('should add prefixes for listed browsers in inline component styles', async () => { + await harness.writeFile( + '.browserslistrc', + ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + ); + + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content + .replace('styleUrls', 'styles') + .replace('./app.component.css', 'div { hyphens: none; }'); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/main.js') + // div[_ngcontent-%COMP%] {\n -webkit-hyphens: none;\n hyphens: none;\n}\n + .content.toMatch(/{\\n\s*-webkit-hyphens:\s*none;\\n\s*hyphens:\s*none;\\n\s*}/); + }); + + it('should not add prefixes if not required by browsers in inline component styles', async () => { + await harness.writeFile( + '.browserslistrc', + ` + Edge 110 + `, + ); + + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content + .replace('styleUrls', 'styles') + .replace('./app.component.css', 'div { hyphens: none; }'); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').content.toMatch(/{\\n\s*hyphens:\s*none;\\n\s*}/); + }); + + it('should add prefixes for listed browsers in inline template styles', async () => { + await harness.writeFile( + '.browserslistrc', + ` + Safari 15.4 + Edge 104 + Firefox 91 + `, + ); + + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('styleUrls', 'styles').replace('./app.component.css', ''); + }); + await harness.modifyFile('src/app/app.component.html', (content) => { + return `\n${content}`; + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/main.js') + // div[_ngcontent-%COMP%] {\n -webkit-hyphens: none;\n hyphens: none;\n}\n + .content.toMatch(/{\\n\s*-webkit-hyphens:\s*none;\\n\s*hyphens:\s*none;\\n\s*}/); + }); + + it('should not add prefixes if not required by browsers in inline template styles', async () => { + await harness.writeFile( + '.browserslistrc', + ` + Edge 110 + `, + ); + + await harness.modifyFile('src/app/app.component.ts', (content) => { + return content.replace('styleUrls', 'styles').replace('./app.component.css', ''); + }); + await harness.modifyFile('src/app/app.component.html', (content) => { + return `\n${content}`; + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').content.toMatch(/{\\n\s*hyphens:\s*none;\\n\s*}/); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/typescript-incremental_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/typescript-incremental_spec.ts new file mode 100644 index 000000000000..2c73e66d9f8b --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/typescript-incremental_spec.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 { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "TypeScript explicit incremental option usage"', () => { + it('should successfully build with incremental disabled', async () => { + // Disable tsconfig incremental option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.incremental = false; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/typescript-isolated-modules_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/typescript-isolated-modules_spec.ts new file mode 100644 index 000000000000..06e66cbd6da9 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/typescript-isolated-modules_spec.ts @@ -0,0 +1,79 @@ +/** + * @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 { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "TypeScript isolated modules direct transpilation"', () => { + it('should successfully build with isolated modules enabled and disabled optimizations', async () => { + // Enable tsconfig isolatedModules option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.isolatedModules = true; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + + it('should successfully build with isolated modules enabled and enabled optimizations', async () => { + // Enable tsconfig isolatedModules option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.isolatedModules = true; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + + it('supports TSX files with isolated modules enabled and enabled optimizations', async () => { + // Enable tsconfig isolatedModules option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.isolatedModules = true; + tsconfig.compilerOptions.jsx = 'react-jsx'; + + return JSON.stringify(tsconfig); + }); + + await harness.writeFile('src/types.d.ts', `declare module 'react/jsx-runtime' { jsx: any }`); + await harness.writeFile('src/abc.tsx', `export function hello() { return

Hello

; }`); + await harness.modifyFile( + 'src/main.ts', + (content) => content + `import { hello } from './abc'; console.log(hello());`, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + optimization: true, + externalDependencies: ['react'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/typescript-path-mapping_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/typescript-path-mapping_spec.ts new file mode 100644 index 000000000000..41539df239f2 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/typescript-path-mapping_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "TypeScript Path Mapping"', () => { + it('should resolve TS files when imported with a path mapping', async () => { + // Change main module import to use path mapping + await harness.modifyFile('src/main.ts', (content) => + content.replace(`'./app/app.module'`, `'@root/app.module'`), + ); + + // Add a path mapping for `@root` + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.paths = { + '@root/*': ['./src/app/*'], + }; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + + it('should fail to resolve if no path mapping for an import is present', async () => { + // Change main module import to use path mapping + await harness.modifyFile('src/main.ts', (content) => + content.replace(`'./app/app.module'`, `'@root/app.module'`), + ); + + // Add a path mapping for `@not-root` + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.paths = { + '@not-root/*': ['./src/app/*'], + }; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBe(false); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Could not resolve "@root/app.module"'), + }), + ); + }); + + it('should resolve JS files when imported with a path mapping', async () => { + // Change main module import to use path mapping + await harness.modifyFile('src/main.ts', (content) => + content.replace(`'./app/app.module'`, `'app-module'`), + ); + + await harness.writeFiles({ + 'a.js': `export * from './src/app/app.module';\n\nconsole.log('A');`, + 'a.d.ts': `export * from './src/app/app.module';`, + }); + + // Add a path mapping for `@root` + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.paths = { + 'app-module': ['a.js'], + }; + + return JSON.stringify(tsconfig); + }); + + // app.module needs to be manually included since it is not referenced via a TS file + // with the test path mapping in place. + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.files.push('app/app.module.ts'); + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain(`console.log("A")`); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/typescript-rebuild-lazy_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/typescript-rebuild-lazy_spec.ts new file mode 100644 index 000000000000..ba01e2a27dce --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/typescript-rebuild-lazy_spec.ts @@ -0,0 +1,72 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { OutputHashing } from '../../schema'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder, expectLog } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + beforeEach(async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files = ['main.server.ts', 'main.ts']; + + return JSON.stringify(tsConfig); + }); + + await harness.writeFiles({ + 'src/lazy.ts': `export const foo: number = 1;`, + 'src/main.ts': `export async function fn () { + const lazy = await import('./lazy'); + return lazy.foo; + }`, + 'src/main.server.ts': `export { fn as default } from './main';`, + }); + }); + + describe('Behavior: "Rebuild both server and browser bundles when using lazy loading"', () => { + it('detect changes and errors when expected', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + namedChunks: true, + outputHashing: OutputHashing.None, + server: 'src/main.server.ts', + ssr: true, + }); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBeTrue(); + + // Add valid code + await harness.appendToFile('src/lazy.ts', `console.log('foo');`); + }, + async ({ result }) => { + expect(result?.success).toBeTrue(); + + // Update type of 'foo' to invalid (number -> string) + await harness.writeFile('src/lazy.ts', `export const foo: string = 1;`); + }, + async ({ result, logs }) => { + expect(result?.success).toBeFalse(); + expectLog(logs, `Type 'number' is not assignable to type 'string'.`); + + // Fix TS error + await harness.writeFile('src/lazy.ts', `export const foo: string = "1";`); + }, + ({ result }) => { + expect(result?.success).toBeTrue(); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/typescript-rebuild-touch-file_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/typescript-rebuild-touch-file_spec.ts new file mode 100644 index 000000000000..eeb160ebef47 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/typescript-rebuild-touch-file_spec.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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Rebuilds when touching file"', () => { + for (const aot of [true, false]) { + it(`Rebuild correctly when file is touched with ${aot ? 'AOT' : 'JIT'}`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + aot, + }); + + await harness.executeWithCases( + [ + async ({ result }) => { + expect(result?.success).toBeTrue(); + // Touch a file without doing any changes. + await harness.modifyFile('src/app/app.component.ts', (content) => content); + }, + async ({ result }) => { + expect(result?.success).toBeTrue(); + await harness.removeFile('src/app/app.component.ts'); + }, + ({ result }) => { + expect(result?.success).toBeFalse(); + }, + ], + { outputLogsOnFailure: false }, + ); + }); + } + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/typescript-resolve-json_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/typescript-resolve-json_spec.ts new file mode 100644 index 000000000000..cf21d5545f7a --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/typescript-resolve-json_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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "TypeScript JSON module resolution"', () => { + it('should resolve JSON files when imported with resolveJsonModule enabled', async () => { + await harness.writeFiles({ + 'src/x.json': `{"a": 1}`, + 'src/main.ts': `import * as x from './x.json'; console.log(x);`, + }); + + // Enable tsconfig resolveJsonModule option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.moduleResolution = 'node'; + tsconfig.compilerOptions.resolveJsonModule = true; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + }); + + it('should fail to resolve with TS if resolveJsonModule is not present', async () => { + await harness.writeFiles({ + 'src/x.json': `{"a": 1}`, + 'src/main.ts': `import * as x from './x.json'; console.log(x);`, + }); + + // Enable tsconfig resolveJsonModule option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.moduleResolution = 'node'; + tsconfig.compilerOptions.resolveJsonModule = undefined; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBe(false); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(`Cannot find module './x.json'`), + }), + ); + }); + + it('should fail to resolve with TS if resolveJsonModule is disabled', async () => { + await harness.writeFiles({ + 'src/x.json': `{"a": 1}`, + 'src/main.ts': `import * as x from './x.json'; console.log(x);`, + }); + + // Enable tsconfig resolveJsonModule option in tsconfig + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions.moduleResolution = 'node'; + tsconfig.compilerOptions.resolveJsonModule = false; + + return JSON.stringify(tsconfig); + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBe(false); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching(`Cannot find module './x.json'`), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/wasm-esm_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/wasm-esm_spec.ts new file mode 100644 index 000000000000..5ae62f020c1c --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/wasm-esm_spec.ts @@ -0,0 +1,275 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +/** + * Compiled and base64 encoded WASM file for the following WAT: + * ``` + * (module + * (export "multiply" (func $multiply)) + * (func $multiply (param i32 i32) (result i32) + * local.get 0 + * local.get 1 + * i32.mul + * ) + * ) + * ``` + */ +const exportWasmBase64 = + 'AGFzbQEAAAABBwFgAn9/AX8DAgEABwwBCG11bHRpcGx5AAAKCQEHACAAIAFsCwAXBG5hbWUBCwEACG11bHRpcGx5AgMBAAA='; +const exportWasmBytes = Buffer.from(exportWasmBase64, 'base64'); + +/** + * Compiled and base64 encoded WASM file for the following WAT: + * ``` + * (module + * (import "./values" "getValue" (func $getvalue (result i32))) + * (export "multiply" (func $multiply)) + * (export "subtract1" (func $subtract)) + * (func $multiply (param i32 i32) (result i32) + * local.get 0 + * local.get 1 + * i32.mul + * ) + * (func $subtract (param i32) (result i32) + * call $getvalue + * local.get 0 + * i32.sub + * ) + * ) + * ``` + */ +const importWasmBase64 = + 'AGFzbQEAAAABEANgAAF/YAJ/fwF/YAF/AX8CFQEILi92YWx1ZXMIZ2V0VmFsdWUAAAMDAgECBxgCCG11bHRpcGx5AAEJc3VidHJhY3QxAAIKEQIHACAAIAFsCwcAEAAgAGsLAC8EbmFtZQEfAwAIZ2V0dmFsdWUBCG11bHRpcGx5AghzdWJ0cmFjdAIHAwAAAQACAA=='; +const importWasmBytes = Buffer.from(importWasmBase64, 'base64'); + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Supports WASM/ES module integration"', () => { + it('should inject initialization code and add an export', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // Create WASM file + await harness.writeFile('src/multiply.wasm', exportWasmBytes); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + // @ts-ignore + import { multiply } from './multiply.wasm'; + + console.log(multiply(4, 5)); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + // Ensure initialization code and export name is present in output code + harness.expectFile('dist/browser/main.js').content.toContain('WebAssembly.instantiate'); + harness.expectFile('dist/browser/main.js').content.toContain('multiply'); + }); + + it('should compile successfully with a provided type definition file', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // Create WASM file + await harness.writeFile('src/multiply.wasm', exportWasmBytes); + await harness.writeFile( + 'src/multiply.wasm.d.ts', + 'export declare function multiply(a: number, b: number): number;', + ); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + import { multiply } from './multiply.wasm'; + + console.log(multiply(4, 5)); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + // Ensure initialization code and export name is present in output code + harness.expectFile('dist/browser/main.js').content.toContain('WebAssembly.instantiate'); + harness.expectFile('dist/browser/main.js').content.toContain('multiply'); + }); + + it('should add WASM defined imports and include resolved TS file for import', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // Create WASM file + await harness.writeFile('src/subtract.wasm', importWasmBytes); + + // Create TS file that is expect by WASM file + await harness.writeFile( + 'src/values.ts', + ` + export function getValue(): number { return 100; } + `, + ); + // The file is not imported into any actual TS files so it needs to be manually added to the TypeScript program + await harness.modifyFile('src/tsconfig.app.json', (content) => + content.replace('"main.ts",', '"main.ts","values.ts",'), + ); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + // @ts-ignore + import { subtract1 } from './subtract.wasm'; + + console.log(subtract1(5)); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + // Ensure initialization code and export name is present in output code + harness.expectFile('dist/browser/main.js').content.toContain('WebAssembly.instantiate'); + harness.expectFile('dist/browser/main.js').content.toContain('subtract1'); + harness.expectFile('dist/browser/main.js').content.toContain('./values'); + harness.expectFile('dist/browser/main.js').content.toContain('getValue'); + }); + + it('should add WASM defined imports and include resolved JS file for import', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // Create WASM file + await harness.writeFile('src/subtract.wasm', importWasmBytes); + + // Create JS file that is expect by WASM file + await harness.writeFile( + 'src/values.js', + ` + export function getValue() { return 100; } + `, + ); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + // @ts-ignore + import { subtract1 } from './subtract.wasm'; + + console.log(subtract1(5)); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + // Ensure initialization code and export name is present in output code + harness.expectFile('dist/browser/main.js').content.toContain('WebAssembly.instantiate'); + harness.expectFile('dist/browser/main.js').content.toContain('subtract1'); + harness.expectFile('dist/browser/main.js').content.toContain('./values'); + harness.expectFile('dist/browser/main.js').content.toContain('getValue'); + }); + + it('should inline WASM files less than 10kb', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // Create WASM file + await harness.writeFile('src/multiply.wasm', exportWasmBytes); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + // @ts-ignore + import { multiply } from './multiply.wasm'; + + console.log(multiply(4, 5)); + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + // Ensure WASM is present in output code + harness.expectFile('dist/browser/main.js').content.toContain(exportWasmBase64); + }); + + it('should show an error on invalid WASM file', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + // Create WASM file + await harness.writeFile('src/multiply.wasm', 'NOT_WASM'); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + // @ts-ignore + import { multiply } from './multiply.wasm'; + + console.log(multiply(4, 5)); + `, + ); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Unable to analyze WASM file'), + }), + ); + }); + + it('should show an error if using Zone.js', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: ['zone.js'], + }); + + // Create WASM file + await harness.writeFile('src/multiply.wasm', importWasmBytes); + + // Create main file that uses the WASM file + await harness.writeFile( + 'src/main.ts', + ` + // @ts-ignore + import { multiply } from './multiply.wasm'; + + console.log(multiply(4, 5)); + `, + ); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching( + 'WASM/ES module integration imports are not supported with Zone.js applications', + ), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.ts new file mode 100644 index 000000000000..135d5ff68165 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +/** + * A regular expression used to check if a built worker is correctly referenced in application code. + */ +const REFERENCED_WORKER_REGEXP = + /new Worker\(new URL\("worker-[A-Z0-9]{8}\.js", import\.meta\.url\)/; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Bundles web worker files within application code"', () => { + it('should use the worker entry point when worker lazy chunks are present', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const workerCodeFile = ` + addEventListener('message', () => { + import('./extra').then((m) => console.log(m.default)); + }); + `; + const extraWorkerCodeFile = ` + export default 'WORKER FILE'; + `; + + // Create a worker file + await harness.writeFile('src/app/worker.ts', workerCodeFile); + await harness.writeFile('src/app/extra.ts', extraWorkerCodeFile); + + // Create app component that uses the directive + await harness.writeFile( + 'src/app/app.component.ts', + ` + import { Component } from '@angular/core' + @Component({ + selector: 'app-root', + standalone: false, + template: '

Worker Test

', + }) + export class AppComponent { + worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' }); + } + `, + ); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + // Ensure built worker is referenced in the application code + harness.expectFile('dist/browser/main.js').content.toMatch(REFERENCED_WORKER_REGEXP); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/options/allowed-common-js-dependencies_spec.ts b/packages/angular/build/src/builders/application/tests/options/allowed-common-js-dependencies_spec.ts new file mode 100644 index 000000000000..bcc361ccdbe1 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/options/allowed-common-js-dependencies_spec.ts @@ -0,0 +1,178 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { + APPLICATION_BUILDER_INFO, + BASE_OPTIONS, + describeBuilder, + expectLog, + expectNoLog, +} from '../setup'; + +describeBuilder(buildApplication, APPLICATION_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 'buffer';`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: [], + optimization: true, + aot, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectLog(logs, /Module 'buffer' used by 'src\/app\/app\.component\.ts' is not ESM/); + expectLog(logs, /CommonJS or AMD dependencies/); + expectNoLog( + logs, + 'base64-js', + '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 'buffer'; + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: ['buffer', 'base64-js', 'ieee754'], + optimization: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectNoLog(logs, /CommonJS or AMD dependencies/); + }); + + it('should not show warning when all dependencies are allowed by wildcard', async () => { + // Add a Common JS dependency + await harness.appendToFile( + 'src/app/app.component.ts', + ` + import 'buffer'; + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: ['*'], + optimization: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectNoLog(logs, /CommonJS or AMD dependencies/); + }); + + it('should not show warning when depending on zone.js', async () => { + // Add a Common JS dependency + await harness.appendToFile( + 'src/app/app.component.ts', + ` + import 'zone.js'; + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: [], + optimization: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectNoLog(logs, /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: [], + optimization: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectNoLog(logs, /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: [], + optimization: true, + aot: false, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectNoLog(logs, /CommonJS or AMD dependencies/); + }); + + it('should not show warning for relative imports', async () => { + await harness.appendToFile('src/main.ts', `import './abc';`); + await harness.writeFile('src/abc.ts', 'console.log("abc");'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + allowedCommonJsDependencies: [], + optimization: true, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expectNoLog(logs, /CommonJS or AMD dependencies/); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts b/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts new file mode 100644 index 000000000000..9c8384b29efc --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts @@ -0,0 +1,180 @@ +/** + * @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.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +const appShellRouteFiles: Record = { + 'src/styles.css': `p { color: #000 }`, + 'src/app/app-shell/app-shell.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-app-shell', + standalone: false, + styles: ['div { color: #fff; }'], + template: '

app-shell works!

', + }) + export class AppShellComponent {}`, + 'src/main.server.ts': ` + import { AppServerModule } from './app/app.module.server'; + export default AppServerModule; + `, + '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, + AppRoutingModule, + RouterModule + ], + bootstrap: [AppComponent] + }) + export class AppModule { } + `, + 'src/app/app.module.server.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 { platformBrowser } from '@angular/platform-browser'; + import { AppModule } from './app/app.module'; + + platformBrowser().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': ``, +}; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + beforeEach(async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files ??= []; + tsConfig.files.push('main.server.ts'); + + return JSON.stringify(tsConfig); + }); + + await harness.writeFiles(appShellRouteFiles); + }); + + describe('Option: "appShell"', () => { + it('renders the application shell', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + polyfills: ['zone.js'], + appShell: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser/main.js').toExist(); + const indexFileContent = harness.expectFile('dist/browser/index.html').content; + indexFileContent.toContain('app-shell works!'); + }); + + it('critical CSS is inlined', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + polyfills: ['zone.js'], + appShell: true, + styles: ['src/styles.css'], + optimization: { + styles: { + minify: true, + inlineCritical: true, + }, + }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + const indexFileContent = harness.expectFile('dist/browser/index.html').content; + indexFileContent.toContain('app-shell works!'); + indexFileContent.toContain('p{color:#000}'); + indexFileContent.toContain( + ``, + ); + }); + + it('applies CSP nonce to critical CSS', async () => { + await harness.modifyFile('src/index.html', (content) => + content.replace(/`, + ); + indexFileContent.toContain(' + + +
+

Blocked request. This host ("${hostname}") is not allowed.

+

To allow this host, add it to allowedHosts under the serve target in angular.json.

+
{
+  "serve": {
+    "options": {
+      "allowedHosts": ["${hostname}"]
+    }
+  }
+}
+
+ + `; +} diff --git a/packages/angular/build/src/tools/vite/middlewares/html-fallback-middleware.ts b/packages/angular/build/src/tools/vite/middlewares/html-fallback-middleware.ts new file mode 100644 index 000000000000..cd52b8a7904f --- /dev/null +++ b/packages/angular/build/src/tools/vite/middlewares/html-fallback-middleware.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.dev/license + */ + +import type { ServerResponse } from 'node:http'; +import type { Connect } from 'vite'; +import { lookupMimeTypeFromRequest } from '../utils'; + +const ALLOWED_FALLBACK_METHODS = Object.freeze(['GET', 'HEAD']); + +export function angularHtmlFallbackMiddleware( + req: Connect.IncomingMessage, + _res: ServerResponse, + next: Connect.NextFunction, +): void { + // Similar to how it is handled in vite + // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L15C19-L15C45 + if (!req.method || !ALLOWED_FALLBACK_METHODS.includes(req.method)) { + // No fallback for unsupported request methods + next(); + + return; + } + + if (req.url) { + const mimeType = lookupMimeTypeFromRequest(req.url); + if ( + (mimeType === 'text/html' || mimeType === 'application/xhtml+xml') && + !/^\/index\.(?:csr\.)?html/.test(req.url) + ) { + // eslint-disable-next-line no-console + console.warn( + `Request for HTML file "${req.url}" was received but no asset found. Asset may be missing from build.`, + ); + } else if (mimeType) { + // No fallback for request of asset-like files + next(); + + return; + } + } + + if ( + !req.headers.accept || + req.headers.accept.includes('text/html') || + req.headers.accept.includes('text/*') || + req.headers.accept.includes('*/*') + ) { + req.url = '/index.html'; + } + + next(); +} diff --git a/packages/angular/build/src/tools/vite/middlewares/index-html-middleware.ts b/packages/angular/build/src/tools/vite/middlewares/index-html-middleware.ts new file mode 100644 index 000000000000..7959ccb7ec03 --- /dev/null +++ b/packages/angular/build/src/tools/vite/middlewares/index-html-middleware.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.dev/license + */ + +import { extname } from 'node:path'; +import type { Connect, ViteDevServer } from 'vite'; +import { AngularMemoryOutputFiles, pathnameWithoutBasePath } from '../utils'; + +export function createAngularIndexHtmlMiddleware( + server: ViteDevServer, + outputFiles: AngularMemoryOutputFiles, + resetComponentUpdates: () => void, + indexHtmlTransformer: ((content: string) => Promise) | undefined, +): Connect.NextHandleFunction { + return function angularIndexHtmlMiddleware(req, res, next) { + if (!req.url) { + next(); + + return; + } + + // Parse the incoming request. + // The base of the URL is unused but required to parse the URL. + const pathname = pathnameWithoutBasePath(req.url, server.config.base); + const extension = extname(pathname); + if (extension !== '.html') { + next(); + + return; + } + + const rawHtml = outputFiles.get(pathname)?.contents; + if (!rawHtml) { + next(); + + return; + } + + // A request for the index indicates a full page reload request. + resetComponentUpdates(); + + server + .transformIndexHtml(req.url, Buffer.from(rawHtml).toString('utf-8')) + .then(async (processedHtml) => { + if (indexHtmlTransformer) { + processedHtml = await indexHtmlTransformer(processedHtml); + } + + res.setHeader('Content-Type', 'text/html'); + res.setHeader('Cache-Control', 'no-cache'); + res.end(processedHtml); + }) + .catch((error) => next(error)); + }; +} diff --git a/packages/angular/build/src/tools/vite/middlewares/index.ts b/packages/angular/build/src/tools/vite/middlewares/index.ts new file mode 100644 index 000000000000..807e739eed59 --- /dev/null +++ b/packages/angular/build/src/tools/vite/middlewares/index.ts @@ -0,0 +1,20 @@ +/** + * @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.dev/license + */ + +export { type ComponentStyleRecord, createAngularAssetsMiddleware } from './assets-middleware'; +export { angularHtmlFallbackMiddleware } from './html-fallback-middleware'; +export { createAngularIndexHtmlMiddleware } from './index-html-middleware'; +export { + createAngularSsrExternalMiddleware, + createAngularSsrInternalMiddleware, +} from './ssr-middleware'; +export { createAngularHeadersMiddleware } from './headers-middleware'; +export { createAngularComponentMiddleware } from './component-middleware'; +export { createChromeDevtoolsMiddleware } from './chrome-devtools-middleware'; +export { patchHostValidationMiddleware } from './host-check-middleware'; +export { patchBaseMiddleware } from './base-middleware'; diff --git a/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.ts b/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.ts new file mode 100644 index 000000000000..4b0a8d8390f1 --- /dev/null +++ b/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.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.dev/license + */ + +import type { + AngularAppEngine as SSRAngularAppEngine, + ɵgetOrCreateAngularServerApp as getOrCreateAngularServerApp, +} from '@angular/ssr'; +import type { ServerResponse } from 'node:http'; +import type { Connect, ViteDevServer } from 'vite'; +import { + isSsrNodeRequestHandler, + isSsrRequestHandler, +} from '../../../utils/server-rendering/utils'; + +export function createAngularSsrInternalMiddleware( + server: ViteDevServer, + indexHtmlTransformer?: (content: string) => Promise, +): Connect.NextHandleFunction { + let cachedAngularServerApp: ReturnType | undefined; + + return function angularSsrMiddleware( + req: Connect.IncomingMessage, + res: ServerResponse, + next: Connect.NextFunction, + ) { + if (req.url === undefined) { + return next(); + } + + (async () => { + // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, + // which must be processed by the runtime linker, even if they are not used. + await import('@angular/compiler'); + const { writeResponseToNodeResponse, createWebRequestFromNodeRequest } = (await import( + '@angular/ssr/node' as string + )) as typeof import('@angular/ssr/node', { with: { 'resolution-mode': 'import' } }); + + const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs')) as { + ɵgetOrCreateAngularServerApp: typeof getOrCreateAngularServerApp; + }; + + const angularServerApp = ɵgetOrCreateAngularServerApp({ + allowStaticRouteRender: true, + }); + + // Only Add the transform hook only if it's a different instance. + if (cachedAngularServerApp !== angularServerApp) { + angularServerApp.hooks.on('html:transform:pre', async ({ html, url }) => { + const processedHtml = await server.transformIndexHtml(url.pathname, html); + + return indexHtmlTransformer?.(processedHtml) ?? processedHtml; + }); + + cachedAngularServerApp = angularServerApp; + } + + const webReq = new Request(createWebRequestFromNodeRequest(req), { + signal: AbortSignal.timeout(30_000), + }); + const webRes = await angularServerApp.handle(webReq); + if (!webRes) { + return next(); + } + + return writeResponseToNodeResponse(webRes, res); + })().catch(next); + }; +} + +export async function createAngularSsrExternalMiddleware( + server: ViteDevServer, + indexHtmlTransformer?: (content: string) => Promise, +): Promise { + let fallbackWarningShown = false; + let cachedAngularAppEngine: typeof SSRAngularAppEngine | undefined; + let angularSsrInternalMiddleware: + | ReturnType + | undefined; + + // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, + // which must be processed by the runtime linker, even if they are not used. + await import('@angular/compiler'); + + const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = (await import( + '@angular/ssr/node' as string + )) as typeof import('@angular/ssr/node', { with: { 'resolution-mode': 'import' } }); + + return function angularSsrExternalMiddleware( + req: Connect.IncomingMessage, + res: ServerResponse, + next: Connect.NextFunction, + ) { + (async () => { + const { reqHandler, AngularAppEngine } = (await server.ssrLoadModule('./server.mjs')) as { + reqHandler?: unknown; + AngularAppEngine: typeof SSRAngularAppEngine; + }; + + if (!isSsrNodeRequestHandler(reqHandler) && !isSsrRequestHandler(reqHandler)) { + if (!fallbackWarningShown) { + // eslint-disable-next-line no-console + console.warn( + `The 'reqHandler' export in 'server.ts' is either undefined or does not provide a recognized request handler. ` + + 'Using the internal SSR middleware instead.', + ); + + fallbackWarningShown = true; + } + + angularSsrInternalMiddleware ??= createAngularSsrInternalMiddleware( + server, + indexHtmlTransformer, + ); + + angularSsrInternalMiddleware(req, res, next); + + return; + } + + if (cachedAngularAppEngine !== AngularAppEngine) { + AngularAppEngine.ɵallowStaticRouteRender = true; + AngularAppEngine.ɵhooks.on('html:transform:pre', async ({ html, url }) => { + const processedHtml = await server.transformIndexHtml(url.pathname, html); + + return indexHtmlTransformer?.(processedHtml) ?? processedHtml; + }); + + cachedAngularAppEngine = AngularAppEngine; + } + + // Forward the request to the middleware in server.ts + if (isSsrNodeRequestHandler(reqHandler)) { + await reqHandler(req, res, next); + } else { + const webRes = await reqHandler(createWebRequestFromNodeRequest(req)); + if (!webRes) { + next(); + + return; + } + + await writeResponseToNodeResponse(webRes, res); + } + })().catch(next); + }; +} diff --git a/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts b/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts new file mode 100644 index 000000000000..be00e3437f27 --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.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.dev/license + */ + +import assert from 'node:assert'; +import { readFile } from 'node:fs/promises'; +import { dirname, join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Plugin } from 'vite'; +import { AngularMemoryOutputFiles } from '../utils'; + +interface AngularMemoryPluginOptions { + virtualProjectRoot: string; + outputFiles: AngularMemoryOutputFiles; + templateUpdates?: ReadonlyMap; + external?: string[]; + disableViteTransport?: boolean; +} + +const ANGULAR_PREFIX = '/@ng/'; +const VITE_FS_PREFIX = '/@fs/'; +const FILE_PROTOCOL = 'file:'; + +export async function createAngularMemoryPlugin( + options: AngularMemoryPluginOptions, +): Promise { + const { virtualProjectRoot, outputFiles, external } = options; + const { normalizePath } = await import('vite'); + + return { + name: 'vite:angular-memory', + // Ensures plugin hooks run before built-in Vite hooks + enforce: 'pre', + async resolveId(source, importer, { ssr }) { + if (source.startsWith(VITE_FS_PREFIX)) { + return; + } + + // For SSR with component HMR, pass through as a virtual module + if (ssr && source.startsWith(FILE_PROTOCOL) && source.includes(ANGULAR_PREFIX)) { + // Vite will resolve these these files example: + // `file:///@ng/component?c=src%2Fapp%2Fapp.component.ts%40AppComponent&t=1737017253850` + const sourcePath = fileURLToPath(source); + const sourceWithoutRoot = normalizePath('/' + relative(virtualProjectRoot, sourcePath)); + + if (sourceWithoutRoot.startsWith(ANGULAR_PREFIX)) { + const [, query] = source.split('?', 2); + + return `\0${sourceWithoutRoot}?${query}`; + } + } + + // Prevent vite from resolving an explicit external dependency (`externalDependencies` option) + if (external?.includes(source)) { + // This is still not ideal since Vite will still transform the import specifier to + // `/@id/${source}` but is currently closer to a raw external than a resolved file path. + return source; + } + + if (importer && source[0] === '.') { + const normalizedImporter = normalizePath(importer); + if (normalizedImporter.startsWith(virtualProjectRoot)) { + // Remove query if present + const [importerFile] = normalizedImporter.split('?', 1); + source = '/' + join(dirname(relative(virtualProjectRoot, importerFile)), source); + } + } + + const [file] = source.split('?', 1); + if (outputFiles.has(normalizePath(file))) { + return join(virtualProjectRoot, source); + } + }, + load(id, loadOptions) { + // For SSR component updates, return the component update module or empty if none + if (loadOptions?.ssr && id.startsWith(`\0${ANGULAR_PREFIX}`)) { + // Extract component identifier (first character is rollup virtual module null) + const requestUrl = new URL(id.slice(1), 'http://localhost'); + const componentId = requestUrl.searchParams.get('c'); + + return (componentId && options.templateUpdates?.get(encodeURIComponent(componentId))) ?? ''; + } + + const [file] = id.split('?', 1); + const relativeFile = '/' + normalizePath(relative(virtualProjectRoot, file)); + const codeContents = outputFiles.get(relativeFile)?.contents; + if (codeContents === undefined) { + if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) { + return loadViteClientCode(file, options.disableViteTransport); + } + + return undefined; + } + + const code = Buffer.from(codeContents).toString('utf-8'); + const mapContents = outputFiles.get(relativeFile + '.map')?.contents; + + return { + // Remove source map URL comments from the code if a sourcemap is present. + // Vite will inline and add an additional sourcemap URL for the sourcemap. + code: mapContents ? code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '') : code, + map: mapContents && Buffer.from(mapContents).toString('utf-8'), + }; + }, + }; +} + +/** + * Reads the resolved Vite client code from disk and updates the content to remove + * an unactionable suggestion to update the Vite configuration file to disable the + * error overlay. The Vite configuration file is not present when used in the Angular + * CLI. + * @param file The absolute path to the Vite client code. + * @returns + */ +async function loadViteClientCode(file: string, disableViteTransport = false): Promise { + const originalContents = await readFile(file, 'utf-8'); + let updatedContents = originalContents.replace( + '"You can also disable this overlay by setting ", ' + + 'h("code", { part: "config-option-name" }, "server.hmr.overlay"), ' + + '" to ", ' + + 'h("code", { part: "config-option-value" }, "false"), ' + + '" in ", ' + + 'h("code", { part: "config-file-name" }, hmrConfigName), ' + + '"."', + '', + ); + + assert(originalContents !== updatedContents, 'Failed to update Vite client error overlay text.'); + + if (disableViteTransport) { + const previousUpdatedContents = updatedContents; + + updatedContents = updatedContents.replace( + 'transport.connect(createHMRHandler(handleMessage));', + '', + ); + assert( + previousUpdatedContents !== updatedContents, + 'Failed to update Vite client WebSocket disable.', + ); + + updatedContents = updatedContents.replace('console.debug("[vite] connecting...")', ''); + } + + return updatedContents; +} diff --git a/packages/angular/build/src/tools/vite/plugins/id-prefix-plugin.ts b/packages/angular/build/src/tools/vite/plugins/id-prefix-plugin.ts new file mode 100644 index 000000000000..5e543734b863 --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/id-prefix-plugin.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.dev/license + */ + +import type { Plugin } from 'vite'; + +// NOTE: the implementation for this Vite plugin is roughly based on: +// https://github.com/MilanKovacic/vite-plugin-externalize-dependencies + +const VITE_ID_PREFIX = '@id/'; + +const escapeRegexSpecialChars = (inputString: string): string => { + return inputString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +}; + +export function createRemoveIdPrefixPlugin(externals: string[]): Plugin { + return { + name: 'angular-plugin-remove-id-prefix', + apply: 'serve', + configResolved: (resolvedConfig) => { + // don't do anything when the list of externals is empty + if (externals.length === 0) { + return; + } + + const escapedExternals = externals.map((e) => escapeRegexSpecialChars(e) + '(?:/.+)?'); + const prefixedExternalRegex = new RegExp( + `${resolvedConfig.base}${VITE_ID_PREFIX}(${escapedExternals.join('|')})`, + 'g', + ); + + // @ts-expect-error: Property 'push' does not exist on type 'readonly Plugin[]' + // Reasoning: + // since the /@id/ prefix is added by Vite's import-analysis plugin, + // we must add our actual plugin dynamically, to ensure that it will run + // AFTER the import-analysis. + resolvedConfig.plugins.push({ + name: 'angular-plugin-remove-id-prefix-transform', + transform: (code: string) => { + // don't do anything when code does not contain the Vite prefix + if (!code.includes(VITE_ID_PREFIX)) { + return code; + } + + return code.replace(prefixedExternalRegex, (_, externalName) => externalName); + }, + }); + }, + }; +} diff --git a/packages/angular/build/src/tools/vite/plugins/index.ts b/packages/angular/build/src/tools/vite/plugins/index.ts new file mode 100644 index 000000000000..6c4cdd4496e4 --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/index.ts @@ -0,0 +1,13 @@ +/** + * @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.dev/license + */ + +export { createAngularMemoryPlugin } from './angular-memory-plugin'; +export { createRemoveIdPrefixPlugin } from './id-prefix-plugin'; +export { createAngularSetupMiddlewaresPlugin, ServerSsrMode } from './setup-middlewares-plugin'; +export { createAngularSsrTransformPlugin } from './ssr-transform-plugin'; +export { createAngularServerSideSSLPlugin } from './ssr-ssl-plugin'; diff --git a/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts b/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts new file mode 100644 index 000000000000..5d20d5c705ac --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts @@ -0,0 +1,139 @@ +/** + * @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.dev/license + */ + +import type { Connect, Plugin } from 'vite'; +import { + ComponentStyleRecord, + angularHtmlFallbackMiddleware, + createAngularAssetsMiddleware, + createAngularComponentMiddleware, + createAngularHeadersMiddleware, + createAngularIndexHtmlMiddleware, + createAngularSsrExternalMiddleware, + createAngularSsrInternalMiddleware, + createChromeDevtoolsMiddleware, + patchBaseMiddleware, + patchHostValidationMiddleware, +} from '../middlewares'; +import { AngularMemoryOutputFiles, AngularOutputAssets } from '../utils'; + +export enum ServerSsrMode { + /** + * No SSR + */ + NoSsr, + + /** + * Internal server-side rendering (SSR) is handled through the built-in middleware. + * + * In this mode, the SSR process is managed internally by the dev-server's middleware. + * The server automatically renders pages on the server without requiring external + * middleware or additional configuration from the developer. + */ + InternalSsrMiddleware, + + /** + * External server-side rendering (SSR) is handled by a custom middleware defined in server.ts. + * + * This mode allows developers to define custom SSR behavior by providing a middleware in the + * `server.ts` file. It gives more flexibility for handling SSR, such as integrating with other + * frameworks or customizing the rendering pipeline. + */ + ExternalSsrMiddleware, +} + +interface AngularSetupMiddlewaresPluginOptions { + outputFiles: AngularMemoryOutputFiles; + assets: AngularOutputAssets; + extensionMiddleware?: Connect.NextHandleFunction[]; + indexHtmlTransformer?: (content: string) => Promise; + componentStyles: Map; + templateUpdates: Map; + ssrMode: ServerSsrMode; + resetComponentUpdates: () => void; + projectRoot: string; +} + +async function createEncapsulateStyle(): Promise< + (style: Uint8Array, componentId: string) => string +> { + const { encapsulateStyle } = await import('@angular/compiler'); + const decoder = new TextDecoder('utf-8'); + + return (style, componentId) => { + return encapsulateStyle(decoder.decode(style), componentId); + }; +} + +export function createAngularSetupMiddlewaresPlugin( + options: AngularSetupMiddlewaresPluginOptions, +): Plugin { + return { + name: 'vite:angular-setup-middlewares', + enforce: 'pre', + async configureServer(server) { + const { + indexHtmlTransformer, + outputFiles, + extensionMiddleware, + assets, + componentStyles, + templateUpdates, + ssrMode, + resetComponentUpdates, + } = options; + + const middlewares = server.middlewares; + + // Headers, assets and resources get handled first + middlewares.use(createAngularHeadersMiddleware(server)); + middlewares.use(createAngularComponentMiddleware(server, templateUpdates)); + middlewares.use( + createAngularAssetsMiddleware( + server, + assets, + outputFiles, + componentStyles, + await createEncapsulateStyle(), + ), + ); + + middlewares.use(createChromeDevtoolsMiddleware(server.config.cacheDir, options.projectRoot)); + + extensionMiddleware?.forEach((middleware) => middlewares.use(middleware)); + + // Returning a function, installs middleware after the main transform middleware but + // before the built-in HTML middleware + // eslint-disable-next-line @typescript-eslint/no-misused-promises + return async () => { + patchHostValidationMiddleware(server.middlewares); + + if (ssrMode === ServerSsrMode.ExternalSsrMiddleware) { + patchBaseMiddleware(server.middlewares, server.config.base); + middlewares.use(await createAngularSsrExternalMiddleware(server, indexHtmlTransformer)); + + return; + } + + if (ssrMode === ServerSsrMode.InternalSsrMiddleware) { + middlewares.use(createAngularSsrInternalMiddleware(server, indexHtmlTransformer)); + } + + middlewares.use(angularHtmlFallbackMiddleware); + middlewares.use( + createAngularIndexHtmlMiddleware( + server, + outputFiles, + resetComponentUpdates, + indexHtmlTransformer, + ), + ); + }; + }, + }; +} diff --git a/packages/angular/build/src/tools/vite/plugins/ssr-ssl-plugin.ts b/packages/angular/build/src/tools/vite/plugins/ssr-ssl-plugin.ts new file mode 100644 index 000000000000..80ddf56e739a --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/ssr-ssl-plugin.ts @@ -0,0 +1,72 @@ +/** + * @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.dev/license + */ + +import { readFile } from 'node:fs/promises'; +import { getCACertificates, rootCertificates, setDefaultCACertificates } from 'node:tls'; +import type { Plugin } from 'vite'; + +export function createAngularServerSideSSLPlugin(): Plugin { + return { + name: 'angular-ssr-ssl-plugin', + apply: 'serve', + async configureServer({ config, httpServer }) { + const { + ssr, + server: { https }, + } = config; + + if (!ssr || !https?.cert) { + return; + } + + if (httpServer && 'ALPNProtocols' in httpServer) { + // Force Vite to use HTTP/1.1 when SSR and SSL are enabled. + // This is required because the Express server used for SSR does not support HTTP/2. + // See: https://github.com/vitejs/vite/blob/46d3077f2b63771cc50230bc907c48f5773c00fb/packages/vite/src/node/http.ts#L126 + + // We directly set the `ALPNProtocols` on the HTTP server to override the default behavior. + // Passing `ALPNProtocols` in the TLS options would cause Node.js to automatically include `h2`. + // Additionally, using `ALPNCallback` is not an option as it is mutually exclusive with `ALPNProtocols`. + // See: https://github.com/nodejs/node/blob/b8b4350ed3b73d225eb9e628d69151df56eaf298/lib/internal/http2/core.js#L3351 + httpServer.ALPNProtocols = ['http/1.1']; + } + + const { cert } = https; + const additionalCerts = Array.isArray(cert) ? cert : [cert]; + + // TODO(alanagius): Remove the `if` check once we only support Node.js 22.18.0+ and 24.5.0+. + if (getCACertificates && setDefaultCACertificates) { + const currentCerts = getCACertificates('default'); + setDefaultCACertificates([...currentCerts, ...additionalCerts]); + + return; + } + + // TODO(alanagius): Remove the below and `undici` dependency once we only support Node.js 22.18.0+ and 24.5.0+. + const { getGlobalDispatcher, setGlobalDispatcher, Agent } = await import('undici'); + const originalDispatcher = getGlobalDispatcher(); + const ca = [...rootCertificates, ...additionalCerts]; + const extraNodeCerts = process.env['NODE_EXTRA_CA_CERTS']; + if (extraNodeCerts) { + ca.push(await readFile(extraNodeCerts)); + } + + setGlobalDispatcher( + new Agent({ + connect: { + ca, + }, + }), + ); + + httpServer?.on('close', () => { + setGlobalDispatcher(originalDispatcher); + }); + }, + }; +} diff --git a/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts b/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts new file mode 100644 index 000000000000..90d183acde02 --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts @@ -0,0 +1,34 @@ +/** + * @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.dev/license + */ + +import remapping, { SourceMapInput } from '@ampproject/remapping'; +import type { Plugin } from 'vite'; + +export async function createAngularSsrTransformPlugin(workspaceRoot: string): Promise { + const { normalizePath } = await import('vite'); + + return { + name: 'vite:angular-ssr-transform', + enforce: 'post', + transform(code, _id, { ssr, inMap }: { ssr?: boolean; inMap?: SourceMapInput } = {}) { + if (!ssr || !inMap) { + return null; + } + + const remappedMap = remapping([inMap], () => null); + // Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root. + remappedMap.sourceRoot = normalizePath(workspaceRoot) + '/'; + + return { + code, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + map: remappedMap as any, + }; + }, + }; +} diff --git a/packages/angular/build/src/tools/vite/utils.ts b/packages/angular/build/src/tools/vite/utils.ts new file mode 100644 index 000000000000..2f7cfba84306 --- /dev/null +++ b/packages/angular/build/src/tools/vite/utils.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.dev/license + */ + +import { lookup as lookupMimeType } from 'mrmime'; +import { builtinModules, isBuiltin } from 'node:module'; +import { extname } from 'node:path'; +import type { DepOptimizationConfig } from 'vite'; +import type { ExternalResultMetadata } from '../esbuild/bundler-execution-result'; +import { JavaScriptTransformer } from '../esbuild/javascript-transformer'; +import { getFeatureSupport } from '../esbuild/utils'; + +export type AngularMemoryOutputFiles = Map< + string, + { contents: Uint8Array; hash: string; servable: boolean } +>; + +export type AngularOutputAssets = Map; + +export function pathnameWithoutBasePath(url: string, basePath: string): string { + const parsedUrl = new URL(url, 'http://localhost'); + const pathname = decodeURIComponent(parsedUrl.pathname); + + // slice(basePath.length - 1) to retain the trailing slash + return basePath !== '/' && pathname.startsWith(basePath) + ? pathname.slice(basePath.length - 1) + : pathname; +} + +export function lookupMimeTypeFromRequest(url: string): string | undefined { + const extension = extname(url.split('?')[0]); + + if (extension === '.ico') { + return 'image/x-icon'; + } + + return extension && lookupMimeType(extension); +} + +type ViteEsBuildPlugin = NonNullable< + NonNullable['plugins'] +>[0]; + +export type EsbuildLoaderOption = Exclude< + DepOptimizationConfig['esbuildOptions'], + undefined +>['loader']; + +export function getDepOptimizationConfig({ + disabled, + exclude, + include, + target, + zoneless, + prebundleTransformer, + ssr, + loader, + thirdPartySourcemaps, + define = {}, +}: { + disabled: boolean; + exclude: string[]; + include: string[]; + target: string[]; + prebundleTransformer: JavaScriptTransformer; + ssr: boolean; + zoneless: boolean; + loader?: EsbuildLoaderOption; + thirdPartySourcemaps: boolean; + define: Record | undefined; +}): DepOptimizationConfig { + const plugins: ViteEsBuildPlugin[] = [ + { + name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${ + thirdPartySourcemaps ? '-vendor-sourcemap' : '' + }`, + setup(build) { + build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => { + return { + contents: await prebundleTransformer.transformFile(args.path), + loader: 'js', + }; + }); + }, + }, + ]; + + return { + // Exclude any explicitly defined dependencies (currently build defined externals) + exclude, + // NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined. + // Include all implict dependencies from the external packages internal option + include: disabled ? undefined : include, + noDiscovery: disabled, + // Add an esbuild plugin to run the Angular linker on dependencies + esbuildOptions: { + // Set esbuild supported targets. + target, + supported: getFeatureSupport(target, zoneless), + plugins, + loader, + define: { + ...define, + 'ngServerMode': `${ssr}`, + }, + resolveExtensions: ['.mjs', '.js', '.cjs'], + }, + }; +} + +export interface DevServerExternalResultMetadata { + implicitBrowser: string[]; + implicitServer: string[]; + explicitBrowser: string[]; + explicitServer: string[]; +} + +export function isAbsoluteUrl(url: string): boolean { + try { + new URL(url); + + return true; + } catch { + return false; + } +} + +export function updateExternalMetadata( + result: { detail?: { externalMetadata?: ExternalResultMetadata } }, + externalMetadata: DevServerExternalResultMetadata, + externalDependencies: string[] | undefined, + explicitPackagesOnly: boolean = false, +): void { + if (!result.detail?.['externalMetadata']) { + return; + } + + const { implicitBrowser, implicitServer, explicit } = result.detail['externalMetadata']; + const implicitServerFiltered = implicitServer.filter((m) => !isBuiltin(m) && !isAbsoluteUrl(m)); + const implicitBrowserFiltered = implicitBrowser.filter((m) => !isAbsoluteUrl(m)); + const explicitBrowserFiltered = explicitPackagesOnly + ? explicit.filter((m) => !isAbsoluteUrl(m)) + : explicit; + + // Empty Arrays to avoid growing unlimited with every re-build. + externalMetadata.explicitBrowser.length = 0; + externalMetadata.explicitServer.length = 0; + externalMetadata.implicitServer.length = 0; + externalMetadata.implicitBrowser.length = 0; + + const externalDeps = externalDependencies ?? []; + externalMetadata.explicitBrowser.push(...explicitBrowserFiltered, ...externalDeps); + externalMetadata.explicitServer.push( + ...explicitBrowserFiltered, + ...externalDeps, + ...builtinModules, + ); + externalMetadata.implicitServer.push(...implicitServerFiltered); + externalMetadata.implicitBrowser.push(...implicitBrowserFiltered); + + // The below needs to be sorted as Vite uses these options as part of the hashing invalidation algorithm. + // See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239 + externalMetadata.explicitBrowser.sort(); + externalMetadata.explicitServer.sort(); + externalMetadata.implicitServer.sort(); + externalMetadata.implicitBrowser.sort(); +} diff --git a/packages/angular/build/src/utils/bundle-calculator.ts b/packages/angular/build/src/utils/bundle-calculator.ts new file mode 100644 index 000000000000..3349a8a40830 --- /dev/null +++ b/packages/angular/build/src/utils/bundle-calculator.ts @@ -0,0 +1,388 @@ +/** + * @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.dev/license + */ + +import { Budget as BudgetEntry, Type as BudgetType } from '../builders/application/schema'; +import { formatSize } from './format-bytes'; + +// Re-export to avoid direct schema importing throughout code +export { type BudgetEntry, BudgetType }; + +export const BYTES_IN_KILOBYTE = 1000; + +interface Size { + size: number; + label?: string; +} + +export interface Threshold { + limit: number; + type: ThresholdType; + severity: ThresholdSeverity; +} + +enum ThresholdType { + Max = 'maximum', + Min = 'minimum', +} + +export enum ThresholdSeverity { + Warning = 'warning', + Error = 'error', +} + +export interface BudgetCalculatorResult { + severity: ThresholdSeverity; + message: string; + label?: string; +} + +export interface BudgetChunk { + files?: string[]; + names?: string[]; + initial?: boolean; +} + +export interface BudgetAsset { + name: string; + size: number; + componentStyle?: boolean; +} + +export interface BudgetStats { + chunks?: BudgetChunk[]; + assets?: BudgetAsset[]; +} + +export function* calculateThresholds(budget: BudgetEntry): IterableIterator { + if (budget.maximumWarning) { + yield { + limit: calculateBytes(budget.maximumWarning, budget.baseline, 1), + type: ThresholdType.Max, + severity: ThresholdSeverity.Warning, + }; + } + + if (budget.maximumError) { + yield { + limit: calculateBytes(budget.maximumError, budget.baseline, 1), + type: ThresholdType.Max, + severity: ThresholdSeverity.Error, + }; + } + + if (budget.minimumWarning) { + yield { + limit: calculateBytes(budget.minimumWarning, budget.baseline, -1), + type: ThresholdType.Min, + severity: ThresholdSeverity.Warning, + }; + } + + if (budget.minimumError) { + yield { + limit: calculateBytes(budget.minimumError, budget.baseline, -1), + type: ThresholdType.Min, + severity: ThresholdSeverity.Error, + }; + } + + if (budget.warning) { + yield { + limit: calculateBytes(budget.warning, budget.baseline, -1), + type: ThresholdType.Min, + severity: ThresholdSeverity.Warning, + }; + + yield { + limit: calculateBytes(budget.warning, budget.baseline, 1), + type: ThresholdType.Max, + severity: ThresholdSeverity.Warning, + }; + } + + if (budget.error) { + yield { + limit: calculateBytes(budget.error, budget.baseline, -1), + type: ThresholdType.Min, + severity: ThresholdSeverity.Error, + }; + + yield { + limit: calculateBytes(budget.error, budget.baseline, 1), + type: ThresholdType.Max, + severity: ThresholdSeverity.Error, + }; + } +} + +/** + * Calculates the sizes for bundles in the budget type provided. + */ +function calculateSizes(budget: BudgetEntry, stats: BudgetStats): Size[] { + type CalculatorTypes = { + new (budget: BudgetEntry, chunks: BudgetChunk[], assets: BudgetAsset[]): Calculator; + }; + const calculatorMap: Record = { + all: AllCalculator, + allScript: AllScriptCalculator, + any: AnyCalculator, + anyScript: AnyScriptCalculator, + anyComponentStyle: AnyComponentStyleCalculator, + bundle: BundleCalculator, + initial: InitialCalculator, + }; + + const ctor = calculatorMap[budget.type]; + const { chunks, assets } = stats; + if (!chunks) { + throw new Error('Webpack stats output did not include chunk information.'); + } + if (!assets) { + throw new Error('Webpack stats output did not include asset information.'); + } + + const calculator = new ctor(budget, chunks, assets); + + return calculator.calculate(); +} + +abstract class Calculator { + constructor( + protected budget: BudgetEntry, + protected chunks: BudgetChunk[], + protected assets: BudgetAsset[], + ) {} + + abstract calculate(): Size[]; + + /** Calculates the size of the given chunk for the provided build type. */ + protected calculateChunkSize(chunk: BudgetChunk): number { + // No differential builds, get the chunk size by summing its assets. + if (!chunk.files) { + return 0; + } + + 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: BudgetAsset): number { + return asset.size; + } +} + +/** + * A named bundle. + */ +class BundleCalculator extends Calculator { + calculate() { + const budgetName = this.budget.name; + if (!budgetName) { + return []; + } + + 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 }]; + } +} + +/** + * The sum of all initial chunks (marked as initial). + */ +class InitialCalculator extends Calculator { + calculate() { + return [ + { + label: `bundle initial`, + size: this.chunks + .filter((chunk) => chunk.initial) + .map((chunk) => this.calculateChunkSize(chunk)) + .reduce((l, r) => l + r, 0), + }, + ]; + } +} + +/** + * The sum of all the scripts portions. + */ +class AllScriptCalculator extends Calculator { + calculate() { + const size = this.assets + .filter((asset) => asset.name.endsWith('.js')) + .map((asset) => this.getAssetSize(asset)) + .reduce((total: number, size: number) => total + size, 0); + + return [{ size, label: 'total scripts' }]; + } +} + +/** + * All scripts and assets added together. + */ +class AllCalculator extends Calculator { + calculate() { + const size = this.assets + .filter((asset) => !asset.name.endsWith('.map') && !asset.componentStyle) + .map((asset) => this.getAssetSize(asset)) + .reduce((total: number, size: number) => total + size, 0); + + return [{ size, label: 'total' }]; + } +} + +/** + * Any script, individually. + */ +class AnyScriptCalculator extends Calculator { + calculate() { + return this.assets + .filter((asset) => asset.name.endsWith('.js')) + .map((asset) => ({ + size: this.getAssetSize(asset), + label: asset.name, + })); + } +} + +/** + * Any script or asset (images, css, etc). + */ +class AnyCalculator extends Calculator { + calculate() { + return this.assets + .filter((asset) => !asset.name.endsWith('.map') && !asset.componentStyle) + .map((asset) => ({ + size: this.getAssetSize(asset), + label: asset.name, + })); + } +} + +/** + * Any compoonent stylesheet + */ +class AnyComponentStyleCalculator extends Calculator { + calculate() { + return this.assets + .filter((asset) => asset.componentStyle) + .map((asset) => ({ + size: this.getAssetSize(asset), + label: asset.name, + })); + } +} + +/** + * Calculate the bytes given a string value. + */ +function calculateBytes(input: string, baseline?: string, factor: 1 | -1 = 1): number { + const matches = input.trim().match(/^(\d+(?:\.\d+)?)[ \t]*(%|[kmg]?b)?$/i); + if (!matches) { + return NaN; + } + + const baselineBytes = (baseline && calculateBytes(baseline)) || 0; + + let value = Number(matches[1]); + switch (matches[2] && matches[2].toLowerCase()) { + case '%': + value = (baselineBytes * value) / 100; + break; + case 'kb': + value *= BYTES_IN_KILOBYTE; + break; + case 'mb': + value *= BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE; + break; + case 'gb': + value *= BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE; + break; + } + + if (baselineBytes === 0) { + return value; + } + + return baselineBytes + value * factor; +} + +export function* checkBudgets( + budgets: BudgetEntry[], + stats: BudgetStats, + checkComponentStyles?: boolean, +): IterableIterator { + // Ignore AnyComponentStyle budgets as these are handled in `AnyComponentStyleBudgetChecker` unless requested + const computableBudgets = checkComponentStyles + ? budgets + : budgets.filter((budget) => budget.type !== BudgetType.AnyComponentStyle); + + for (const budget of computableBudgets) { + const sizes = calculateSizes(budget, stats); + for (const { size, label } of sizes) { + yield* checkThresholds(calculateThresholds(budget), size, label); + } + } +} + +export function* checkThresholds( + thresholds: Iterable, + size: number, + label?: string, +): IterableIterator { + for (const threshold of thresholds) { + switch (threshold.type) { + case ThresholdType.Max: { + if (size <= threshold.limit) { + continue; + } + + const sizeDifference = formatSize(size - threshold.limit); + yield { + severity: threshold.severity, + label, + message: `${label} exceeded maximum budget. Budget ${formatSize( + threshold.limit, + )} was not met by ${sizeDifference} with a total of ${formatSize(size)}.`, + }; + break; + } + case ThresholdType.Min: { + if (size >= threshold.limit) { + continue; + } + + const sizeDifference = formatSize(threshold.limit - size); + yield { + severity: threshold.severity, + 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: { + throw new Error(`Unexpected threshold type: ${ThresholdType[threshold.type]}`); + } + } + } +} diff --git a/packages/angular/build/src/utils/bundle-calculator_spec.ts b/packages/angular/build/src/utils/bundle-calculator_spec.ts new file mode 100644 index 000000000000..9bb44394496b --- /dev/null +++ b/packages/angular/build/src/utils/bundle-calculator_spec.ts @@ -0,0 +1,379 @@ +/** + * @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.dev/license + */ + +import { + BYTES_IN_KILOBYTE, + BudgetEntry, + BudgetType, + ThresholdSeverity, + checkBudgets, +} from './bundle-calculator'; + +describe('bundle-calculator', () => { + describe('checkBudgets()', () => { + it('yields maximum budgets exceeded', () => { + const budgets: BudgetEntry[] = [ + { + type: BudgetType.Any, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [], + assets: [ + { + name: 'foo.js', + size: 1.5 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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: BudgetEntry[] = [ + { + type: BudgetType.Any, + minimumError: '1kb', + }, + ]; + const stats = { + chunks: [], + assets: [ + { + name: 'foo.js', + size: 1.5 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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: BudgetEntry[] = [ + { + type: BudgetType.Bundle, + name: 'foo', + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + names: ['foo'], + files: ['foo.js', 'bar.js'], + }, + ], + assets: [ + { + name: 'foo.js', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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 initial budget', () => { + const budgets: BudgetEntry[] = [ + { + type: BudgetType.Initial, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['foo'], + files: ['foo.js', 'bar.js'], + }, + ], + assets: [ + { + name: 'foo.js', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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 total scripts budget', () => { + const budgets: BudgetEntry[] = [ + { + type: BudgetType.AllScript, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['foo'], + files: ['foo.js', 'bar.js'], + }, + ], + assets: [ + { + name: 'foo.js', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + { + name: 'baz.css', + size: 1.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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: BudgetEntry[] = [ + { + type: BudgetType.All, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['foo'], + files: ['foo.js', 'bar.css'], + }, + ], + assets: [ + { + name: 'foo.js', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.css', + size: 0.75 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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: BudgetEntry[] = [ + { + type: BudgetType.AnyComponentStyle, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['foo'], + files: ['foo.css', 'bar.js'], + }, + ], + assets: [ + { + name: 'foo.css', + size: 1.5 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + const failures = Array.from(checkBudgets(budgets, stats)); + + expect(failures.length).toBe(0); + }); + + it('yields exceeded individual script budget', () => { + const budgets: BudgetEntry[] = [ + { + type: BudgetType.AnyScript, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['foo'], + files: ['foo.js', 'bar.js'], + }, + ], + assets: [ + { + name: 'foo.js', + size: 1.5 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.js', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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: BudgetEntry[] = [ + { + type: BudgetType.Any, + maximumError: '1kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['foo'], + files: ['foo.ext', 'bar.ext'], + }, + ], + assets: [ + { + name: 'foo.ext', + size: 1.5 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.ext', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + 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 exceed the individual file budget limit', () => { + const budgets: BudgetEntry[] = [ + { + type: BudgetType.Bundle, + maximumError: '1000kb', + }, + ]; + const stats = { + chunks: [ + { + id: 0, + initial: true, + names: ['main'], + files: ['main.ext', 'bar.ext'], + }, + ], + assets: [ + { + name: 'main.ext', + size: 1 * BYTES_IN_KILOBYTE, + }, + { + name: 'bar.ext', + size: 0.5 * BYTES_IN_KILOBYTE, + }, + ], + }; + + const failures = Array.from(checkBudgets(budgets, stats)); + + expect(failures).toHaveSize(0); + }); + }); +}); diff --git a/packages/angular/build/src/utils/check-port.ts b/packages/angular/build/src/utils/check-port.ts new file mode 100644 index 000000000000..d7c04f0b9f72 --- /dev/null +++ b/packages/angular/build/src/utils/check-port.ts @@ -0,0 +1,67 @@ +/** + * @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.dev/license + */ + +import assert from 'node:assert'; +import { createServer } from 'node:net'; +import { isTTY } from './tty'; + +function createInUseError(port: number): Error { + return new Error(`Port ${port} is already in use. Use '--port' to specify a different port.`); +} + +export async function checkPort(port: number, host: string): Promise { + // Disabled due to Vite not handling port 0 and instead always using the default value (5173) + // TODO: Enable this again once Vite is fixed + // if (port === 0) { + // return 0; + // } + + return new Promise((resolve, reject) => { + const server = createServer(); + + server + .once('error', (err: NodeJS.ErrnoException) => { + if (err.code !== 'EADDRINUSE') { + reject(err); + + return; + } + + if (!isTTY()) { + reject(createInUseError(port)); + + return; + } + + import('@inquirer/confirm') + .then(({ default: confirm }) => + confirm({ + message: `Port ${port} is already in use.\nWould you like to use a different port?`, + default: true, + theme: { prefix: '' }, + }), + ) + .then( + (answer) => (answer ? resolve(checkPort(0, host)) : reject(createInUseError(port))), + () => reject(createInUseError(port)), + ); + }) + .once('listening', () => { + // Get the actual address from the listening server instance + const address = server.address(); + assert( + address && typeof address !== 'string', + 'Port check server address should always be an object.', + ); + + server.close(); + resolve(address.port); + }) + .listen(port, host); + }); +} diff --git a/packages/angular/build/src/utils/color.ts b/packages/angular/build/src/utils/color.ts new file mode 100644 index 000000000000..3915d99ce248 --- /dev/null +++ b/packages/angular/build/src/utils/color.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.dev/license + */ + +import { WriteStream } from 'node:tty'; + +export { color as colors, figures } from 'listr2'; + +export function supportColor(stream: NodeJS.WritableStream = process.stdout): boolean { + if (stream instanceof WriteStream) { + return stream.hasColors(); + } + + try { + // The hasColors function does not rely on any instance state and should ideally be static + return WriteStream.prototype.hasColors(); + } catch { + return process.env['FORCE_COLOR'] !== undefined && process.env['FORCE_COLOR'] !== '0'; + } +} diff --git a/packages/angular/build/src/utils/delete-output-dir.ts b/packages/angular/build/src/utils/delete-output-dir.ts new file mode 100644 index 000000000000..45084760793d --- /dev/null +++ b/packages/angular/build/src/utils/delete-output-dir.ts @@ -0,0 +1,52 @@ +/** + * @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.dev/license + */ + +import { readdir, rm } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; + +/** + * Delete an output directory, but error out if it's the root of the project. + */ +export async function deleteOutputDir( + root: string, + outputPath: string, + emptyOnlyDirectories?: string[], +): Promise { + const resolvedOutputPath = resolve(root, outputPath); + if (resolvedOutputPath === root) { + throw new Error('Output path MUST not be project root directory!'); + } + + const directoriesToEmpty = emptyOnlyDirectories + ? new Set(emptyOnlyDirectories.map((directory) => join(resolvedOutputPath, directory))) + : undefined; + + // Avoid removing the actual directory to avoid errors in cases where the output + // directory is mounted or symlinked. Instead the contents are removed. + let entries; + try { + entries = await readdir(resolvedOutputPath); + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { + return; + } + throw error; + } + + for (const entry of entries) { + const fullEntry = join(resolvedOutputPath, entry); + + // Leave requested directories. This allows symlinks to continue to function. + if (directoriesToEmpty?.has(fullEntry)) { + await deleteOutputDir(resolvedOutputPath, fullEntry); + continue; + } + + await rm(fullEntry, { force: true, recursive: true, maxRetries: 3 }); + } +} diff --git a/packages/angular/build/src/utils/environment-options.ts b/packages/angular/build/src/utils/environment-options.ts new file mode 100644 index 000000000000..80f71d56c119 --- /dev/null +++ b/packages/angular/build/src/utils/environment-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.dev/license + */ + +import { availableParallelism } from 'node:os'; + +/** A set of strings that are considered "truthy" when parsing environment variables. */ +const TRUTHY_VALUES = new Set(['1', 'true']); + +/** A set of strings that are considered "falsy" when parsing environment variables. */ +const FALSY_VALUES = new Set(['0', 'false']); + +/** + * Checks if an environment variable is present and has a non-empty value. + * @param variable The environment variable to check. + * @returns `true` if the variable is a non-empty string. + */ +function isPresent(variable: string | undefined): variable is string { + return typeof variable === 'string' && variable !== ''; +} + +/** + * Parses an environment variable into a boolean or undefined. + * @returns `true` if the variable is truthy ('1', 'true'). + * @returns `false` if the variable is falsy ('0', 'false'). + * @returns `undefined` if the variable is not present or has an unknown value. + */ +function parseTristate(variable: string | undefined): boolean | undefined { + if (!isPresent(variable)) { + return undefined; + } + + const value = variable.toLowerCase(); + if (TRUTHY_VALUES.has(value)) { + return true; + } + if (FALSY_VALUES.has(value)) { + return false; + } + + // TODO: Consider whether a warning is useful in this case of a malformed value + return undefined; +} + +// Optimization and mangling +const debugOptimizeVariable = process.env['NG_BUILD_DEBUG_OPTIMIZE']; +const debugOptimize = (() => { + if (!isPresent(debugOptimizeVariable) || parseTristate(debugOptimizeVariable) === false) { + return { + mangle: true, + minify: true, + beautify: false, + }; + } + + const debugValue = { + mangle: false, + minify: false, + beautify: true, + }; + + if (parseTristate(debugOptimizeVariable) === true) { + return debugValue; + } + + for (const part of debugOptimizeVariable.split(',')) { + switch (part.trim().toLowerCase()) { + case 'mangle': + debugValue.mangle = true; + break; + case 'minify': + debugValue.minify = true; + break; + case 'beautify': + debugValue.beautify = true; + break; + } + } + + return debugValue; +})(); + +/** + * Allows disabling of code mangling when the `NG_BUILD_MANGLE` environment variable is set to `0` or `false`. + * This is useful for debugging build output. + */ +export const allowMangle = parseTristate(process.env['NG_BUILD_MANGLE']) ?? debugOptimize.mangle; + +/** + * Allows beautification of build output when the `NG_BUILD_DEBUG_OPTIMIZE` environment variable is enabled. + * This is useful for debugging build output. + */ +export const shouldBeautify = debugOptimize.beautify; + +/** + * Allows disabling of code minification when the `NG_BUILD_DEBUG_OPTIMIZE` environment variable is enabled. + * This is useful for debugging build output. + */ +export const allowMinify = debugOptimize.minify; + +/** + * 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']; + +/** + * The maximum number of workers to use for parallel processing. + * This can be controlled by the `NG_BUILD_MAX_WORKERS` environment variable. + */ +export const maxWorkers = isPresent(maxWorkersVariable) + ? +maxWorkersVariable + : Math.min(4, Math.max(availableParallelism() - 1, 1)); + +/** + * When `NG_BUILD_PARALLEL_TS` is set to `0` or `false`, parallel TypeScript compilation is disabled. + */ +export const useParallelTs = parseTristate(process.env['NG_BUILD_PARALLEL_TS']) !== false; + +/** + * When `NG_BUILD_DEBUG_PERF` is enabled, performance debugging information is printed. + */ +export const debugPerformance = parseTristate(process.env['NG_BUILD_DEBUG_PERF']) === true; + +/** + * When `NG_BUILD_WATCH_ROOT` is enabled, the build will watch the root directory for changes. + */ +export const shouldWatchRoot = parseTristate(process.env['NG_BUILD_WATCH_ROOT']) === true; + +/** + * When `NG_BUILD_TYPE_CHECK` is set to `0` or `false`, type checking is disabled. + */ +export const useTypeChecking = parseTristate(process.env['NG_BUILD_TYPE_CHECK']) !== false; + +/** + * When `NG_BUILD_LOGS_JSON` is enabled, build logs will be output in JSON format. + */ +export const useJSONBuildLogs = parseTristate(process.env['NG_BUILD_LOGS_JSON']) === true; + +/** + * When `NG_BUILD_OPTIMIZE_CHUNKS` is enabled, the build will optimize chunks. + */ +export const shouldOptimizeChunks = parseTristate(process.env['NG_BUILD_OPTIMIZE_CHUNKS']) === true; + +/** + * When `NG_HMR_CSTYLES` is enabled, component styles will be hot-reloaded. + */ +export const useComponentStyleHmr = parseTristate(process.env['NG_HMR_CSTYLES']) === true; + +/** + * When `NG_HMR_TEMPLATES` is set to `0` or `false`, component templates will not be hot-reloaded. + */ +export const useComponentTemplateHmr = parseTristate(process.env['NG_HMR_TEMPLATES']) !== false; + +/** + * When `NG_BUILD_PARTIAL_SSR` is enabled, a partial server-side rendering build will be performed. + */ +export const usePartialSsrBuild = parseTristate(process.env['NG_BUILD_PARTIAL_SSR']) === true; + +const bazelBinDirectory = process.env['BAZEL_BINDIR']; +const bazelExecRoot = process.env['JS_BINARY__EXECROOT']; + +export const bazelEsbuildPluginPath = + bazelBinDirectory && bazelExecRoot + ? process.env['NG_INTERNAL_ESBUILD_PLUGINS_DO_NOT_USE'] + : undefined; diff --git a/packages/angular/build/src/utils/error.ts b/packages/angular/build/src/utils/error.ts new file mode 100644 index 000000000000..0ca77c331d2d --- /dev/null +++ b/packages/angular/build/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.dev/license + */ + +import assert from 'node: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/build/src/utils/format-bytes.ts b/packages/angular/build/src/utils/format-bytes.ts new file mode 100644 index 000000000000..5c9ecee6875a --- /dev/null +++ b/packages/angular/build/src/utils/format-bytes.ts @@ -0,0 +1,21 @@ +/** + * @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.dev/license + */ + +export function formatSize(size: number): string { + if (size <= 0) { + return '0 bytes'; + } + + const abbreviations = ['bytes', 'kB', 'MB', 'GB']; + const index = Math.floor(Math.log(size) / Math.log(1000)); + const roundedSize = size / Math.pow(1000, index); + // bytes don't have a fraction + const fractionDigits = index === 0 ? 0 : 2; + + return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`; +} diff --git a/packages/angular/build/src/utils/format-bytes_spec.ts b/packages/angular/build/src/utils/format-bytes_spec.ts new file mode 100644 index 000000000000..63cb3f761ecf --- /dev/null +++ b/packages/angular/build/src/utils/format-bytes_spec.ts @@ -0,0 +1,27 @@ +/** + * @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.dev/license + */ + +import { formatSize } from './format-bytes'; + +describe('formatSize', () => { + it('1000 bytes to be 1kB', () => { + expect(formatSize(1000)).toBe('1.00 kB'); + }); + + it('1_000_000 bytes to be 1MB', () => { + expect(formatSize(1_000_000)).toBe('1.00 MB'); + }); + + it('1_500_000 bytes to be 1.5MB', () => { + expect(formatSize(1_500_000)).toBe('1.50 MB'); + }); + + it('1_000_000_000 bytes to be 1GB', () => { + expect(formatSize(1_000_000_000)).toBe('1.00 GB'); + }); +}); diff --git a/packages/angular/build/src/utils/i18n-options.ts b/packages/angular/build/src/utils/i18n-options.ts new file mode 100644 index 000000000000..822683bef03d --- /dev/null +++ b/packages/angular/build/src/utils/i18n-options.ts @@ -0,0 +1,293 @@ +/** + * @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.dev/license + */ + +import path from 'node:path'; +import type { TranslationLoader } from './load-translations'; + +export interface LocaleDescription { + files: { + path: string; + integrity?: string; + format?: string; + }[]; + translation?: Record; + dataPath?: string; + baseHref?: string; + subPath: string; +} + +export interface I18nOptions { + inlineLocales: Set; + sourceLocale: string; + locales: Record; + flatOutput?: boolean; + readonly shouldInline: boolean; + hasDefinedSourceLocale?: boolean; +} + +function normalizeTranslationFileOption( + option: unknown, + locale: string, + expectObjectInError: boolean, +): string[] { + if (typeof option === 'string') { + return [option]; + } + + if (Array.isArray(option) && option.every((element) => typeof element === 'string')) { + return option; + } + + let errorMessage = `Project i18n locales translation field value for '${locale}' is malformed. `; + if (expectObjectInError) { + errorMessage += 'Expected a string, array of strings, or object.'; + } else { + errorMessage += 'Expected a string or array of strings.'; + } + + throw new Error(errorMessage); +} + +function ensureObject(value: unknown, name: string): asserts value is Record { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + throw new Error(`Project field '${name}' is malformed. Expected an object.`); + } +} + +function ensureString(value: unknown, name: string): asserts value is string { + if (typeof value !== 'string') { + throw new Error(`Project field '${name}' is malformed. Expected a string.`); + } +} + +function ensureValidSubPath(value: unknown, name: string): asserts value is string { + ensureString(value, name); + + if (!/^[\w-]*$/.test(value)) { + throw new Error( + `Project field '${name}' is invalid. It can only contain letters, numbers, hyphens, and underscores.`, + ); + } +} +export function createI18nOptions( + projectMetadata: { i18n?: unknown }, + inline?: boolean | string[], + logger?: { + warn(message: string): void; + }, + ssrEnabled?: boolean, +): I18nOptions { + const { i18n: metadata = {} } = projectMetadata; + + ensureObject(metadata, 'i18n'); + + const i18n: I18nOptions = { + inlineLocales: new Set(), + // en-US is the default locale added to Angular applications (https://angular.dev/guide/i18n/format-data-locale) + sourceLocale: 'en-US', + locales: {}, + get shouldInline() { + return this.inlineLocales.size > 0; + }, + }; + + let rawSourceLocale: string | undefined; + let rawSourceLocaleBaseHref: string | undefined; + let rawsubPath: string | undefined; + if (typeof metadata.sourceLocale === 'string') { + rawSourceLocale = metadata.sourceLocale; + } else if (metadata.sourceLocale !== undefined) { + ensureObject(metadata.sourceLocale, 'i18n.sourceLocale'); + + if (metadata.sourceLocale.code !== undefined) { + ensureString(metadata.sourceLocale.code, 'i18n.sourceLocale.code'); + rawSourceLocale = metadata.sourceLocale.code; + } + + if (metadata.sourceLocale.baseHref !== undefined) { + ensureString(metadata.sourceLocale.baseHref, 'i18n.sourceLocale.baseHref'); + if (ssrEnabled) { + logger?.warn( + `'baseHref' in 'i18n.sourceLocale' may lead to undefined behavior when used with SSR. ` + + `Consider using 'subPath' instead.\n\n` + + `Note: 'subPath' specifies the URL segment for the locale, serving as both the HTML base HREF ` + + `and the output directory name.\nBy default, if not explicitly set, 'subPath' defaults to the locale code.`, + ); + } + + rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref; + } + + if (metadata.sourceLocale.subPath !== undefined) { + ensureValidSubPath(metadata.sourceLocale.subPath, 'i18n.sourceLocale.subPath'); + rawsubPath = metadata.sourceLocale.subPath; + } + + if (rawsubPath !== undefined && rawSourceLocaleBaseHref !== undefined) { + throw new Error( + `'i18n.sourceLocale.subPath' and 'i18n.sourceLocale.baseHref' cannot be used together.`, + ); + } + } + + if (rawSourceLocale !== undefined) { + i18n.sourceLocale = rawSourceLocale; + i18n.hasDefinedSourceLocale = true; + } + + i18n.locales[i18n.sourceLocale] = { + files: [], + baseHref: rawSourceLocaleBaseHref, + subPath: rawsubPath ?? i18n.sourceLocale, + }; + + if (metadata.locales !== undefined) { + ensureObject(metadata.locales, 'i18n locales'); + + for (const [locale, options] of Object.entries(metadata.locales)) { + let translationFiles: string[] | undefined; + let baseHref: string | undefined; + let subPath: string | undefined; + + if (options && typeof options === 'object' && 'translation' in options) { + translationFiles = normalizeTranslationFileOption(options.translation, locale, false); + + if ('baseHref' in options) { + ensureString(options.baseHref, `i18n.locales.${locale}.baseHref`); + + if (ssrEnabled) { + logger?.warn( + `'baseHref' in 'i18n.locales.${locale}' may lead to undefined behavior when used with SSR. ` + + `Consider using 'subPath' instead.\n\n` + + `Note: 'subPath' specifies the URL segment for the locale, serving as both the HTML base HREF ` + + `and the output directory name.\nBy default, if not explicitly set, 'subPath' defaults to the locale code.`, + ); + } + baseHref = options.baseHref; + } + + if ('subPath' in options) { + ensureValidSubPath(options.subPath, `i18n.locales.${locale}.subPath`); + subPath = options.subPath; + } + + if (subPath !== undefined && baseHref !== undefined) { + throw new Error( + `'i18n.locales.${locale}.subPath' and 'i18n.locales.${locale}.baseHref' cannot be used together.`, + ); + } + } else { + translationFiles = normalizeTranslationFileOption(options, locale, true); + } + + if (locale === i18n.sourceLocale) { + throw new Error( + `An i18n locale ('${locale}') cannot both be a source locale and provide a translation.`, + ); + } + + i18n.locales[locale] = { + files: translationFiles.map((file) => ({ path: file })), + baseHref, + subPath: subPath ?? locale, + }; + } + } + + if (inline === true) { + i18n.inlineLocales.add(i18n.sourceLocale); + 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) { + throw new Error(`Requested locale '${locale}' is not defined for the project.`); + } + + i18n.inlineLocales.add(locale); + } + } + + // Check that subPaths are unique only the locales that we are inlining. + const localesData = Object.entries(i18n.locales).filter(([locale]) => + i18n.inlineLocales.has(locale), + ); + + for (let i = 0; i < localesData.length; i++) { + const [localeA, { subPath: subPathA }] = localesData[i]; + + for (let j = i + 1; j < localesData.length; j++) { + const [localeB, { subPath: subPathB }] = localesData[j]; + + if (subPathA === subPathB) { + throw new Error( + `Invalid i18n configuration: Locales '${localeA}' and '${localeB}' cannot have the same subPath: '${subPathB}'.`, + ); + } + } + } + + return i18n; +} + +export function loadTranslations( + locale: string, + desc: LocaleDescription, + workspaceRoot: string, + loader: TranslationLoader, + logger: { warn: (message: string) => void; error: (message: string) => void }, + usedFormats?: Set, + duplicateTranslation?: 'ignore' | 'error' | 'warning', +) { + 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 (loadResult.locale !== undefined && loadResult.locale !== locale) { + logger.warn( + `WARNING [${file.path}]: File target locale ('${loadResult.locale}') does not match configured locale ('${locale}')`, + ); + } + + 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 'ignore': + break; + case 'error': + logger.error(`ERROR ${duplicateTranslationMessage}`); + break; + case 'warning': + default: + logger.warn(`WARNING ${duplicateTranslationMessage}`); + break; + } + } + translations[id] = message; + } + } else { + // First or only translation file + translations = loadResult.translations; + } + } + desc.translation = translations; +} diff --git a/packages/angular/build/src/utils/index-file/add-event-dispatch-contract.ts b/packages/angular/build/src/utils/index-file/add-event-dispatch-contract.ts new file mode 100644 index 000000000000..749a489f8a4a --- /dev/null +++ b/packages/angular/build/src/utils/index-file/add-event-dispatch-contract.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.dev/license + */ + +import { readFile } from 'node:fs/promises'; +import { htmlRewritingStream } from './html-rewriting-stream'; + +let jsActionContractScript: string; + +export async function addEventDispatchContract(html: string): Promise { + const { rewriter, transformedContent } = await htmlRewritingStream(html); + + jsActionContractScript ??= + ''; + + rewriter.on('startTag', (tag) => { + rewriter.emitStartTag(tag); + + if (tag.tagName === 'body') { + rewriter.emitRaw(jsActionContractScript); + } + }); + + return transformedContent(); +} diff --git a/packages/angular/build/src/utils/index-file/add-event-dispatch-contract_spec.ts b/packages/angular/build/src/utils/index-file/add-event-dispatch-contract_spec.ts new file mode 100644 index 000000000000..6c0747730c29 --- /dev/null +++ b/packages/angular/build/src/utils/index-file/add-event-dispatch-contract_spec.ts @@ -0,0 +1,26 @@ +/** + * @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.dev/license + */ + +import { addEventDispatchContract } from './add-event-dispatch-contract'; + +describe('addEventDispatchContract', () => { + it('should inline event dispatcher script', async () => { + const result = await addEventDispatchContract(` + + + +

Hello World!

+ + + `); + + expect(result).toMatch( + /\s*`); + } + + let headerLinkTags: string[] = []; + let bodyLinkTags: string[] = []; + for (const src of stylesheets) { + const attrs = [`rel="stylesheet"`, `href="${generateUrl(src, deployUrl)}"`]; + + if (crossOrigin !== 'none') { + attrs.push(`crossorigin="${crossOrigin}"`); + } + + if (sri) { + const content = await loadOutputFile(src); + attrs.push(generateSriAttributes(content)); + } + + headerLinkTags.push(``); + } + + if (params.hints?.length) { + for (const hint of params.hints) { + const attrs = [`rel="${hint.mode}"`, `href="${generateUrl(hint.url, deployUrl)}"`]; + + if (hint.mode !== 'modulepreload' && crossOrigin !== 'none') { + // Value is considered anonymous by the browser when not present or empty + attrs.push(crossOrigin === 'anonymous' ? 'crossorigin' : `crossorigin="${crossOrigin}"`); + } + + if (hint.mode === 'preload' || hint.mode === 'prefetch') { + switch (extname(hint.url)) { + case '.js': + attrs.push('as="script"'); + break; + case '.css': + attrs.push('as="style"'); + break; + default: + if (hint.as) { + attrs.push(`as="${hint.as}"`); + } + break; + } + } + + if ( + sri && + (hint.mode === 'preload' || hint.mode === 'prefetch' || hint.mode === 'modulepreload') + ) { + const content = await loadOutputFile(hint.url); + attrs.push(generateSriAttributes(content)); + } + + const tag = ``; + if (hint.mode === 'modulepreload') { + // Module preloads should be placed by the inserted script elements in the body since + // they are only useful in combination with the scripts. + bodyLinkTags.push(tag); + } else { + headerLinkTags.push(tag); + } + } + } + + const dir = lang ? await getLanguageDirection(lang, warnings) : undefined; + const { rewriter, transformedContent } = await htmlRewritingStream(html); + const baseTagExists = html.includes('(); + + rewriter + .on('startTag', (tag, rawTagHtml) => { + 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 + if (!baseTagExists && isString(baseHref)) { + rewriter.emitStartTag(tag); + rewriter.emitRaw(``); + + return; + } + break; + case 'base': + // Adjust base href if specified + if (isString(baseHref)) { + updateAttribute(tag, 'href', baseHref); + } + break; + case 'link': + if (readAttribute(tag, 'rel') === 'preconnect') { + const href = readAttribute(tag, 'href'); + if (href) { + foundPreconnects.add(href); + } + } + break; + default: + if (tag.selfClosing && !VALID_SELF_CLOSING_TAGS.has(tag.tagName)) { + errors.push(`Invalid self-closing element in index HTML file: '${rawTagHtml}'.`); + + return; + } + } + + rewriter.emitStartTag(tag); + }) + .on('endTag', (tag) => { + switch (tag.tagName) { + case 'head': + for (const linkTag of headerLinkTags) { + rewriter.emitRaw(linkTag); + } + if (imageDomains) { + for (const imageDomain of imageDomains) { + if (!foundPreconnects.has(imageDomain)) { + rewriter.emitRaw(``); + } + } + } + headerLinkTags = []; + break; + case 'body': + for (const linkTag of bodyLinkTags) { + rewriter.emitRaw(linkTag); + } + bodyLinkTags = []; + + // Add script tags + for (const scriptTag of scriptTags) { + rewriter.emitRaw(scriptTag); + } + + scriptTags = []; + break; + } + + rewriter.emitEndTag(tag); + }); + + const content = await transformedContent(); + + return { + content: + headerLinkTags.length || scriptTags.length + ? // In case no body/head tags are not present (dotnet partial templates) + headerLinkTags.join('') + scriptTags.join('') + content + : content, + warnings, + errors, + }; +} + +function generateSriAttributes(content: string): string { + const algo = 'sha384'; + const hash = createHash(algo).update(content, 'utf8').digest('base64'); + + return `integrity="${algo}-${hash}"`; +} + +function generateUrl(value: string, deployUrl: string | undefined): string { + if (!deployUrl) { + return value; + } + + // Skip if root-relative, absolute or protocol relative url + if (/^((?:\w+:)?\/\/|data:|chrome:|\/)/.test(value)) { + return value; + } + + return `${deployUrl}${value}`; +} + +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) { + tag.attrs.push(newValue); + } else { + tag.attrs[index] = newValue; + } +} + +function readAttribute( + tag: { attrs: { name: string; value: string }[] }, + name: string, +): string | undefined { + const targetAttr = tag.attrs.find((attr) => attr.name === name); + + return targetAttr ? targetAttr.value : undefined; +} + +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 import(`@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/build/src/utils/index-file/augment-index-html_spec.ts b/packages/angular/build/src/utils/index-file/augment-index-html_spec.ts new file mode 100644 index 000000000000..55adf8d88f0b --- /dev/null +++ b/packages/angular/build/src/utils/index-file/augment-index-html_spec.ts @@ -0,0 +1,576 @@ +/** + * @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.dev/license + */ + +import { AugmentIndexHtmlOptions, augmentIndexHtml } from './augment-index-html'; + +describe('augment-index-html', () => { + const indexGeneratorOptions: AugmentIndexHtmlOptions = { + html: '', + baseHref: '/', + sri: false, + files: [], + loadOutputFile: async (_fileName: string) => '', + entrypoints: [ + ['scripts', false], + ['polyfills', true], + ['main', true], + ['styles', false], + ], + }; + + const oneLineHtml = (html: TemplateStringsArray) => + `${html}`.replace(/(>\s+)/g, '>').replace(/\s+ { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + files: [ + { file: 'styles.css', extension: '.css', name: 'styles' }, + { file: 'runtime.js', extension: '.js', name: 'main' }, + { file: 'main.js', extension: '.js', name: 'main' }, + { file: 'runtime.js', extension: '.js', name: 'polyfills' }, + { file: 'polyfills.js', extension: '.js', name: 'polyfills' }, + ], + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + + + + + `); + }); + + it('should replace base href value', async () => { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + html: '', + baseHref: '/Apps/', + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + `); + }); + + it('should add lang and dir LTR attribute for French (fr)', async () => { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + lang: 'fr', + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + + `); + }); + + it('should add lang and dir RTL attribute for Pashto (ps)', async () => { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + lang: 'ps', + }); + + 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: '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', + }); + + expect(warnings).toEqual([ + `Locale data for 'xx-XX' cannot be found. 'dir' attribute will not be set for this locale.`, + ]); + expect(content).toEqual(oneLineHtml` + + + + + + + + `); + }); + + it(`should add script and link tags even when body and head element doesn't exist`, async () => { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + html: ``, + files: [ + { file: 'styles.css', extension: '.css', name: 'styles' }, + { file: 'runtime.js', extension: '.js', name: 'main' }, + { file: 'main.js', extension: '.js', name: 'main' }, + { file: 'runtime.js', extension: '.js', name: 'polyfills' }, + { file: 'polyfills.js', extension: '.js', name: 'polyfills' }, + ], + }); + + expect(content).toEqual(oneLineHtml` + + + + + + `); + }); + + it(`should add preconnect and dns-prefetch hints when provided with cross origin`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + hints: [ + { mode: 'preconnect', url: 'http://example.com' }, + { mode: 'dns-prefetch', url: 'http://example.com' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add preconnect and dns-prefetch hints when provided with "use-credentials" cross origin`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + crossOrigin: 'use-credentials', + hints: [ + { mode: 'preconnect', url: 'http://example.com' }, + { mode: 'dns-prefetch', url: 'http://example.com' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add preconnect and dns-prefetch hints when provided with "anonymous" cross origin`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + crossOrigin: 'anonymous', + hints: [ + { mode: 'preconnect', url: 'http://example.com' }, + { mode: 'dns-prefetch', url: 'http://example.com' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add preconnect and dns-prefetch hints when provided with "none" cross origin`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + crossOrigin: 'none', + hints: [ + { mode: 'preconnect', url: 'http://example.com' }, + { mode: 'dns-prefetch', url: 'http://example.com' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add preconnect and dns-prefetch hints when provided with no cross origin`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + hints: [ + { mode: 'preconnect', url: 'http://example.com' }, + { mode: 'dns-prefetch', url: 'http://example.com' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add modulepreload hint when provided`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + hints: [ + { mode: 'modulepreload', url: 'x.js' }, + { mode: 'modulepreload', url: 'y/z.js' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add modulepreload hint with no crossorigin attribute when provided with cross origin set`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + crossOrigin: 'anonymous', + hints: [ + { mode: 'modulepreload', url: 'x.js' }, + { mode: 'modulepreload', url: 'y/z.js' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add prefetch/preload hints with as=script when specified with a JS url`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + hints: [ + { mode: 'prefetch', url: 'x.js' }, + { mode: 'preload', url: 'y/z.js' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add prefetch/preload hints with as=style when specified with a CSS url`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + hints: [ + { mode: 'prefetch', url: 'x.css' }, + { mode: 'preload', url: 'y/z.css' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should add prefetch/preload hints with as=style when specified with a URL and an 'as' option`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + hints: [ + { mode: 'prefetch', url: 'https://example.com/x?a=1', as: 'style' }, + { mode: 'preload', url: 'http://example.com/y?b=2', as: 'style' }, + ], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it(`should not add deploy URL to hints with an absolute URL`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + deployUrl: 'https://localhost/', + hints: [{ mode: 'preload', url: 'http://example.com/y?b=2' }], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + `); + }); + + it(`should not add deploy URL to hints with a root-relative URL`, async () => { + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + deployUrl: 'https://example.com/', + hints: [{ mode: 'preload', url: '/y?b=2' }], + }); + + expect(warnings).toHaveSize(0); + expect(content).toEqual(oneLineHtml` + + + + + + + + + `); + }); + + it('should add `.mjs` script tags', async () => { + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + files: [{ file: 'main.mjs', extension: '.mjs', name: 'main' }], + entrypoints: [['main', true /* isModule */]], + }); + + expect(content).toContain(''); + }); + + it('should reject non-module `.mjs` scripts', async () => { + const options: AugmentIndexHtmlOptions = { + ...indexGeneratorOptions, + files: [{ file: 'main.mjs', extension: '.mjs', name: 'main' }], + entrypoints: [['main', false /* isModule */]], + }; + + await expectAsync(augmentIndexHtml(options)).toBeRejectedWithError( + '`.mjs` files *must* set `isModule` to `true`.', + ); + }); + + it('should add image domain preload tags', async () => { + const imageDomains = ['https://www.example.com', 'https://www.example2.com']; + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + imageDomains, + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it('should add no image preconnects if provided empty domain list', async () => { + const imageDomains: Array = []; + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + imageDomains, + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + + `); + }); + + it('should not add duplicate preconnects', async () => { + const imageDomains = ['https://www.example1.com', 'https://www.example2.com']; + const { content, warnings } = await augmentIndexHtml({ + ...indexGeneratorOptions, + html: '', + imageDomains, + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + + + + `); + }); + + it('should add image preconnects if it encounters preconnect elements for other resources', async () => { + const imageDomains = ['https://www.example2.com', 'https://www.example3.com']; + const { content } = await augmentIndexHtml({ + ...indexGeneratorOptions, + html: '', + imageDomains, + }); + + expect(content).toEqual(oneLineHtml` + + + + + + + + + + + `); + }); + + describe('self-closing tags', () => { + it('should return an error when used on a not supported element', async () => { + const { errors } = await augmentIndexHtml({ + ...indexGeneratorOptions, + html: ` + + + + + ' + `, + }); + + expect(errors.length).toEqual(1); + expect(errors).toEqual([`Invalid self-closing element in index HTML file: ''.`]); + }); + + it('should not return an error when used on a supported element', async () => { + const { errors } = await augmentIndexHtml({ + ...indexGeneratorOptions, + html: ` + + +
+ + + ' + `, + }); + + expect(errors.length).toEqual(0); + }); + }); +}); diff --git a/packages/angular/build/src/utils/index-file/auto-csp.ts b/packages/angular/build/src/utils/index-file/auto-csp.ts new file mode 100644 index 000000000000..c50e0bfce3f2 --- /dev/null +++ b/packages/angular/build/src/utils/index-file/auto-csp.ts @@ -0,0 +1,303 @@ +/** + * @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.dev/license + */ + +import * as crypto from 'node:crypto'; +import { StartTag, htmlRewritingStream } from './html-rewriting-stream'; + +/** + * The hash function to use for hash directives to use in the CSP. + */ +const HASH_FUNCTION = 'sha256'; + +/** + * Store the appropriate attributes of a sourced script tag to generate the loader script. + */ +interface SrcScriptTag { + src: string; + type?: string; + async: boolean; + defer: boolean; +} + +/** + * Get the specified attribute or return undefined if the tag doesn't have that attribute. + * + * @param tag StartTag of the `); + scriptContent = []; + } + + rewriter.on('startTag', (tag) => { + if (tag.tagName === 'script') { + openedScriptTag = tag; + const src = getScriptAttributeValue(tag, 'src'); + + if (src) { + // If there are any interesting attributes, note them down. + const scriptType = getScriptAttributeValue(tag, 'type'); + if (shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) { + scriptContent.push({ + src: src, + type: scriptType, + async: getScriptAttributeValue(tag, 'async') !== undefined, + defer: getScriptAttributeValue(tag, 'defer') !== undefined, + }); + + return; // Skip writing my script tag until we've read it all. + } + } + } + // We are encountering the first start tag that's not tag if it's a part of the + // dynamic loader script. + if (src && shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) { + return; + } + } + + if (tag.tagName === 'head' || tag.tagName === 'body' || tag.tagName === 'html') { + // Write the loader script if a string of +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + expect(csps[0]).toMatch(ONE_HASH_CSP); + expect(csps[0]).toContain(hashTextContent("console.log('foo');")); + }); + + it('should rewrite a single source script', async () => { + const result = await autoCsp(` + + + + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + expect(csps[0]).toMatch(ONE_HASH_CSP); + expect(result).toContain(`var scripts = [['./main.js', '', false, false]];`); + }); + + it('should rewrite a single source script in place', async () => { + const result = await autoCsp(` + + + + +
Some text
+ + + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + expect(csps[0]).toMatch(ONE_HASH_CSP); + // Our loader script appears after the HTML text content. + expect(result).toMatch( + /Some text<\/div>\s* + + + + + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + expect(csps[0]).toMatch(TWO_HASH_CSP); + expect(result).toContain( + // eslint-disable-next-line max-len + `var scripts = [['./main1.js', '', false, false],['./main2.js', '', true, false],['./main3.js', 'module', true, true]];`, + ); + // Head loader script is in the head. + expect(result).toContain(``); + // Only two loader scripts are created. + expect(Array.from(result.matchAll(/ + + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + expect(csps[0]).toMatch(ONE_HASH_CSP); + // & encodes correctly + expect(result).toContain(`'/foo&bar'`); + // Impossible to escape a string and create invalid loader JS with a ' + // (Quotes and backslashes work) + expect(result).toContain(`'/one\\'two%5C\\'three%5C%5C\\'four%5C%5C%5C\\'five'`); + // HTML entities work + expect(result).toContain(`'/one&two&three&four'`); + // Cannot escape JS context to HTML + expect(result).toContain(`'./%3C/script%3E'`); + }); + + it('should rewrite all script tags', async () => { + const result = await autoCsp(` + + + + + + + + + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + // Exactly four hashes for the four scripts that remain (inline, loader, inline, loader). + expect(csps[0]).toMatch(FOUR_HASH_CSP); + expect(csps[0]).toContain(hashTextContent("console.log('foo');")); + expect(csps[0]).toContain(hashTextContent("console.log('bar');")); + // Loader script for main.js and main2.js appear after 'foo' and before 'bar'. + expect(result).toMatch( + // eslint-disable-next-line max-len + /console.log\('foo'\);<\/script>\s* + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps.length).toBe(1); + expect(csps[0]).toMatch(ONE_HASH_CSP); + + expect(result).toContain( + // eslint-disable-next-line max-len + `document.lastElementChild.appendChild`, + ); + // Head loader script is in the head. + expect(result).toContain(``); + // Only one loader script is created. + expect(Array.from(result.matchAll(/ + + + + + `); + + expect(result).toContain(``); + expect(result).toContain(''); + expect(result).toContain(``); + }); +}); diff --git a/packages/angular/build/src/utils/index-file/valid-self-closing-tags.ts b/packages/angular/build/src/utils/index-file/valid-self-closing-tags.ts new file mode 100644 index 000000000000..f86d556b36f0 --- /dev/null +++ b/packages/angular/build/src/utils/index-file/valid-self-closing-tags.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.dev/license + */ + +/** A list of valid self closing HTML elements */ +export const VALID_SELF_CLOSING_TAGS = new Set([ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', + + /** SVG tags */ + 'animate', + 'animateMotion', + 'animateTransform', + 'circle', + 'ellipse', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'line', + 'path', + 'polygon', + 'polyline', + 'rect', + 'text', + 'tspan', + 'linearGradient', + 'radialGradient', + 'stop', + 'image', + 'pattern', + 'defs', + 'g', + 'marker', + 'mask', + 'style', + 'symbol', + 'use', + 'view', + + /** MathML tags */ + 'mspace', + 'mphantom', + 'mrow', + 'mfrac', + 'msqrt', + 'mroot', + 'mstyle', + 'merror', + 'mpadded', + 'mtable', +]); diff --git a/packages/angular/build/src/utils/index.ts b/packages/angular/build/src/utils/index.ts new file mode 100644 index 000000000000..1a7cb15cd9c3 --- /dev/null +++ b/packages/angular/build/src/utils/index.ts @@ -0,0 +1,12 @@ +/** + * @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.dev/license + */ + +export * from './normalize-asset-patterns'; +export * from './normalize-optimization'; +export * from './normalize-source-maps'; +export * from './load-proxy-config'; diff --git a/packages/angular/build/src/utils/load-esm.ts b/packages/angular/build/src/utils/load-esm.ts new file mode 100644 index 000000000000..6a6220f66288 --- /dev/null +++ b/packages/angular/build/src/utils/load-esm.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.dev/license + */ + +/** + * Lazily compiled dynamic import loader function. + */ +let load: ((modulePath: string | URL) => Promise) | undefined; + +/** + * 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 { + load ??= new Function('modulePath', `return import(modulePath);`) as Exclude< + typeof load, + undefined + >; + + return load(modulePath); +} diff --git a/packages/angular/build/src/utils/load-proxy-config.ts b/packages/angular/build/src/utils/load-proxy-config.ts new file mode 100644 index 000000000000..cf4cb9e3c03e --- /dev/null +++ b/packages/angular/build/src/utils/load-proxy-config.ts @@ -0,0 +1,187 @@ +/** + * @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.dev/license + */ + +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { extname, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { makeRe as makeRegExpFromGlob } from 'picomatch'; +import { isDynamicPattern } from 'tinyglobby'; +import { assertIsError } from './error'; +import { loadEsmModule } from './load-esm'; + +export async function loadProxyConfiguration( + root: string, + proxyConfig: string | undefined, +): Promise | undefined> { + if (!proxyConfig) { + return undefined; + } + + const proxyPath = resolve(root, proxyConfig); + + if (!existsSync(proxyPath)) { + throw new Error(`Proxy configuration file ${proxyPath} does not exist.`); + } + + let proxyConfiguration; + switch (extname(proxyPath)) { + case '.json': { + const content = await readFile(proxyPath, 'utf-8'); + + const { parse, printParseErrorCode } = await import('jsonc-parser'); + const parseErrors: import('jsonc-parser').ParseError[] = []; + 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); + } + + break; + } + default: { + try { + proxyConfiguration = await import(proxyPath); + } catch (e) { + assertIsError(e); + if (e.code !== 'ERR_REQUIRE_ASYNC_MODULE') { + throw e; + } + + proxyConfiguration = await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath)); + } + + break; + } + } + + if ('default' in proxyConfiguration) { + proxyConfiguration = proxyConfiguration.default; + } + + return normalizeProxyConfiguration(proxyConfiguration); +} + +/** + * Converts glob patterns to regular expressions to support Vite's proxy option. + * Also converts the Webpack supported array form to an object form supported by both. + * + * @param proxy A proxy configuration object. + */ +function normalizeProxyConfiguration( + proxy: Record | object[], +): Record { + let normalizedProxy: Record | undefined; + + if (Array.isArray(proxy)) { + // Construct an object-form proxy configuration from the array + normalizedProxy = {}; + for (const proxyEntry of proxy) { + if (!('context' in proxyEntry)) { + continue; + } + if (!Array.isArray(proxyEntry.context)) { + continue; + } + + // Array-form entries contain a context string array with the path(s) + // to use for the configuration entry. + const context = proxyEntry.context; + delete proxyEntry.context; + for (const contextEntry of context) { + if (typeof contextEntry !== 'string') { + continue; + } + + normalizedProxy[contextEntry] = proxyEntry; + } + } + } else { + normalizedProxy = proxy; + } + + // TODO: Consider upstreaming glob support + for (const key of Object.keys(normalizedProxy)) { + if (key[0] !== '^' && isDynamicPattern(key)) { + const pattern = makeRegExpFromGlob(key).source; + normalizedProxy[pattern] = normalizedProxy[key]; + delete normalizedProxy[key]; + } + } + + // Replace `pathRewrite` field with a `rewrite` function + for (const proxyEntry of Object.values(normalizedProxy)) { + if ( + typeof proxyEntry === 'object' && + 'pathRewrite' in proxyEntry && + proxyEntry.pathRewrite && + typeof proxyEntry.pathRewrite === 'object' + ) { + // Preprocess path rewrite entries + const pathRewriteEntries: [RegExp, string][] = []; + for (const [pattern, value] of Object.entries( + proxyEntry.pathRewrite as Record, + )) { + pathRewriteEntries.push([new RegExp(pattern), value]); + } + + (proxyEntry as Record).rewrite = pathRewriter.bind( + undefined, + pathRewriteEntries, + ); + + delete proxyEntry.pathRewrite; + } + } + + return normalizedProxy; +} + +function pathRewriter(pathRewriteEntries: [RegExp, string][], path: string): string { + for (const [pattern, value] of pathRewriteEntries) { + const updated = path.replace(pattern, value); + if (path !== updated) { + return updated; + } + } + + return path; +} + +/** + * 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 }; +} diff --git a/packages/angular/build/src/utils/load-translations.ts b/packages/angular/build/src/utils/load-translations.ts new file mode 100644 index 000000000000..e202273dc73d --- /dev/null +++ b/packages/angular/build/src/utils/load-translations.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.dev/license + */ + +import type { Diagnostics } from '@angular/localize/tools'; +import { createHash } from 'node:crypto'; +import * as fs from 'node:fs'; + +export type TranslationLoader = (path: string) => { + translations: Record; + format: string; + locale?: string; + diagnostics: Diagnostics; + integrity: string; +}; + +export async function createTranslationLoader(): Promise { + const { parsers, diagnostics } = await importParsers(); + + return (path: string) => { + const content = fs.readFileSync(path, 'utf8'); + const unusedParsers = new Map(); + for (const [format, parser] of Object.entries(parsers)) { + const analysis = parser.analyze(path, content); + if (analysis.canParse) { + // 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 }; + } else { + unusedParsers.set(parser, analysis); + } + } + + const messages: string[] = []; + for (const [parser, analysis] of unusedParsers.entries()) { + messages.push(analysis.diagnostics.formatDiagnostics(`*** ${parser.constructor.name} ***`)); + } + throw new Error( + `Unsupported translation file format in ${path}. The following parsers were tried:\n` + + messages.join('\n'), + ); + }; +} + +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 import('@angular/localize/tools'); + + const diagnostics = new Diagnostics(); + const parsers = { + arb: new ArbTranslationParser(), + json: new SimpleJsonTranslationParser(), + xlf: new Xliff1TranslationParser(), + xlf2: new Xliff2TranslationParser(), + // The name ('xmb') needs to match the AOT compiler option + xmb: new XtbTranslationParser(), + }; + + return { parsers, diagnostics }; + } catch { + throw new Error( + `Unable to load translation file parsers. Please ensure '@angular/localize' is installed.`, + ); + } +} diff --git a/packages/angular/build/src/utils/normalize-asset-patterns.ts b/packages/angular/build/src/utils/normalize-asset-patterns.ts new file mode 100644 index 000000000000..8a8b2c2cbf1f --- /dev/null +++ b/packages/angular/build/src/utils/normalize-asset-patterns.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.dev/license + */ + +import assert from 'node:assert'; +import { statSync } from 'node:fs'; +import * as path from 'node:path'; +import { AssetPattern, AssetPatternClass } from '../builders/application/schema'; + +export class MissingAssetSourceRootException extends Error { + constructor(path: string) { + super(`The ${path} asset path must start with the project source root.`); + } +} + +export function normalizeAssetPatterns( + assetPatterns: AssetPattern[], + workspaceRoot: string, + projectRoot: string, + projectSourceRoot: string | undefined, +): (AssetPatternClass & { output: string })[] { + if (assetPatterns.length === 0) { + return []; + } + + // When sourceRoot is not available, we default to ${projectRoot}/src. + const sourceRoot = projectSourceRoot || path.join(projectRoot, 'src'); + const resolvedSourceRoot = path.resolve(workspaceRoot, sourceRoot); + + 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); + + // Check if the string asset is within sourceRoot. + if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) { + throw new MissingAssetSourceRootException(assetPattern); + } + + let glob: string, input: string; + let isDirectory = false; + + try { + isDirectory = statSync(resolvedAssetPath).isDirectory(); + } catch { + isDirectory = true; + } + + if (isDirectory) { + // Folders get a recursive star glob. + glob = '**/*'; + // Input directory is their original path. + input = assetPath; + } else { + // 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)); + + assetPattern = { glob, input, output }; + } else { + assetPattern.output = path.join('.', assetPattern.output ?? ''); + } + + assert(assetPattern.output !== undefined); + + if (assetPattern.output.startsWith('..')) { + throw new Error('An asset cannot be written to a location outside of the output path.'); + } + + return assetPattern as AssetPatternClass & { output: string }; + }); +} diff --git a/packages/angular/build/src/utils/normalize-cache.ts b/packages/angular/build/src/utils/normalize-cache.ts new file mode 100644 index 000000000000..f272f6a78e45 --- /dev/null +++ b/packages/angular/build/src/utils/normalize-cache.ts @@ -0,0 +1,75 @@ +/** + * @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.dev/license + */ + +import { join, resolve } from 'node:path'; + +/** Version placeholder is replaced during the build process with actual package version */ +const VERSION = '0.0.0-PLACEHOLDER'; + +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; +} + +function hasCacheMetadata(value: unknown): value is { cli: { cache: CacheMetadata } } { + return ( + !!value && + typeof value === 'object' && + 'cli' in value && + !!value['cli'] && + typeof value['cli'] === 'object' && + 'cache' in value['cli'] + ); +} + +export function normalizeCacheOptions( + projectMetadata: unknown, + worspaceRoot: string, +): NormalizedCachedOptions { + const cacheMetadata = hasCacheMetadata(projectMetadata) ? projectMetadata.cli.cache : {}; + + const { + // Webcontainers do not currently benefit from persistent disk caching and can lead to increased browser memory usage + enabled = !process.versions.webcontainer, + 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/build/src/utils/normalize-optimization.ts b/packages/angular/build/src/utils/normalize-optimization.ts new file mode 100644 index 000000000000..fcd5b556f27f --- /dev/null +++ b/packages/angular/build/src/utils/normalize-optimization.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.dev/license + */ + +import { + FontsClass, + OptimizationClass, + OptimizationUnion, + StylesClass, +} from '../builders/application/schema'; + +export type NormalizedOptimizationOptions = Required< + Omit +> & { + fonts: FontsClass; + styles: StylesClass; +}; + +export function normalizeOptimization( + optimization: OptimizationUnion = true, +): NormalizedOptimizationOptions { + if (typeof optimization === 'object') { + const styleOptimization = !!optimization.styles; + + return { + scripts: !!optimization.scripts, + styles: + typeof optimization.styles === 'object' + ? optimization.styles + : { + minify: styleOptimization, + removeSpecialComments: styleOptimization, + inlineCritical: styleOptimization, + }, + fonts: + typeof optimization.fonts === 'object' + ? optimization.fonts + : { + inline: !!optimization.fonts, + }, + }; + } + + return { + scripts: optimization, + styles: { + minify: optimization, + inlineCritical: optimization, + removeSpecialComments: optimization, + }, + fonts: { + inline: optimization, + }, + }; +} diff --git a/packages/angular/build/src/utils/normalize-source-maps.ts b/packages/angular/build/src/utils/normalize-source-maps.ts new file mode 100644 index 000000000000..cf26ca236bae --- /dev/null +++ b/packages/angular/build/src/utils/normalize-source-maps.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.dev/license + */ + +import { SourceMapClass, SourceMapUnion } from '../builders/application/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 sourcesContent = typeof sourceMap === 'object' ? sourceMap.sourcesContent : sourceMap; + + return { + vendor, + hidden, + scripts, + styles, + sourcesContent, + }; +} diff --git a/packages/angular/build/src/utils/path.ts b/packages/angular/build/src/utils/path.ts new file mode 100644 index 000000000000..036dcb23502e --- /dev/null +++ b/packages/angular/build/src/utils/path.ts @@ -0,0 +1,37 @@ +/** + * @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.dev/license + */ + +import { posix } from 'node:path'; +import { platform } from 'node:process'; + +const WINDOWS_PATH_SEPERATOR_REGEXP = /\\/g; + +/** + * Converts a Windows-style file path to a POSIX-compliant path. + * + * This function replaces all backslashes (`\`) with forward slashes (`/`). + * It is a no-op on POSIX systems (e.g., Linux, macOS), as the conversion + * only runs on Windows (`win32`). + * + * @param path - The file path to convert. + * @returns The POSIX-compliant file path. + * + * @example + * ```ts + * // On a Windows system: + * toPosixPath('C:\\Users\\Test\\file.txt'); + * // => 'C:/Users/Test/file.txt' + * + * // On a POSIX system (Linux/macOS): + * toPosixPath('/home/user/file.txt'); + * // => '/home/user/file.txt' + * ``` + */ +export function toPosixPath(path: string): string { + return platform === 'win32' ? path.replace(WINDOWS_PATH_SEPERATOR_REGEXP, posix.sep) : path; +} diff --git a/packages/angular/build/src/utils/postcss-configuration.ts b/packages/angular/build/src/utils/postcss-configuration.ts new file mode 100644 index 000000000000..6f3f1f3671f9 --- /dev/null +++ b/packages/angular/build/src/utils/postcss-configuration.ts @@ -0,0 +1,127 @@ +/** + * @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.dev/license + */ + +import { readFile, readdir } from 'node:fs/promises'; +import { join } from 'node:path'; + +export interface PostcssConfiguration { + plugins: [name: string, options?: object | string][]; +} + +interface RawPostcssConfiguration { + plugins?: Record | (string | [string, object])[]; +} + +const postcssConfigurationFiles: string[] = ['postcss.config.json', '.postcssrc.json']; +const tailwindConfigFiles: string[] = [ + 'tailwind.config.js', + 'tailwind.config.cjs', + 'tailwind.config.mjs', + 'tailwind.config.ts', +]; + +export interface SearchDirectory { + root: string; + files: Set; +} + +export async function generateSearchDirectories(roots: string[]): Promise { + return await Promise.all( + roots.map((root) => + readdir(root, { withFileTypes: true }).then((entries) => ({ + root, + files: new Set(entries.filter((entry) => entry.isFile()).map((entry) => entry.name)), + })), + ), + ); +} + +function findFile( + searchDirectories: SearchDirectory[], + potentialFiles: string[], +): string | undefined { + for (const { root, files } of searchDirectories) { + for (const potential of potentialFiles) { + if (files.has(potential)) { + return join(root, potential); + } + } + } + + return undefined; +} + +export function findTailwindConfiguration( + searchDirectories: SearchDirectory[], +): string | undefined { + return findFile(searchDirectories, tailwindConfigFiles); +} + +async function readPostcssConfiguration( + configurationFile: string, +): Promise { + const data = await readFile(configurationFile, 'utf-8'); + const config = JSON.parse(data) as RawPostcssConfiguration; + + return config; +} + +export async function loadPostcssConfiguration(searchDirectories: SearchDirectory[]): Promise< + | { + configPath: string; + config: PostcssConfiguration; + } + | undefined +> { + const configPath = findFile(searchDirectories, postcssConfigurationFiles); + if (!configPath) { + return undefined; + } + + const raw = await readPostcssConfiguration(configPath); + + // If no plugins are defined, consider it equivalent to no configuration + if (!raw.plugins || typeof raw.plugins !== 'object') { + return undefined; + } + + // Normalize plugin array form + if (Array.isArray(raw.plugins)) { + if (raw.plugins.length < 1) { + return undefined; + } + + const config: PostcssConfiguration = { plugins: [] }; + for (const element of raw.plugins) { + if (typeof element === 'string') { + config.plugins.push([element]); + } else { + config.plugins.push(element); + } + } + + return { config, configPath }; + } + + // Normalize plugin object map form + const entries = Object.entries(raw.plugins); + if (entries.length < 1) { + return undefined; + } + + const config: PostcssConfiguration = { plugins: [] }; + for (const [name, options] of entries) { + if (!options || (typeof options !== 'object' && typeof options !== 'string')) { + continue; + } + + config.plugins.push([name, options]); + } + + return { config, configPath }; +} diff --git a/packages/angular/build/src/utils/project-metadata.ts b/packages/angular/build/src/utils/project-metadata.ts new file mode 100644 index 000000000000..31912d5e9905 --- /dev/null +++ b/packages/angular/build/src/utils/project-metadata.ts @@ -0,0 +1,37 @@ +/** + * @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.dev/license + */ + +import { join } from 'node:path'; + +/** + * Normalize a directory path string. + * Currently only removes a trailing slash if present. + * @param path A path string. + * @returns A normalized path string. + */ +export function normalizeDirectoryPath(path: string): string { + const last = path.at(-1); + if (last === '/' || last === '\\') { + return path.slice(0, -1); + } + + return path; +} + +export function getProjectRootPaths( + workspaceRoot: string, + projectMetadata: { root?: string; sourceRoot?: string }, +) { + const projectRoot = normalizeDirectoryPath(join(workspaceRoot, projectMetadata.root ?? '')); + const rawSourceRoot = projectMetadata.sourceRoot; + const projectSourceRoot = normalizeDirectoryPath( + rawSourceRoot === undefined ? join(projectRoot, 'src') : join(workspaceRoot, rawSourceRoot), + ); + + return { projectRoot, projectSourceRoot }; +} diff --git a/packages/angular/build/src/utils/purge-cache.ts b/packages/angular/build/src/utils/purge-cache.ts new file mode 100644 index 000000000000..5851d052d54a --- /dev/null +++ b/packages/angular/build/src/utils/purge-cache.ts @@ -0,0 +1,43 @@ +/** + * @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.dev/license + */ + +import { BuilderContext } from '@angular-devkit/architect'; +import { readdir, rm } from 'node:fs/promises'; +import { join } from 'node: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) { + return; + } + + let baseEntries; + try { + baseEntries = await readdir(basePath, { withFileTypes: true }); + } catch { + // No purging possible if base path does not exist or cannot otherwise be accessed + return; + } + + const entriesToDelete = baseEntries + .filter((d) => d.isDirectory()) + .map((d) => join(basePath, d.name)) + .filter((cachePath) => cachePath !== path) + .map((stalePath) => rm(stalePath, { force: true, recursive: true, maxRetries: 3 })); + + await Promise.allSettled(entriesToDelete); +} diff --git a/packages/angular/build/src/utils/resolve-assets.ts b/packages/angular/build/src/utils/resolve-assets.ts new file mode 100644 index 000000000000..e98879e58de7 --- /dev/null +++ b/packages/angular/build/src/utils/resolve-assets.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.dev/license + */ + +import path from 'node:path'; +import { glob } from 'tinyglobby'; + +export async function resolveAssets( + entries: { + glob: string; + ignore?: string[]; + input: string; + output: string; + flatten?: boolean; + followSymlinks?: boolean; + }[], + root: string, +): Promise<{ source: string; destination: string }[]> { + const defaultIgnore = ['.gitkeep', '**/.DS_Store', '**/Thumbs.db']; + + const outputFiles: { source: string; destination: string }[] = []; + + for (const entry of entries) { + const cwd = path.resolve(root, entry.input); + const files = await glob(entry.glob, { + cwd, + dot: true, + ignore: entry.ignore ? defaultIgnore.concat(entry.ignore) : defaultIgnore, + followSymbolicLinks: entry.followSymlinks, + }); + + for (const file of files) { + const src = path.join(cwd, file); + const filePath = entry.flatten ? path.basename(file) : file; + + outputFiles.push({ source: src, destination: path.join(entry.output, filePath) }); + } + } + + return outputFiles; +} diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts new file mode 100644 index 000000000000..1d0d9df32d30 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts @@ -0,0 +1,152 @@ +/** + * @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.dev/license + */ + +import assert from 'node:assert'; +import { randomUUID } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +/** + * @note For some unknown reason, setting `globalThis.ngServerMode = true` does not work when using ESM loader hooks. + */ +const NG_SERVER_MODE_INIT_BYTES = new TextEncoder().encode('var ngServerMode=true;'); + +/** + * Node.js ESM loader to redirect imports to in memory files. + * @see: https://nodejs.org/api/esm.html#loaders for more information about loaders. + */ + +const MEMORY_URL_SCHEME = 'memory://'; + +export interface ESMInMemoryFileLoaderWorkerData { + outputFiles: Record; + workspaceRoot: string; +} + +let memoryVirtualRootUrl: string; +let outputFiles: Record; + +export function initialize(data: ESMInMemoryFileLoaderWorkerData) { + // This path does not actually exist but is used to overlay the in memory files with the + // actual filesystem for resolution purposes. + // A custom URL schema (such as `memory://`) cannot be used for the resolve output because + // the in-memory files may use `import.meta.url` in ways that assume a file URL. + // `createRequire` is one example of this usage. + memoryVirtualRootUrl = pathToFileURL( + join(data.workspaceRoot, `.angular/prerender-root/${randomUUID()}/`), + ).href; + outputFiles = data.outputFiles; +} + +export function resolve( + specifier: string, + context: { parentURL: undefined | string }, + nextResolve: Function, +) { + // In-memory files loaded from external code will contain a memory scheme + if (specifier.startsWith(MEMORY_URL_SCHEME)) { + let memoryUrl; + try { + memoryUrl = new URL(specifier); + } catch { + assert.fail('External code attempted to use malformed memory scheme: ' + specifier); + } + + // Resolve with a URL based from the virtual filesystem root + return { + format: 'module', + shortCircuit: true, + url: new URL(memoryUrl.pathname.slice(1), memoryVirtualRootUrl).href, + }; + } + + // Use next/default resolve if the parent is not from the virtual root + if (!context.parentURL?.startsWith(memoryVirtualRootUrl)) { + return nextResolve(specifier, context); + } + + // Check for `./` and `../` relative specifiers + const isRelative = + specifier[0] === '.' && + (specifier[1] === '/' || (specifier[1] === '.' && specifier[2] === '/')); + + // Relative specifiers from memory file should be based from the parent memory location + if (isRelative) { + let specifierUrl; + try { + specifierUrl = new URL(specifier, context.parentURL); + } catch {} + + if ( + specifierUrl?.pathname && + Object.hasOwn(outputFiles, specifierUrl.href.slice(memoryVirtualRootUrl.length)) + ) { + return { + format: 'module', + shortCircuit: true, + url: specifierUrl.href, + }; + } + + assert.fail( + `In-memory ESM relative file should always exist: '${context.parentURL}' --> '${specifier}'`, + ); + } + + // Update the parent URL to allow for module resolution for the workspace. + // This handles bare specifiers (npm packages) and absolute paths. + // Defer to the next hook in the chain, which would be the + // Node.js default resolve if this is the last user-specified loader. + return nextResolve(specifier, { + ...context, + parentURL: new URL('index.js', memoryVirtualRootUrl).href, + }); +} + +export async function load(url: string, context: { format?: string | null }, nextLoad: Function) { + const { format } = context; + + // Load the file from memory if the URL is based in the virtual root + if (url.startsWith(memoryVirtualRootUrl)) { + const source = outputFiles[url.slice(memoryVirtualRootUrl.length)]; + assert(source !== undefined, 'Resolved in-memory ESM file should always exist: ' + url); + + // In-memory files have already been transformer during bundling and can be returned directly + return { + format, + shortCircuit: true, + source, + }; + } + + // Only module files potentially require transformation. Angular libraries that would + // need linking are ESM only. + if (format === 'module' && isFileProtocol(url)) { + const filePath = fileURLToPath(url); + let source = await readFile(filePath); + + if (filePath.includes('@angular/')) { + // Prepend 'var ngServerMode=true;' to the source. + source = Buffer.concat([NG_SERVER_MODE_INIT_BYTES, source]); + } + + return { + format, + shortCircuit: true, + source, + }; + } + + // Let Node.js handle all other URLs. + return nextLoad(url); +} + +function isFileProtocol(url: string): boolean { + return url.startsWith('file://'); +} diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/register-hooks.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/register-hooks.ts new file mode 100644 index 000000000000..b23fe297bc19 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/register-hooks.ts @@ -0,0 +1,13 @@ +/** + * @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.dev/license + */ + +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; +import { workerData } from 'node:worker_threads'; + +register('./loader-hooks.js', { parentURL: pathToFileURL(__filename), data: workerData }); diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts new file mode 100644 index 000000000000..3af354f6ba0f --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts @@ -0,0 +1,13 @@ +/** + * @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.dev/license + */ + +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export const IMPORT_EXEC_ARGV = + '--import=' + pathToFileURL(join(__dirname, 'register-hooks.js')).href; diff --git a/packages/angular/build/src/utils/server-rendering/fetch-patch.ts b/packages/angular/build/src/utils/server-rendering/fetch-patch.ts new file mode 100644 index 000000000000..c099d7dd902c --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/fetch-patch.ts @@ -0,0 +1,72 @@ +/** + * @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.dev/license + */ + +import { lookup as lookupMimeType } from 'mrmime'; +import { readFile } from 'node:fs/promises'; +import { extname } from 'node:path'; +import { workerData } from 'node:worker_threads'; + +/** + * This is passed as workerData when setting up the worker via the `piscina` package. + */ +const { assetFiles } = workerData as { + assetFiles: Record; +}; + +const assetsCache: Map; content: Buffer }> = + new Map(); + +export function patchFetchToLoadInMemoryAssets(baseURL: URL): void { + const originalFetch = globalThis.fetch; + const patchedFetch: typeof fetch = async (input, init) => { + let url: URL; + if (input instanceof URL) { + url = input; + } else if (typeof input === 'string') { + url = new URL(input, baseURL); + } else if (typeof input === 'object' && 'url' in input) { + url = new URL(input.url, baseURL); + } else { + return originalFetch(input, init); + } + + const { hostname } = url; + const pathname = decodeURIComponent(url.pathname); + + if (hostname !== baseURL.hostname || !assetFiles[pathname]) { + // Only handle relative requests or files that are in assets. + return originalFetch(input, init); + } + + const cachedAsset = assetsCache.get(pathname); + if (cachedAsset) { + const { content, headers } = cachedAsset; + + return new Response(content, { + headers, + }); + } + + const extension = extname(pathname); + const mimeType = lookupMimeType(extension); + const content = await readFile(assetFiles[pathname]); + const headers = mimeType + ? { + 'Content-Type': mimeType, + } + : undefined; + + assetsCache.set(pathname, { headers, content }); + + return new Response(content, { + headers, + }); + }; + + globalThis.fetch = patchedFetch; +} diff --git a/packages/angular/build/src/utils/server-rendering/launch-server.ts b/packages/angular/build/src/utils/server-rendering/launch-server.ts new file mode 100644 index 000000000000..95b2784c6f63 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/launch-server.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.dev/license + */ + +import assert from 'node:assert'; +import { createServer } from 'node:http'; +import { loadEsmModuleFromMemory } from './load-esm-from-memory'; +import { isSsrNodeRequestHandler, isSsrRequestHandler } from './utils'; + +export const DEFAULT_URL = new URL('http://ng-localhost/'); + +/** + * Launches a server that handles local requests. + * + * @returns A promise that resolves to the URL of the running server. + */ +export async function launchServer(): Promise { + const { reqHandler } = await loadEsmModuleFromMemory('./server.mjs'); + const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = (await import( + '@angular/ssr/node' as string + )) as typeof import('@angular/ssr/node', { with: { 'resolution-mode': 'import' } }); + + if (!isSsrNodeRequestHandler(reqHandler) && !isSsrRequestHandler(reqHandler)) { + return DEFAULT_URL; + } + + const server = createServer((req, res) => { + (async () => { + // handle request + if (isSsrNodeRequestHandler(reqHandler)) { + await reqHandler(req, res, (e) => { + throw e ?? new Error(`Unable to handle request: '${req.url}'.`); + }); + } else { + const webRes = await reqHandler(createWebRequestFromNodeRequest(req)); + if (webRes) { + await writeResponseToNodeResponse(webRes, res); + } else { + res.statusCode = 501; + res.end('Not Implemented.'); + } + } + })().catch((e) => { + res.statusCode = 500; + res.end('Internal Server Error.'); + // eslint-disable-next-line no-console + console.error(e); + }); + }); + + server.unref(); + + await new Promise((resolve) => server.listen(0, 'localhost', resolve)); + + const serverAddress = server.address(); + assert(serverAddress, 'Server address should be defined.'); + assert(typeof serverAddress !== 'string', 'Server address should not be a string.'); + + return new URL(`http://localhost:${serverAddress.port}/`); +} diff --git a/packages/angular/build/src/utils/server-rendering/load-esm-from-memory.ts b/packages/angular/build/src/utils/server-rendering/load-esm-from-memory.ts new file mode 100644 index 000000000000..87ca9928a86f --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/load-esm-from-memory.ts @@ -0,0 +1,51 @@ +/** + * @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.dev/license + */ + +import type { ApplicationRef, Type } from '@angular/core'; +import type { BootstrapContext } from '@angular/platform-browser'; +import type { ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp } from '@angular/ssr'; +import { assertIsError } from '../error'; +import { loadEsmModule } from '../load-esm'; + +/** + * Represents the exports available from the main server bundle. + */ +interface MainServerBundleExports { + default: ((context: BootstrapContext) => Promise) | Type; + ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree; + ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp; +} + +/** + * Represents the exports available from the server bundle. + */ +interface ServerBundleExports { + reqHandler?: unknown; +} + +export function loadEsmModuleFromMemory( + path: './main.server.mjs', +): Promise; +export function loadEsmModuleFromMemory(path: './server.mjs'): Promise; +export function loadEsmModuleFromMemory( + path: './main.server.mjs' | './server.mjs', +): Promise { + return loadEsmModule(new URL(path, 'memory://')).catch((e) => { + assertIsError(e); + + // While the error is an 'instanceof Error', it is extended with non transferable properties + // and cannot be transferred from a worker when using `--import`. This results in the error object + // displaying as '[Object object]' when read outside of the worker. Therefore, we reconstruct the error message here. + const error: Error & { code?: string } = new Error(e.message); + error.stack = e.stack; + error.name = e.name; + error.code = e.code; + + throw error; + }) as Promise; +} diff --git a/packages/angular/build/src/utils/server-rendering/manifest.ts b/packages/angular/build/src/utils/server-rendering/manifest.ts new file mode 100644 index 000000000000..b01bff38b58f --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/manifest.ts @@ -0,0 +1,226 @@ +/** + * @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.dev/license + */ + +import type { Metafile } from 'esbuild'; +import { extname } from 'node:path'; +import { runInThisContext } from 'node:vm'; +import { NormalizedApplicationBuildOptions } from '../../builders/application/options'; +import { type BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context'; +import { createOutputFile } from '../../tools/esbuild/utils'; +import { shouldOptimizeChunks } from '../environment-options'; + +export const SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs'; +export const SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs'; + +interface FilesMapping { + path: string; + dynamicImport: boolean; +} + +const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs'; + +/** + * A mapping of unsafe characters to their escaped Unicode equivalents. + */ +const UNSAFE_CHAR_MAP: Record = { + '`': '\\`', + '$': '\\$', + '\\': '\\\\', +}; + +/** + * Escapes unsafe characters in a given string by replacing them with + * their Unicode escape sequences. + * + * @param str - The string to be escaped. + * @returns The escaped string where unsafe characters are replaced. + */ +function escapeUnsafeChars(str: string): string { + return str.replace(/[$`\\]/g, (c) => UNSAFE_CHAR_MAP[c]); +} + +/** + * Generates the server manifest for the App Engine environment. + * + * This manifest is used to configure the server-side rendering (SSR) setup for the + * Angular application when deployed to Google App Engine. It includes the entry points + * for different locales and the base HREF for the application. + * + * @param i18nOptions - The internationalization options for the application build. This + * includes settings for inlining locales and determining the output structure. + * @param baseHref - The base HREF for the application. This is used to set the base URL + * for all relative URLs in the application. + */ +export function generateAngularServerAppEngineManifest( + i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], + baseHref: string | undefined, +): string { + const entryPoints: Record = {}; + const supportedLocales: Record = {}; + + if (i18nOptions.shouldInline && !i18nOptions.flatOutput) { + for (const locale of i18nOptions.inlineLocales) { + const { subPath } = i18nOptions.locales[locale]; + const importPath = `${subPath ? `${subPath}/` : ''}${MAIN_SERVER_OUTPUT_FILENAME}`; + entryPoints[subPath] = `() => import('./${importPath}')`; + supportedLocales[locale] = subPath; + } + } else { + entryPoints[''] = `() => import('./${MAIN_SERVER_OUTPUT_FILENAME}')`; + supportedLocales[i18nOptions.sourceLocale] = ''; + } + + // Remove trailing slash but retain leading slash. + let basePath = baseHref || '/'; + if (basePath.length > 1 && basePath.at(-1) === '/') { + basePath = basePath.slice(0, -1); + } + + const manifestContent = ` +export default { + basePath: '${basePath}', + supportedLocales: ${JSON.stringify(supportedLocales, undefined, 2)}, + entryPoints: { + ${Object.entries(entryPoints) + .map(([key, value]) => `'${key}': ${value}`) + .join(',\n ')} + }, +}; +`; + + return manifestContent; +} + +/** + * Generates the server manifest for the standard Node.js environment. + * + * This manifest is used to configure the server-side rendering (SSR) setup for the + * Angular application when running in a standard Node.js environment. It includes + * information about the bootstrap module, whether to inline critical CSS, and any + * additional HTML and CSS output files. + * + * @param additionalHtmlOutputFiles - A map of additional HTML output files generated + * during the build process, keyed by their file paths. + * @param outputFiles - An array of all output files from the build process, including + * JavaScript and CSS files. + * @param inlineCriticalCss - A boolean indicating whether critical CSS should be inlined + * in the server-side rendered pages. + * @param routes - An optional array of route definitions for the application, used for + * server-side rendering and routing. + * @param locale - An optional string representing the locale or language code to be used for + * the application, helping with localization and rendering content specific to the locale. + * @param baseHref - The base HREF for the application. This is used to set the base URL + * for all relative URLs in the application. + * @param initialFiles - A list of initial files that preload tags have already been added for. + * @param metafile - An esbuild metafile object. + * @param publicPath - The configured public path. + * + * @returns An object containing: + * - `manifestContent`: A string of the SSR manifest content. + * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server. + */ +export function generateAngularServerAppManifest( + additionalHtmlOutputFiles: Map, + outputFiles: BuildOutputFile[], + inlineCriticalCss: boolean, + routes: readonly unknown[] | undefined, + locale: string | undefined, + baseHref: string, + initialFiles: Set, + metafile: Metafile, + publicPath: string | undefined, +): { + manifestContent: string; + serverAssetsChunks: BuildOutputFile[]; +} { + const serverAssetsChunks: BuildOutputFile[] = []; + const serverAssets: Record = {}; + + for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) { + const extension = extname(file.path); + if (extension === '.html' || (inlineCriticalCss && extension === '.css')) { + const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`; + const escapedContent = escapeUnsafeChars(file.text); + + serverAssetsChunks.push( + createOutputFile( + jsChunkFilePath, + `export default \`${escapedContent}\`;`, + BuildOutputFileType.ServerApplication, + ), + ); + + // This is needed because JavaScript engines script parser convert `\r\n` to `\n` in template literals, + // which can result in an incorrect byte length. + const size = runInThisContext(`new TextEncoder().encode(\`${escapedContent}\`).byteLength`); + + serverAssets[file.path] = + `{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`; + } + } + + // When routes have been extracted, mappings are no longer needed, as preloads will be included in the metadata. + // When shouldOptimizeChunks is enabled the metadata is no longer correct and thus we cannot generate the mappings. + const entryPointToBrowserMapping = + routes?.length || shouldOptimizeChunks + ? undefined + : generateLazyLoadedFilesMappings(metafile, initialFiles, publicPath); + + const manifestContent = ` +export default { + bootstrap: () => import('./main.server.mjs').then(m => m.default), + inlineCriticalCss: ${inlineCriticalCss}, + baseHref: '${baseHref}', + locale: ${JSON.stringify(locale)}, + routes: ${JSON.stringify(routes, undefined, 2)}, + entryPointToBrowserMapping: ${JSON.stringify(entryPointToBrowserMapping, undefined, 2)}, + assets: { + ${Object.entries(serverAssets) + .map(([key, value]) => `'${key}': ${value}`) + .join(',\n ')} + }, +}; +`; + + return { manifestContent, serverAssetsChunks }; +} + +/** + * Maps entry points to their corresponding browser bundles for lazy loading. + * + * This function processes a metafile's outputs to generate a mapping between browser-side entry points + * and the associated JavaScript files that should be loaded in the browser. It includes the entry-point's + * own path and any valid imports while excluding initial files or external resources. + */ +function generateLazyLoadedFilesMappings( + metafile: Metafile, + initialFiles: Set, + publicPath = '', +): Record { + const entryPointToBundles: Record = {}; + for (const [fileName, { entryPoint, exports, imports }] of Object.entries(metafile.outputs)) { + // Skip files that don't have an entryPoint, no exports, or are not .js + if (!entryPoint || exports?.length < 1 || !fileName.endsWith('.js')) { + continue; + } + + const importedPaths: string[] = [`${publicPath}${fileName}`]; + + for (const { kind, external, path } of imports) { + if (external || initialFiles.has(path) || kind !== 'import-statement') { + continue; + } + + importedPaths.push(`${publicPath}${path}`); + } + + entryPointToBundles[entryPoint] = importedPaths; + } + + return entryPointToBundles; +} diff --git a/packages/angular/build/src/utils/server-rendering/models.ts b/packages/angular/build/src/utils/server-rendering/models.ts new file mode 100644 index 000000000000..9a9020d2db7f --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/models.ts @@ -0,0 +1,40 @@ +/** + * @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.dev/license + */ + +import type { RenderMode, ɵextractRoutesAndCreateRouteTree } from '@angular/ssr'; +import { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks'; + +type Writeable = T extends readonly (infer U)[] ? U[] : never; + +export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData { + assetFiles: Record; +} + +export type SerializableRouteTreeNode = ReturnType< + Awaited>['routeTree']['toObject'] +>; + +export type WritableSerializableRouteTreeNode = Writeable; + +export interface RoutersExtractorWorkerResult { + serializedRouteTree: SerializableRouteTreeNode; + appShellRoute?: string; + errors: string[]; +} + +/** + * Local copy of `RenderMode` exported from `@angular/ssr`. + * This constant is needed to handle interop between CommonJS (CJS) and ES Modules (ESM) formats. + * + * It maps `RenderMode` enum values to their corresponding numeric identifiers. + */ +export const RouteRenderMode: Record = { + Server: 0, + Client: 1, + Prerender: 2, +}; diff --git a/packages/angular/build/src/utils/server-rendering/prerender.ts b/packages/angular/build/src/utils/server-rendering/prerender.ts new file mode 100644 index 000000000000..f33f851f10c4 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/prerender.ts @@ -0,0 +1,371 @@ +/** + * @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.dev/license + */ + +import { readFile } from 'node:fs/promises'; +import { extname, posix } from 'node:path'; +import { NormalizedApplicationBuildOptions } from '../../builders/application/options'; +import { OutputMode } from '../../builders/application/schema'; +import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context'; +import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result'; +import { assertIsError } from '../error'; +import { toPosixPath } from '../path'; +import { addLeadingSlash, addTrailingSlash, joinUrlParts, stripLeadingSlash } from '../url'; +import { WorkerPool } from '../worker-pool'; +import { IMPORT_EXEC_ARGV } from './esm-in-memory-loader/utils'; +import { SERVER_APP_MANIFEST_FILENAME } from './manifest'; +import { + RouteRenderMode, + RoutersExtractorWorkerResult, + RoutesExtractorWorkerData, + SerializableRouteTreeNode, + WritableSerializableRouteTreeNode, +} from './models'; +import type { RenderWorkerData } from './render-worker'; +import { generateRedirectStaticPage } from './utils'; + +type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions']; +type AppShellOptions = NormalizedApplicationBuildOptions['appShellOptions']; + +/** + * Represents the output of a prerendering process. + * + * The key is the file path, and the value is an object containing the following properties: + * + * - `content`: The HTML content or output generated for the corresponding file path. + * - `appShellRoute`: A boolean flag indicating whether the content is an app shell. + * + * @example + * { + * '/index.html': { content: '...', appShell: false }, + * '/shell/index.html': { content: '...', appShellRoute: true } + * } + */ +type PrerenderOutput = Record; + +export async function prerenderPages( + workspaceRoot: string, + baseHref: string, + appShellOptions: AppShellOptions | undefined, + prerenderOptions: PrerenderOptions | undefined, + outputFiles: Readonly, + assets: Readonly, + outputMode: OutputMode | undefined, + sourcemap = false, + maxThreads = 1, +): Promise<{ + output: PrerenderOutput; + warnings: string[]; + errors: string[]; + serializableRouteTreeNode: SerializableRouteTreeNode; +}> { + const outputFilesForWorker: Record = {}; + const serverBundlesSourceMaps = new Map(); + const warnings: string[] = []; + const errors: string[] = []; + + for (const { text, path, type } of outputFiles) { + if (type !== BuildOutputFileType.ServerApplication && type !== BuildOutputFileType.ServerRoot) { + continue; + } + + // Contains the server runnable application code + if (extname(path) === '.map') { + serverBundlesSourceMaps.set(path.slice(0, -4), text); + } else { + outputFilesForWorker[path] = text; + } + } + + // Inline sourcemap into JS file. This is needed to make Node.js resolve sourcemaps + // when using `--enable-source-maps` when using in memory files. + for (const [filePath, map] of serverBundlesSourceMaps) { + const jsContent = outputFilesForWorker[filePath]; + if (jsContent) { + outputFilesForWorker[filePath] = + jsContent + + `\n//# sourceMappingURL=` + + `data:application/json;base64,${Buffer.from(map).toString('base64')}`; + } + } + serverBundlesSourceMaps.clear(); + + const assetsReversed: Record = {}; + for (const { source, destination } of assets) { + assetsReversed[addLeadingSlash(toPosixPath(destination))] = source; + } + + // Get routes to prerender + const { + errors: extractionErrors, + serializedRouteTree: serializableRouteTreeNode, + appShellRoute, + } = await getAllRoutes( + workspaceRoot, + baseHref, + outputFilesForWorker, + assetsReversed, + appShellOptions, + prerenderOptions, + sourcemap, + outputMode, + ).catch((err) => { + return { + errors: [`An error occurred while extracting routes.\n\n${err.message ?? err.stack ?? err}`], + serializedRouteTree: [], + appShellRoute: undefined, + }; + }); + + errors.push(...extractionErrors); + + const serializableRouteTreeNodeForPrerender: WritableSerializableRouteTreeNode = []; + for (const metadata of serializableRouteTreeNode) { + if (outputMode !== OutputMode.Static && metadata.redirectTo) { + // Skip redirects if output mode is not static. + continue; + } + + if (metadata.route.includes('*')) { + // Skip catch all routes from prerender. + continue; + } + + switch (metadata.renderMode) { + case undefined: /* Legacy building mode */ + case RouteRenderMode.Prerender: + serializableRouteTreeNodeForPrerender.push(metadata); + break; + case RouteRenderMode.Server: + if (outputMode === OutputMode.Static) { + errors.push( + `Route '${metadata.route}' is configured with server render mode, but the build 'outputMode' is set to 'static'.`, + ); + } + break; + } + } + + if (!serializableRouteTreeNodeForPrerender.length || errors.length > 0) { + return { + errors, + warnings, + output: {}, + serializableRouteTreeNode, + }; + } + + // Add the extracted routes to the manifest file. + // We could re-generate it from the start, but that would require a number of options to be passed down. + const manifest = outputFilesForWorker[SERVER_APP_MANIFEST_FILENAME]; + if (manifest) { + outputFilesForWorker[SERVER_APP_MANIFEST_FILENAME] = manifest.replace( + 'routes: undefined,', + `routes: ${JSON.stringify(serializableRouteTreeNodeForPrerender, undefined, 2)},`, + ); + } + + // Render routes + const { errors: renderingErrors, output } = await renderPages( + baseHref, + sourcemap, + serializableRouteTreeNodeForPrerender, + maxThreads, + workspaceRoot, + outputFilesForWorker, + assetsReversed, + outputMode, + appShellRoute ?? appShellOptions?.route, + ); + + errors.push(...renderingErrors); + + return { + errors, + warnings, + output, + serializableRouteTreeNode, + }; +} + +async function renderPages( + baseHref: string, + sourcemap: boolean, + serializableRouteTreeNode: SerializableRouteTreeNode, + maxThreads: number, + workspaceRoot: string, + outputFilesForWorker: Record, + assetFilesForWorker: Record, + outputMode: OutputMode | undefined, + appShellRoute: string | undefined, +): Promise<{ + output: PrerenderOutput; + errors: string[]; +}> { + const output: PrerenderOutput = {}; + const errors: string[] = []; + const workerExecArgv = [IMPORT_EXEC_ARGV]; + + if (sourcemap) { + workerExecArgv.push('--enable-source-maps'); + } + + const renderWorker = new WorkerPool({ + filename: require.resolve('./render-worker'), + maxThreads: Math.min(serializableRouteTreeNode.length, maxThreads), + workerData: { + workspaceRoot, + outputFiles: outputFilesForWorker, + assetFiles: assetFilesForWorker, + outputMode, + hasSsrEntry: !!outputFilesForWorker['server.mjs'], + } as RenderWorkerData, + execArgv: workerExecArgv, + }); + + try { + const renderingPromises: Promise[] = []; + const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute); + const baseHrefPathnameWithLeadingSlash = new URL(baseHref, 'http://localhost').pathname; + + for (const { route, redirectTo } of serializableRouteTreeNode) { + // Remove the base href from the file output path. + const routeWithoutBaseHref = addTrailingSlash(route).startsWith( + baseHrefPathnameWithLeadingSlash, + ) + ? addLeadingSlash(route.slice(baseHrefPathnameWithLeadingSlash.length)) + : route; + + const outPath = stripLeadingSlash(posix.join(routeWithoutBaseHref, 'index.html')); + + if (typeof redirectTo === 'string') { + output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false }; + + continue; + } + + const render: Promise = renderWorker.run({ url: route }); + const renderResult: Promise = render + .then((content) => { + if (content !== null) { + output[outPath] = { + content, + appShellRoute: appShellRouteWithLeadingSlash === routeWithoutBaseHref, + }; + } + }) + .catch((err) => { + errors.push( + `An error occurred while prerendering route '${route}'.\n\n${err.message ?? err.stack ?? err.code ?? err}`, + ); + void renderWorker.destroy(); + }); + + renderingPromises.push(renderResult); + } + + await Promise.all(renderingPromises); + } finally { + void renderWorker.destroy(); + } + + return { + errors, + output, + }; +} + +async function getAllRoutes( + workspaceRoot: string, + baseHref: string, + outputFilesForWorker: Record, + assetFilesForWorker: Record, + appShellOptions: AppShellOptions | undefined, + prerenderOptions: PrerenderOptions | undefined, + sourcemap: boolean, + outputMode: OutputMode | undefined, +): Promise<{ + serializedRouteTree: SerializableRouteTreeNode; + appShellRoute?: string; + errors: string[]; +}> { + const { routesFile, discoverRoutes } = prerenderOptions ?? {}; + const routes: WritableSerializableRouteTreeNode = []; + let appShellRoute: string | undefined; + + if (appShellOptions) { + appShellRoute = joinUrlParts(baseHref, appShellOptions.route); + + routes.push({ + renderMode: RouteRenderMode.Prerender, + route: appShellRoute, + }); + } + + if (routesFile) { + const routesFromFile = (await readFile(routesFile, 'utf8')).split(/\r?\n/); + for (const route of routesFromFile) { + routes.push({ + renderMode: RouteRenderMode.Prerender, + route: joinUrlParts(baseHref, route.trim()), + }); + } + } + + if (!discoverRoutes) { + return { errors: [], appShellRoute, serializedRouteTree: routes }; + } + + const workerExecArgv = [IMPORT_EXEC_ARGV]; + + if (sourcemap) { + workerExecArgv.push('--enable-source-maps'); + } + + const renderWorker = new WorkerPool({ + filename: require.resolve('./routes-extractor-worker'), + maxThreads: 1, + workerData: { + workspaceRoot, + outputFiles: outputFilesForWorker, + assetFiles: assetFilesForWorker, + outputMode, + hasSsrEntry: !!outputFilesForWorker['server.mjs'], + } as RoutesExtractorWorkerData, + execArgv: workerExecArgv, + }); + + try { + const { serializedRouteTree, appShellRoute, errors }: RoutersExtractorWorkerResult = + await renderWorker.run({}); + + if (!routes.length) { + return { errors, appShellRoute, serializedRouteTree }; + } + + // Merge the routing trees + const uniqueRoutes = new Map(); + for (const item of [...routes, ...serializedRouteTree]) { + if (!uniqueRoutes.has(item.route)) { + uniqueRoutes.set(item.route, item); + } + } + + return { errors, serializedRouteTree: Array.from(uniqueRoutes.values()) }; + } catch (err) { + assertIsError(err); + + return { + errors: [ + `An error occurred while extracting routes.\n\n${err.message ?? err.stack ?? err.code ?? err}`, + ], + serializedRouteTree: [], + }; + } finally { + void renderWorker.destroy(); + } +} diff --git a/packages/angular/build/src/utils/server-rendering/render-worker.ts b/packages/angular/build/src/utils/server-rendering/render-worker.ts new file mode 100644 index 000000000000..7ded0550b826 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/render-worker.ts @@ -0,0 +1,75 @@ +/** + * @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.dev/license + */ + +import { workerData } from 'node:worker_threads'; +import type { OutputMode } from '../../builders/application/schema'; +import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks'; +import { patchFetchToLoadInMemoryAssets } from './fetch-patch'; +import { DEFAULT_URL, launchServer } from './launch-server'; +import { loadEsmModuleFromMemory } from './load-esm-from-memory'; +import { generateRedirectStaticPage } from './utils'; + +export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData { + assetFiles: Record; + outputMode: OutputMode | undefined; + hasSsrEntry: boolean; +} + +export interface RenderOptions { + url: string; +} + +/** + * This is passed as workerData when setting up the worker via the `piscina` package. + */ +const { outputMode, hasSsrEntry } = workerData as { + outputMode: OutputMode | undefined; + hasSsrEntry: boolean; +}; + +let serverURL = DEFAULT_URL; + +/** + * Renders each route in routes and writes them to //index.html. + */ +async function renderPage({ url }: RenderOptions): Promise { + const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = + await loadEsmModuleFromMemory('./main.server.mjs'); + + const angularServerApp = getOrCreateAngularServerApp({ + allowStaticRouteRender: true, + }); + + const response = await angularServerApp.handle( + new Request(new URL(url, serverURL), { signal: AbortSignal.timeout(30_000) }), + ); + + if (!response) { + return null; + } + + const location = response.headers.get('Location'); + + return location ? generateRedirectStaticPage(location) : response.text(); +} + +async function initialize() { + // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, + // which must be processed by the runtime linker, even if they are not used. + await import('@angular/compiler'); + + if (outputMode !== undefined && hasSsrEntry) { + serverURL = await launchServer(); + } + + patchFetchToLoadInMemoryAssets(serverURL); + + return renderPage; +} + +export default initialize(); diff --git a/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts b/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts new file mode 100644 index 000000000000..423a71e83ba5 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.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.dev/license + */ + +import { workerData } from 'node:worker_threads'; +import { OutputMode } from '../../builders/application/schema'; +import { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks'; +import { patchFetchToLoadInMemoryAssets } from './fetch-patch'; +import { DEFAULT_URL, launchServer } from './launch-server'; +import { loadEsmModuleFromMemory } from './load-esm-from-memory'; +import { RoutersExtractorWorkerResult } from './models'; + +export interface ExtractRoutesWorkerData extends ESMInMemoryFileLoaderWorkerData { + outputMode: OutputMode | undefined; +} + +/** + * This is passed as workerData when setting up the worker via the `piscina` package. + */ +const { outputMode, hasSsrEntry } = workerData as { + outputMode: OutputMode | undefined; + hasSsrEntry: boolean; +}; + +/** Renders an application based on a provided options. */ +async function extractRoutes(): Promise { + // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, + // which must be processed by the runtime linker, even if they are not used. + await import('@angular/compiler'); + + const serverURL = outputMode !== undefined && hasSsrEntry ? await launchServer() : DEFAULT_URL; + + patchFetchToLoadInMemoryAssets(serverURL); + + const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = + await loadEsmModuleFromMemory('./main.server.mjs'); + + const { routeTree, appShellRoute, errors } = await extractRoutesAndCreateRouteTree({ + url: serverURL, + invokeGetPrerenderParams: outputMode !== undefined, + includePrerenderFallbackRoutes: outputMode === OutputMode.Server, + signal: AbortSignal.timeout(30_000), + }); + + return { + errors, + appShellRoute, + serializedRouteTree: routeTree.toObject(), + }; +} + +export default extractRoutes; diff --git a/packages/angular/build/src/utils/server-rendering/utils.ts b/packages/angular/build/src/utils/server-rendering/utils.ts new file mode 100644 index 000000000000..c740d4de06c4 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/utils.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.dev/license + */ + +import type { createRequestHandler } from '@angular/ssr'; +import type { createNodeRequestHandler } from '@angular/ssr/node' with { 'resolution-mode': 'import' }; + +export function isSsrNodeRequestHandler( + value: unknown, +): value is ReturnType { + return typeof value === 'function' && '__ng_node_request_handler__' in value; +} +export function isSsrRequestHandler( + value: unknown, +): value is ReturnType { + return typeof value === 'function' && '__ng_request_handler__' in value; +} + +/** + * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL. + * + * This function creates a simple HTML page that performs a redirect using a meta tag. + * It includes a fallback link in case the meta-refresh doesn't work. + * + * @param url - The URL to which the page should redirect. + * @returns The HTML content of the static redirect page. + */ +export function generateRedirectStaticPage(url: string): string { + return ` + + + + + Redirecting + + + +
Redirecting to ${url}
+ + +`.trim(); +} diff --git a/packages/angular/build/src/utils/service-worker.ts b/packages/angular/build/src/utils/service-worker.ts new file mode 100644 index 000000000000..1535684f635c --- /dev/null +++ b/packages/angular/build/src/utils/service-worker.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.dev/license + */ + +import type { + Config, + Filesystem, +} from '@angular/service-worker/config' with { 'resolution-mode': 'import' }; +import * as crypto from 'node:crypto'; +import { existsSync, promises as fsPromises } from 'node:fs'; +import * as path from 'node:path'; +import { BuildOutputFile, BuildOutputFileType } from '../tools/esbuild/bundler-context'; +import { BuildOutputAsset } from '../tools/esbuild/bundler-execution-result'; +import { assertIsError } from './error'; +import { toPosixPath } from './path'; + +class CliFilesystem implements Filesystem { + constructor( + private fs: typeof fsPromises, + private base: string, + ) {} + + list(dir: string): Promise { + return this._recursiveList(this._resolve(dir), []); + } + + read(file: string): Promise { + return this.fs.readFile(this._resolve(file), 'utf-8'); + } + + async hash(file: string): Promise { + return crypto + .createHash('sha1') + .update(await this.fs.readFile(this._resolve(file))) + .digest('hex'); + } + + write(_file: string, _content: string): never { + throw new Error('This should never happen.'); + } + + private _resolve(file: string): string { + return path.join(this.base, file); + } + + private async _recursiveList(dir: string, items: string[]): Promise { + const subdirectories = []; + 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('/' + toPosixPath(path.relative(this.base, entryPath))); + } else if (stats.isDirectory()) { + subdirectories.push(entryPath); + } + } + + for (const subdirectory of subdirectories) { + await this._recursiveList(subdirectory, items); + } + + return items; + } +} + +class ResultFilesystem implements Filesystem { + private readonly fileReaders = new Map Promise>(); + + constructor( + outputFiles: BuildOutputFile[], + assetFiles: { source: string; destination: string }[], + ) { + for (const file of outputFiles) { + if (file.type === BuildOutputFileType.Media || file.type === BuildOutputFileType.Browser) { + this.fileReaders.set('/' + toPosixPath(file.path), async () => file.contents); + } + } + for (const file of assetFiles) { + this.fileReaders.set('/' + toPosixPath(file.destination), () => + fsPromises.readFile(file.source), + ); + } + } + + async list(dir: string): Promise { + if (dir !== '/') { + throw new Error('Serviceworker manifest generator should only list files from root.'); + } + + return [...this.fileReaders.keys()]; + } + + async read(file: string): Promise { + const reader = this.fileReaders.get(file); + if (reader === undefined) { + throw new Error('File does not exist.'); + } + const contents = await reader(); + + return Buffer.from(contents.buffer, contents.byteOffset, contents.byteLength).toString('utf-8'); + } + + async hash(file: string): Promise { + const reader = this.fileReaders.get(file); + if (reader === undefined) { + throw new Error('File does not exist.'); + } + + return crypto + .createHash('sha1') + .update(await reader()) + .digest('hex'); + } + + write(): never { + throw new Error('Serviceworker manifest generator should not attempted to write.'); + } +} + +export async function augmentAppWithServiceWorker( + appRoot: string, + workspaceRoot: string, + outputPath: string, + baseHref: string, + ngswConfigPath?: string, + inputFileSystem = fsPromises, + outputFileSystem = fsPromises, +): Promise { + // Determine the configuration file path + 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 inputFileSystem.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 ${appRoot} folder. Either provide one or` + + ' disable Service Worker in the angular.json configuration file.', + ); + } else { + throw error; + } + } + + const result = await augmentAppWithServiceWorkerCore( + config, + new CliFilesystem(outputFileSystem, outputPath), + baseHref, + ); + + const copy = async (src: string, dest: string): Promise => { + const resolvedDest = path.join(outputPath, dest); + + return outputFileSystem.writeFile(resolvedDest, await inputFileSystem.readFile(src)); + }; + + await outputFileSystem.writeFile(path.join(outputPath, 'ngsw.json'), result.manifest); + + for (const { source, destination } of result.assetFiles) { + await copy(source, destination); + } +} + +// This is currently used by the esbuild-based builder +export async function augmentAppWithServiceWorkerEsbuild( + workspaceRoot: string, + configPath: string, + baseHref: string, + indexHtml: string | undefined, + outputFiles: BuildOutputFile[], + assetFiles: BuildOutputAsset[], +): Promise<{ manifest: string; assetFiles: BuildOutputAsset[] }> { + // Read the configuration file + let config: Config | undefined; + try { + const configurationData = await fsPromises.readFile(configPath, 'utf-8'); + config = JSON.parse(configurationData) as Config; + + if (indexHtml) { + config.index = indexHtml; + } + } 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; + } + } + + return augmentAppWithServiceWorkerCore( + config, + new ResultFilesystem(outputFiles, assetFiles), + baseHref, + ); +} + +export async function augmentAppWithServiceWorkerCore( + config: Config, + serviceWorkerFilesystem: Filesystem, + baseHref: string, +): Promise<{ manifest: string; assetFiles: { source: string; destination: string }[] }> { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { Generator } = (await import('@angular/service-worker/config' as any)) as typeof import( + '@angular/service-worker/config', + { with: { 'resolution-mode': 'import' } } + ); + + // Generate the manifest + const generator = new Generator(serviceWorkerFilesystem, baseHref); + const output = await generator.process(config); + + // Write the manifest + const manifest = JSON.stringify(output, null, 2); + + // Find the service worker package + const workerPath = require.resolve('@angular/service-worker/ngsw-worker.js'); + + const result = { + manifest, + // Main worker code + assetFiles: [{ source: workerPath, destination: 'ngsw-worker.js' }], + }; + + // If present, write the safety worker code + const safetyPath = path.join(path.dirname(workerPath), 'safety-worker.js'); + if (existsSync(safetyPath)) { + result.assetFiles.push({ source: safetyPath, destination: 'worker-basic.min.js' }); + result.assetFiles.push({ source: safetyPath, destination: 'safety-worker.js' }); + } + + return result; +} diff --git a/packages/angular/build/src/utils/stats-table.ts b/packages/angular/build/src/utils/stats-table.ts new file mode 100644 index 000000000000..b007fd7a4aa5 --- /dev/null +++ b/packages/angular/build/src/utils/stats-table.ts @@ -0,0 +1,290 @@ +/** + * @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.dev/license + */ + +import { stripVTControlCharacters } from 'node:util'; +import { BudgetCalculatorResult } from './bundle-calculator'; +import { colors as ansiColors } from './color'; +import { formatSize } from './format-bytes'; + +export type BundleStatsData = [ + files: string, + names: string, + rawSize: number | string, + estimatedTransferSize: number | string, +]; +export interface BundleStats { + initial: boolean; + stats: BundleStatsData; +} + +export function generateEsbuildBuildStatsTable( + [browserStats, serverStats]: [browserStats: BundleStats[], serverStats: BundleStats[]], + colors: boolean, + showTotalSize: boolean, + showEstimatedTransferSize: boolean, + budgetFailures?: BudgetCalculatorResult[], + verbose?: boolean, +): string { + const bundleInfo = generateBuildStatsData( + browserStats, + colors, + showTotalSize, + showEstimatedTransferSize, + budgetFailures, + verbose, + ); + + if (serverStats.length) { + const m = (x: string) => (colors ? ansiColors.magenta(x) : x); + if (browserStats.length) { + bundleInfo.unshift([m('Browser bundles')]); + // Add seperators between browser and server logs + bundleInfo.push([], []); + } + + bundleInfo.push( + [m('Server bundles')], + ...generateBuildStatsData(serverStats, colors, false, false, undefined, verbose), + ); + } + + return generateTableText(bundleInfo, colors); +} + +export function generateBuildStatsTable( + data: BundleStats[], + colors: boolean, + showTotalSize: boolean, + showEstimatedTransferSize: boolean, + budgetFailures?: BudgetCalculatorResult[], +): string { + const bundleInfo = generateBuildStatsData( + data, + colors, + showTotalSize, + showEstimatedTransferSize, + budgetFailures, + true, + ); + + return generateTableText(bundleInfo, colors); +} + +function generateBuildStatsData( + data: BundleStats[], + colors: boolean, + showTotalSize: boolean, + showEstimatedTransferSize: boolean, + budgetFailures?: BudgetCalculatorResult[], + verbose?: boolean, +): (string | number)[][] { + if (data.length === 0) { + return []; + } + + const g = (x: string) => (colors ? ansiColors.green(x) : x); + const c = (x: string) => (colors ? ansiColors.cyan(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 initialTotalRawSize = 0; + let changedLazyChunksCount = 0; + let initialTotalEstimatedTransferSize; + const maxLazyChunksWithoutBudgetFailures = 15; + + 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); + } + } + } + + // Sort descending by raw size + data.sort((a, b) => { + if (a.stats[2] > b.stats[2]) { + return -1; + } + + if (a.stats[2] < b.stats[2]) { + return 1; + } + + return 0; + }); + + for (const { initial, stats } of data) { + const [files, names, rawSize, estimatedTransferSize] = stats; + if ( + !initial && + !verbose && + changedLazyChunksStats.length >= maxLazyChunksWithoutBudgetFailures && + !budgets.has(names) && + !budgets.has(files) + ) { + // Limit the number of lazy chunks displayed in the stats table when there is no budget failure and not in verbose mode. + changedLazyChunksCount++; + continue; + } + + const getRawSizeColor = getSizeColor(names, files); + let data: BundleStatsData; + if (showEstimatedTransferSize) { + data = [ + g(files), + dim(names), + getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), + c( + typeof estimatedTransferSize === 'number' + ? formatSize(estimatedTransferSize) + : estimatedTransferSize, + ), + ]; + } else { + data = [ + g(files), + dim(names), + getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), + '', + ]; + } + + if (initial) { + changedEntryChunksStats.push(data); + if (typeof rawSize === 'number') { + initialTotalRawSize += rawSize; + } + if (showEstimatedTransferSize && typeof estimatedTransferSize === 'number') { + if (initialTotalEstimatedTransferSize === undefined) { + initialTotalEstimatedTransferSize = 0; + } + initialTotalEstimatedTransferSize += estimatedTransferSize; + } + } else { + changedLazyChunksStats.push(data); + changedLazyChunksCount++; + } + } + + const bundleInfo: (string | number)[][] = []; + const baseTitles = ['Names', 'Raw size']; + + if (showEstimatedTransferSize) { + baseTitles.push('Estimated transfer size'); + } + + // Entry chunks + if (changedEntryChunksStats.length) { + bundleInfo.push(['Initial chunk files', ...baseTitles].map(bold), ...changedEntryChunksStats); + + if (showTotalSize) { + 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)); + } + } + + // Seperator + if (changedEntryChunksStats.length && changedLazyChunksStats.length) { + bundleInfo.push([]); + } + + // Lazy chunks + if (changedLazyChunksStats.length) { + bundleInfo.push(['Lazy chunk files', ...baseTitles].map(bold), ...changedLazyChunksStats); + + if (changedLazyChunksCount > changedLazyChunksStats.length) { + bundleInfo.push([ + dim( + `...and ${changedLazyChunksCount - changedLazyChunksStats.length} more lazy chunks files. ` + + 'Use "--verbose" to show all the files.', + ), + ]); + } + } + + return bundleInfo; +} + +function generateTableText(bundleInfo: (string | number)[][], colors: boolean): string { + const skipText = (value: string) => value.includes('...and '); + const longest: number[] = []; + for (const item of bundleInfo) { + for (let i = 0; i < item.length; i++) { + if (item[i] === undefined) { + continue; + } + + const currentItem = item[i].toString(); + if (skipText(currentItem)) { + continue; + } + + const currentLongest = (longest[i] ??= 0); + const currentItemLength = stripVTControlCharacters(currentItem).length; + if (currentLongest < currentItemLength) { + longest[i] = currentItemLength; + } + } + } + + const seperator = colors ? ansiColors.dim(' | ') : ' | '; + const outputTable: string[] = []; + for (const item of bundleInfo) { + for (let i = 0; i < longest.length; i++) { + if (item[i] === undefined) { + continue; + } + + const currentItem = item[i].toString(); + if (skipText(currentItem)) { + continue; + } + + const currentItemLength = stripVTControlCharacters(currentItem).length; + const stringPad = ' '.repeat(longest[i] - currentItemLength); + // Values in columns at index 2 and 3 (Raw and Estimated sizes) are always right aligned. + item[i] = i >= 2 ? stringPad + currentItem : currentItem + stringPad; + } + + outputTable.push(item.join(seperator)); + } + + return outputTable.join('\n'); +} diff --git a/packages/angular/build/src/utils/supported-browsers.ts b/packages/angular/build/src/utils/supported-browsers.ts new file mode 100644 index 000000000000..d871d75789d3 --- /dev/null +++ b/packages/angular/build/src/utils/supported-browsers.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.dev/license + */ + +import browserslist from 'browserslist'; + +// The below is replaced by bazel `npm_package`. +const BASELINE_DATE = 'BASELINE-DATE-PLACEHOLDER'; + +export function getSupportedBrowsers( + projectRoot: string, + logger: { warn(message: string): void }, +): string[] { + // Read the browserslist configuration containing Angular's browser support policy. + const angularBrowserslist = browserslist(`baseline widely available on ${getBaselineDate()}`); + + // Use Angular's configuration as the default. + browserslist.defaults = angularBrowserslist; + + // Get the minimum set of browser versions supported by Angular. + const minimumBrowsers = new Set(angularBrowserslist); + + // 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 nonEs6Browsers: string[] = []; + const unsupportedBrowsers: string[] = []; + for (const browser of browsersFromConfigOrDefault) { + if (!browsersThatSupportEs6.has(browser)) { + // Any browser which does not support ES6 is explicitly ignored, as Angular will not build successfully. + browsersFromConfigOrDefault.delete(browser); + nonEs6Browsers.push(browser); + } else if (!minimumBrowsers.has(browser)) { + // Any other unsupported browser we will attempt to use, but provide no support for. + unsupportedBrowsers.push(browser); + } + } + + if (nonEs6Browsers.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:\n${nonEs6Browsers.join(', ')}`, + ); + } + + if (unsupportedBrowsers.length) { + logger.warn( + `One or more browsers which are configured in the project's Browserslist configuration ` + + "fall outside Angular's browser support for this version.\n" + + `Unsupported browsers:\n${unsupportedBrowsers.join(', ')}`, + ); + } + + return Array.from(browsersFromConfigOrDefault); +} + +function getBaselineDate(): string { + // Unlike `npm_package`, `ts_project` which is used to run unit tests does not support substitutions. + return BASELINE_DATE[0] === 'B' ? '2025-01-01' : BASELINE_DATE; +} diff --git a/packages/angular/build/src/utils/test-files.ts b/packages/angular/build/src/utils/test-files.ts new file mode 100644 index 000000000000..522bb1e778c0 --- /dev/null +++ b/packages/angular/build/src/utils/test-files.ts @@ -0,0 +1,51 @@ +/** + * @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.dev/license + */ + +import * as fs from 'node:fs/promises'; +import path from 'node:path'; +import { ResultFile } from '../builders/application/results'; +import { BuildOutputFileType } from '../tools/esbuild/bundler-context'; +import { emitFilesToDisk } from '../tools/esbuild/utils'; + +/** + * Writes a collection of build result files to a specified directory. + * This function handles both in-memory and on-disk files, creating subdirectories + * as needed. + * + * @param files A map of file paths to `ResultFile` objects, representing the build output. + * @param testDir The absolute path to the directory where the files should be written. + */ +export async function writeTestFiles( + files: Record, + testDir: string, +): Promise { + const directoryExists = new Set(); + // Writes the test related output files to disk and ensures the containing directories are present + await emitFilesToDisk(Object.entries(files), async ([filePath, file]) => { + if (file.type !== BuildOutputFileType.Browser && file.type !== BuildOutputFileType.Media) { + return; + } + + const fullFilePath = path.join(testDir, filePath); + + // Ensure output subdirectories exist + const fileBasePath = path.dirname(fullFilePath); + if (fileBasePath && !directoryExists.has(fileBasePath)) { + await fs.mkdir(fileBasePath, { recursive: true }); + directoryExists.add(fileBasePath); + } + + if (file.origin === 'memory') { + // Write file contents + await fs.writeFile(fullFilePath, file.contents); + } else { + // Copy file contents + await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE); + } + }); +} diff --git a/packages/angular/build/src/utils/tty.ts b/packages/angular/build/src/utils/tty.ts new file mode 100644 index 000000000000..0d669c0301e3 --- /dev/null +++ b/packages/angular/build/src/utils/tty.ts @@ -0,0 +1,22 @@ +/** + * @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.dev/license + */ + +function _isTruthy(value: undefined | string): boolean { + // Returns true if value is a string that is anything but 0 or false. + return value !== undefined && value !== '0' && value.toUpperCase() !== 'FALSE'; +} + +export function isTTY(): boolean { + // If we force TTY, we always return true. + const force = process.env['NG_FORCE_TTY']; + if (force !== undefined) { + return _isTruthy(force); + } + + return !!process.stdout.isTTY && !_isTruthy(process.env['CI']); +} diff --git a/packages/angular/build/src/utils/url.ts b/packages/angular/build/src/utils/url.ts new file mode 100644 index 000000000000..689eac37eab5 --- /dev/null +++ b/packages/angular/build/src/utils/url.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.dev/license + */ + +/** + * Removes the trailing slash from a URL if it exists. + * + * @param url - The URL string from which to remove the trailing slash. + * @returns The URL string without a trailing slash. + * + * @example + * ```js + * stripTrailingSlash('path/'); // 'path' + * stripTrailingSlash('/path'); // '/path' + * stripTrailingSlash('/'); // '/' + * stripTrailingSlash(''); // '' + * ``` + */ +export function stripTrailingSlash(url: string): string { + // Check if the last character of the URL is a slash + return url.length > 1 && url.at(-1) === '/' ? url.slice(0, -1) : url; +} + +/** + * Removes the leading slash from a URL if it exists. + * + * @param url - The URL string from which to remove the leading slash. + * @returns The URL string without a leading slash. + * + * @example + * ```js + * stripLeadingSlash('/path'); // 'path' + * stripLeadingSlash('/path/'); // 'path/' + * stripLeadingSlash('/'); // '/' + * stripLeadingSlash(''); // '' + * ``` + */ +export function stripLeadingSlash(url: string): string { + // Check if the first character of the URL is a slash + return url.length > 1 && url[0] === '/' ? url.slice(1) : url; +} + +/** + * Adds a leading slash to a URL if it does not already have one. + * + * @param url - The URL string to which the leading slash will be added. + * @returns The URL string with a leading slash. + * + * @example + * ```js + * addLeadingSlash('path'); // '/path' + * addLeadingSlash('/path'); // '/path' + * ``` + */ +export function addLeadingSlash(url: string): string { + // Check if the URL already starts with a slash + return url[0] === '/' ? url : `/${url}`; +} + +/** + * Adds a trailing slash to a URL if it does not already have one. + * + * @param url - The URL string to which the trailing slash will be added. + * @returns The URL string with a trailing slash. + * + * @example + * ```js + * addTrailingSlash('path'); // 'path/' + * addTrailingSlash('path/'); // 'path/' + * ``` + */ +export function addTrailingSlash(url: string): string { + // Check if the URL already end with a slash + return url.at(-1) === '/' ? url : `${url}/`; +} + +/** + * Joins URL parts into a single URL string. + * + * This function takes multiple URL segments, normalizes them by removing leading + * and trailing slashes where appropriate, and then joins them into a single URL. + * + * @param parts - The parts of the URL to join. Each part can be a string with or without slashes. + * @returns The joined URL string, with normalized slashes. + * + * @example + * ```js + * joinUrlParts('path/', '/to/resource'); // '/path/to/resource' + * joinUrlParts('/path/', 'to/resource'); // '/path/to/resource' + * joinUrlParts('http://localhost/path/', 'to/resource'); // 'http://localhost/path/to/resource' + * joinUrlParts('', ''); // '/' + * ``` + */ +export function joinUrlParts(...parts: string[]): string { + const normalizeParts: string[] = []; + for (const part of parts) { + if (part === '') { + // Skip any empty parts + continue; + } + + let normalizedPart = part; + if (part[0] === '/') { + normalizedPart = normalizedPart.slice(1); + } + if (part.at(-1) === '/') { + normalizedPart = normalizedPart.slice(0, -1); + } + if (normalizedPart !== '') { + normalizeParts.push(normalizedPart); + } + } + + const protocolMatch = normalizeParts.length && /^https?:\/\//.test(normalizeParts[0]); + const joinedParts = normalizeParts.join('/'); + + return protocolMatch ? joinedParts : addLeadingSlash(joinedParts); +} diff --git a/packages/angular/build/src/utils/version.ts b/packages/angular/build/src/utils/version.ts new file mode 100644 index 000000000000..51f493bfe993 --- /dev/null +++ b/packages/angular/build/src/utils/version.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.dev/license + */ + +/* eslint-disable no-console */ + +import { createRequire } from 'node:module'; +import { SemVer, satisfies } from 'semver'; + +export function assertCompatibleAngularVersion(projectRoot: string): void | never { + let angularPkgJson; + + // Create a custom require function for ESM compliance. + // NOTE: The trailing slash is significant. + const projectRequire = createRequire(projectRoot + '/'); + + try { + angularPkgJson = projectRequire('@angular/core/package.json'); + } catch { + console.error( + 'Error: It appears that "@angular/core" is missing as a dependency. Please ensure it is included in your project.', + ); + + process.exit(2); + } + + if (!angularPkgJson?.['version']) { + console.error( + 'Error: Unable to determine the versions of "@angular/core".\n' + + 'This likely indicates a corrupted local installation. Please try reinstalling your packages.', + ); + + process.exit(2); + } + + const supportedAngularSemver = '0.0.0-ANGULAR-FW-PEER-DEP'; + if (angularPkgJson['version'] === '0.0.0' || supportedAngularSemver.startsWith('0.0.0')) { + // Internal CLI and FW testing version. + return; + } + + const angularVersion = new SemVer(angularPkgJson['version']); + + if (!satisfies(angularVersion, supportedAngularSemver, { includePrerelease: true })) { + console.error( + `Error: The current version of "@angular/build" supports Angular versions ${supportedAngularSemver},\n` + + `but detected Angular version ${angularVersion} instead.\n` + + 'Please visit the link below to find instructions on how to update Angular.\nhttps://update.angular.dev/', + ); + + process.exit(3); + } +} diff --git a/packages/angular/build/src/utils/worker-pool.ts b/packages/angular/build/src/utils/worker-pool.ts new file mode 100644 index 000000000000..78db4302ef1a --- /dev/null +++ b/packages/angular/build/src/utils/worker-pool.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.dev/license + */ + +import { getCompileCacheDir } from 'node:module'; +import { Piscina } from 'piscina'; + +export type WorkerPoolOptions = ConstructorParameters[0]; + +export class WorkerPool extends Piscina { + constructor(options: WorkerPoolOptions) { + const piscinaOptions: WorkerPoolOptions = { + minThreads: 1, + idleTimeout: 4_000, + // Web containers do not support transferable objects with receiveOnMessagePort which + // is used when the Atomics based wait loop is enable. + atomics: process.versions.webcontainer ? 'disabled' : 'sync', + recordTiming: false, + ...options, + }; + + // Enable compile code caching if enabled for the main process (only exists on Node.js v22.8+). + // Skip if running inside Bazel via a RUNFILES environment variable check. The cache does not work + // well with Bazel's hermeticity requirements. + const compileCacheDirectory = process.env['RUNFILES'] ? undefined : getCompileCacheDir?.(); + if (compileCacheDirectory) { + if (typeof piscinaOptions.env === 'object') { + piscinaOptions.env['NODE_COMPILE_CACHE'] = compileCacheDirectory; + } else { + // Default behavior of `env` option is to copy current process values + piscinaOptions.env = { + ...process.env, + 'NODE_COMPILE_CACHE': compileCacheDirectory, + }; + } + } + + super(piscinaOptions); + } +} diff --git a/packages/angular/cli/BUILD.bazel b/packages/angular/cli/BUILD.bazel new file mode 100644 index 000000000000..abed616cd810 --- /dev/null +++ b/packages/angular/cli/BUILD.bazel @@ -0,0 +1,184 @@ +# 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.dev/license + +load("@npm//:defs.bzl", "npm_link_all_packages") +load("//tools:defaults.bzl", "jasmine_test", "ng_examples_db", "npm_package", "ts_project") +load("//tools:ng_cli_schema_generator.bzl", "cli_json_schema") +load("//tools:ts_json_schema.bzl", "ts_json_schema") + +licenses(["notice"]) + +package(default_visibility = ["//visibility:public"]) + +npm_link_all_packages() + +genrule( + name = "angular_best_practices", + srcs = [ + "//:node_modules/@angular/core/dir", + ], + outs = ["src/commands/mcp/resources/best-practices.md"], + cmd = """ + cp "$(location //:node_modules/@angular/core/dir)/resources/best-practices.md" $@ + """, +) + +RUNTIME_ASSETS = glob( + include = [ + "bin/**/*", + "src/**/*.md", + "src/**/*.json", + ], + exclude = [ + "lib/config/workspace-schema.json", + ], +) + [ + "//packages/angular/cli:lib/config/schema.json", + "//packages/angular/cli:lib/code-examples.db", + ":angular_best_practices", +] + +ts_project( + name = "angular-cli", + srcs = glob( + include = [ + "lib/**/*.ts", + "src/**/*.ts", + ], + exclude = [ + "**/*_spec.ts", + "**/testing/**", + ], + ) + [ + # These files are generated from the JSON schema + "//packages/angular/cli:lib/config/workspace-schema.ts", + "//packages/angular/cli:src/commands/update/schematic/schema.ts", + ], + data = RUNTIME_ASSETS, + deps = [ + ":node_modules/@angular-devkit/architect", + ":node_modules/@angular-devkit/core", + ":node_modules/@angular-devkit/schematics", + ":node_modules/@inquirer/prompts", + ":node_modules/@listr2/prompt-adapter-inquirer", + ":node_modules/@modelcontextprotocol/sdk", + ":node_modules/@yarnpkg/lockfile", + ":node_modules/algoliasearch", + ":node_modules/ini", + ":node_modules/jsonc-parser", + ":node_modules/listr2", + ":node_modules/npm-package-arg", + ":node_modules/pacote", + ":node_modules/parse5-html-rewriting-stream", + ":node_modules/resolve", + ":node_modules/yargs", + ":node_modules/zod", + "//:node_modules/@angular/core", + "//:node_modules/@types/ini", + "//:node_modules/@types/node", + "//:node_modules/@types/npm-package-arg", + "//:node_modules/@types/pacote", + "//:node_modules/@types/resolve", + "//:node_modules/@types/semver", + "//:node_modules/@types/yargs", + "//:node_modules/@types/yarnpkg__lockfile", + "//:node_modules/semver", + "//:node_modules/typescript", + ], +) + +ng_examples_db( + name = "cli_example_database", + srcs = glob( + include = [ + "lib/examples/**/*.md", + ], + ), + out = "lib/code-examples.db", + path = "packages/angular/cli/lib/examples", +) + +CLI_SCHEMA_DATA = [ + "//packages/angular/build:schemas", + "//packages/angular_devkit/build_angular:schemas", + "//packages/schematics/angular:schemas", +] + +cli_json_schema( + name = "cli_config_schema", + src = "lib/config/workspace-schema.json", + out = "lib/config/schema.json", + data = CLI_SCHEMA_DATA, +) + +ts_json_schema( + name = "cli_schema", + src = "lib/config/workspace-schema.json", + data = CLI_SCHEMA_DATA, +) + +ts_json_schema( + name = "update_schematic_schema", + src = "src/commands/update/schematic/schema.json", +) + +ts_project( + name = "angular-cli_test_lib", + testonly = True, + srcs = glob( + include = [ + "**/*_spec.ts", + "**/testing/**", + ], + exclude = [ + # NB: we need to exclude the nested node_modules that is laid out by yarn workspaces + "node_modules/**", + ], + ), + deps = [ + ":angular-cli", + ":node_modules/@angular-devkit/core", + ":node_modules/@angular-devkit/schematics", + ":node_modules/@modelcontextprotocol/sdk", + ":node_modules/yargs", + "//:node_modules/@types/semver", + "//:node_modules/@types/yargs", + "//:node_modules/semver", + "//:node_modules/typescript", + ], +) + +jasmine_test( + name = "test", + data = [":angular-cli_test_lib"], +) + +genrule( + name = "license", + srcs = ["//:LICENSE"], + outs = ["LICENSE"], + cmd = "cp $(execpath //:LICENSE) $@", +) + +npm_package( + name = "pkg", + pkg_deps = [ + "//packages/angular_devkit/architect:package.json", + "//packages/angular_devkit/build_angular:package.json", + "//packages/angular_devkit/build_webpack:package.json", + "//packages/angular_devkit/core:package.json", + "//packages/angular_devkit/schematics:package.json", + "//packages/schematics/angular:package.json", + ], + stamp_files = [ + "src/utilities/version.js", + ], + tags = ["release-package"], + deps = RUNTIME_ASSETS + [ + ":README.md", + ":angular-cli", + ":license", + ], +) diff --git a/packages/angular/cli/README.md b/packages/angular/cli/README.md new file mode 100644 index 000000000000..4fa87391f04c --- /dev/null +++ b/packages/angular/cli/README.md @@ -0,0 +1,5 @@ +# Angular CLI - The CLI tool for Angular. + +The sources for this package are in the [Angular CLI](https://github.com/angular/angular-cli) repository. Please file issues and pull requests against that repository. + +Usage information and reference details can be found in repository [README](https://github.com/angular/angular-cli/blob/main/README.md) file. diff --git a/packages/angular/cli/bin/bootstrap.js b/packages/angular/cli/bin/bootstrap.js new file mode 100644 index 000000000000..18d1ed73160c --- /dev/null +++ b/packages/angular/cli/bin/bootstrap.js @@ -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.dev/license + */ + +/** + * @fileoverview + * + * This file is used to bootstrap the CLI process by dynamically importing the main initialization code. + * This is done to allow the main bin file (`ng`) to remain CommonJS so that older versions of Node.js + * can be checked and validated prior to the execution of the CLI. This separate bootstrap file is + * needed to allow the use of a dynamic import expression without crashing older versions of Node.js that + * do not support dynamic import expressions and would otherwise throw a syntax error. This bootstrap file + * is required from the main bin file only after the Node.js version is determined to be in the supported + * range. + */ + +// Enable on-disk code caching if available (Node.js 22.8+) +// Skip if running inside Bazel via a RUNFILES environment variable check and no explicit cache +// location defined. The default cache location does not work well with Bazel's hermeticity requirements. +if (!process.env['RUNFILES'] || process.env['NODE_COMPILE_CACHE']) { + try { + const { enableCompileCache } = require('node:module'); + + enableCompileCache?.(); + } catch {} +} + +// Initialize the Angular CLI +void import('../lib/init.js'); diff --git a/packages/angular/cli/bin/ng.js b/packages/angular/cli/bin/ng.js new file mode 100755 index 000000000000..e0f5eb36a2ef --- /dev/null +++ b/packages/angular/cli/bin/ng.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node +/** + * @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.dev/license + */ + +/* eslint-disable no-console */ +/* eslint-disable import/no-unassigned-import */ +'use strict'; + +const path = require('path'); + +// Error if the external CLI appears to be used inside a google3 context. +if (process.cwd().split(path.sep).includes('google3')) { + console.error( + 'This is the external Angular CLI, but you appear to be running in google3. There is a separate, internal version of the CLI which should be used instead. See http://go/angular/cli.', + ); + process.exit(); +} + +// Provide a title to the process in `ps`. +// Due to an obscure Mac bug, do not start this title with any symbol. +try { + process.title = 'ng ' + Array.from(process.argv).slice(2).join(' '); +} catch (_) { + // If an error happened above, use the most basic title. + process.title = 'ng'; +} + +const rawCommandName = process.argv[2]; +if (rawCommandName === '--get-yargs-completions' || rawCommandName === 'completion') { + // Skip Node.js supported checks when running ng completion. + // A warning at this stage could cause a broken source action (`source <(ng completion script)`) when in the shell init script. + require('./bootstrap'); + + return; +} + +// This node version check ensures that extremely old versions of node are not used. +// These may not support ES2015 features such as const/let/async/await/etc. +// These would then crash with a hard to diagnose error message. +const [major, minor] = process.versions.node.split('.', 2).map((part) => Number(part)); + +if (major % 2 === 1) { + // Allow new odd numbered releases with a warning (currently v17+) + console.warn( + 'Node.js version ' + + process.version + + ' detected.\n' + + 'Odd numbered Node.js versions will not enter LTS status and should not be used for production.' + + ' For more information, please see https://nodejs.org/en/about/previous-releases/.', + ); + + require('./bootstrap'); +} else if (major < 20 || (major === 20 && minor < 19) || (major === 22 && minor < 12)) { + // Error and exit if less than 20.19 or 22.12 + console.error( + 'Node.js version ' + + process.version + + ' detected.\n' + + 'The Angular CLI requires a minimum Node.js version of v20.19 or v22.12.\n\n' + + 'Please update your Node.js version or visit https://nodejs.org/ for additional instructions.\n', + ); + + process.exitCode = 3; +} else { + require('./bootstrap'); +} diff --git a/packages/angular/cli/bin/package.json b/packages/angular/cli/bin/package.json new file mode 100644 index 000000000000..5bbefffbabee --- /dev/null +++ b/packages/angular/cli/bin/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/angular/cli/lib/cli/index.ts b/packages/angular/cli/lib/cli/index.ts new file mode 100644 index 000000000000..ac7591e43630 --- /dev/null +++ b/packages/angular/cli/lib/cli/index.ts @@ -0,0 +1,117 @@ +/** + * @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.dev/license + */ + +import { logging } from '@angular-devkit/core'; +import { format, stripVTControlCharacters } from 'node:util'; +import { CommandModuleError } from '../../src/command-builder/command-module'; +import { runCommand } from '../../src/command-builder/command-runner'; +import { colors, supportColor } from '../../src/utilities/color'; +import { ngDebug } from '../../src/utilities/environment-options'; +import { writeErrorToLogFile } from '../../src/utilities/log-file'; + +export { VERSION } from '../../src/utilities/version'; + +const MIN_NODEJS_VERSION = [20, 19] as const; + +/* eslint-disable no-console */ +export default async function (options: { cliArgs: string[] }) { + // This node version check ensures that the requirements of the project instance of the CLI are met + const [major, minor] = process.versions.node.split('.').map((part) => Number(part)); + if ( + major < MIN_NODEJS_VERSION[0] || + (major === MIN_NODEJS_VERSION[0] && minor < MIN_NODEJS_VERSION[1]) + ) { + process.stderr.write( + `Node.js version ${process.version} detected.\n` + + `The Angular CLI requires a minimum of v${MIN_NODEJS_VERSION[0]}.${MIN_NODEJS_VERSION[1]}.\n\n` + + 'Please update your Node.js version or visit https://nodejs.org/ for additional instructions.\n', + ); + + return 3; + } + + const colorLevels: Record string> = { + info: (s) => s, + debug: (s) => s, + warn: (s) => colors.bold(colors.yellow(s)), + error: (s) => colors.bold(colors.red(s)), + fatal: (s) => colors.bold(colors.red(s)), + }; + const logger = new logging.IndentLogger('cli-main-logger'); + const logInfo = console.log; + const logError = console.error; + const useColor = supportColor(); + + const loggerFinished = logger.forEach((entry) => { + if (!ngDebug && entry.level === 'debug') { + return; + } + + const color = useColor ? colorLevels[entry.level] : stripVTControlCharacters; + const message = color(entry.message); + + switch (entry.level) { + case 'warn': + case 'fatal': + case 'error': + logError(message); + break; + default: + logInfo(message); + break; + } + }); + + // Redirect console to logger + console.info = console.log = function (...args) { + logger.info(format(...args)); + }; + console.warn = function (...args) { + logger.warn(format(...args)); + }; + console.error = function (...args) { + logger.error(format(...args)); + }; + + try { + return await runCommand(options.cliArgs, logger); + } catch (err) { + if (err instanceof CommandModuleError) { + logger.fatal(`Error: ${err.message}`); + } else if (err instanceof Error) { + try { + const logPath = writeErrorToLogFile(err); + logger.fatal( + `An unhandled exception occurred: ${err.message}\n` + + `See "${logPath}" for further details.`, + ); + } catch (e) { + logger.fatal( + `An unhandled exception occurred: ${err.message}\n` + + `Fatal error writing debug log file: ${e}`, + ); + if (err.stack) { + logger.fatal(err.stack); + } + } + + return 127; + } else if (typeof err === 'string') { + logger.fatal(err); + } else if (typeof err === 'number') { + // Log nothing. + } else { + logger.fatal(`An unexpected error occurred: ${err}`); + } + + return 1; + } finally { + logger.complete(); + await loggerFinished; + } +} diff --git a/packages/angular/cli/lib/config/workspace-schema.json b/packages/angular/cli/lib/config/workspace-schema.json new file mode 100644 index 000000000000..3fede1746559 --- /dev/null +++ b/packages/angular/cli/lib/config/workspace-schema.json @@ -0,0 +1,886 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "ng-cli://config/schema.json", + "title": "Angular CLI Workspace Configuration", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "version": { + "$ref": "#/definitions/fileVersion" + }, + "cli": { + "$ref": "#/definitions/cliOptions" + }, + "schematics": { + "$ref": "#/definitions/schematicOptions" + }, + "newProjectRoot": { + "type": "string", + "description": "Path where new projects will be created." + }, + "projects": { + "type": "object", + "patternProperties": { + "^(?:@[a-zA-Z0-9._-]+/)?[a-zA-Z0-9._-]+$": { + "$ref": "#/definitions/project" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": ["version"], + "definitions": { + "cliOptions": { + "type": "object", + "properties": { + "schematicCollections": { + "type": "array", + "description": "The list of schematic collections to use.", + "items": { + "type": "string", + "uniqueItems": true + } + }, + "packageManager": { + "description": "Specify which package manager tool to use.", + "type": "string", + "enum": ["npm", "yarn", "pnpm", "bun"] + }, + "warnings": { + "description": "Control CLI specific console warnings", + "type": "object", + "properties": { + "versionMismatch": { + "description": "Show a warning when the global version is newer than the local one.", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "analytics": { + "type": ["boolean", "string"], + "description": "Share pseudonymous usage data with the Angular Team at Google." + }, + "cache": { + "description": "Control disk cache.", + "type": "object", + "properties": { + "environment": { + "description": "Configure in which environment disk cache is enabled.", + "type": "string", + "enum": ["local", "ci", "all"] + }, + "enabled": { + "description": "Configure whether disk caching is enabled.", + "type": "boolean" + }, + "path": { + "description": "Cache base path.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "cliGlobalOptions": { + "type": "object", + "properties": { + "schematicCollections": { + "type": "array", + "description": "The list of schematic collections to use.", + "items": { + "type": "string", + "uniqueItems": true + } + }, + "packageManager": { + "description": "Specify which package manager tool to use.", + "type": "string", + "enum": ["npm", "yarn", "pnpm", "bun"] + }, + "warnings": { + "description": "Control CLI specific console warnings", + "type": "object", + "properties": { + "versionMismatch": { + "description": "Show a warning when the global version is newer than the local one.", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "analytics": { + "type": ["boolean", "string"], + "description": "Share pseudonymous usage data with the Angular Team at Google." + }, + "completion": { + "type": "object", + "description": "Angular CLI completion settings.", + "properties": { + "prompted": { + "type": "boolean", + "description": "Whether the user has been prompted to add completion command prompt." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "schematicOptions": { + "type": "object", + "properties": { + "@schematics/angular:application": { + "$ref": "../../../../schematics/angular/application/schema.json" + }, + "@schematics/angular:class": { + "$ref": "../../../../schematics/angular/class/schema.json" + }, + "@schematics/angular:component": { + "$ref": "../../../../schematics/angular/component/schema.json" + }, + "@schematics/angular:directive": { + "$ref": "../../../../schematics/angular/directive/schema.json" + }, + "@schematics/angular:enum": { + "$ref": "../../../../schematics/angular/enum/schema.json" + }, + "@schematics/angular:guard": { + "$ref": "../../../../schematics/angular/guard/schema.json" + }, + "@schematics/angular:interceptor": { + "$ref": "../../../../schematics/angular/interceptor/schema.json" + }, + "@schematics/angular:interface": { + "$ref": "../../../../schematics/angular/interface/schema.json" + }, + "@schematics/angular:library": { + "$ref": "../../../../schematics/angular/library/schema.json" + }, + "@schematics/angular:pipe": { + "$ref": "../../../../schematics/angular/pipe/schema.json" + }, + "@schematics/angular:ng-new": { + "$ref": "../../../../schematics/angular/ng-new/schema.json" + }, + "@schematics/angular:resolver": { + "$ref": "../../../../schematics/angular/resolver/schema.json" + }, + "@schematics/angular:service": { + "$ref": "../../../../schematics/angular/service/schema.json" + }, + "@schematics/angular:web-worker": { + "$ref": "../../../../schematics/angular/web-worker/schema.json" + } + }, + "additionalProperties": true + }, + "fileVersion": { + "type": "integer", + "description": "File format version", + "minimum": 1 + }, + "project": { + "type": "object", + "properties": { + "cli": { + "schematicCollections": { + "type": "array", + "description": "The list of schematic collections to use.", + "items": { + "type": "string", + "uniqueItems": true + } + } + }, + "schematics": { + "$ref": "#/definitions/schematicOptions" + }, + "prefix": { + "type": "string", + "format": "html-selector", + "description": "The prefix to apply to generated selectors." + }, + "root": { + "type": "string", + "description": "Root of the project files." + }, + "i18n": { + "$ref": "#/definitions/project/definitions/i18n" + }, + "sourceRoot": { + "type": "string", + "description": "The root of the source files, assets and index.html file structure." + }, + "projectType": { + "type": "string", + "description": "Project type.", + "enum": ["application", "library"] + }, + "architect": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/project/definitions/target" + } + }, + "targets": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/project/definitions/target" + } + } + }, + "required": ["root", "projectType"], + "anyOf": [ + { + "required": ["architect"], + "not": { + "required": ["targets"] + } + }, + { + "required": ["targets"], + "not": { + "required": ["architect"] + } + }, + { + "not": { + "required": ["targets", "architect"] + } + } + ], + "additionalProperties": false, + "patternProperties": { + "^[a-z]{1,3}-.*": {} + }, + "definitions": { + "i18n": { + "description": "Project i18n options", + "type": "object", + "properties": { + "sourceLocale": { + "oneOf": [ + { + "type": "string", + "description": "Specifies the source locale of the application.", + "default": "en-US", + "$comment": "IETF BCP 47 language tag (simplified)", + "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})+)?$" + }, + { + "type": "object", + "description": "Localization options to use for the source locale.", + "properties": { + "code": { + "type": "string", + "description": "Specifies the locale code of the source locale.", + "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})+)?$" + }, + "baseHref": { + "type": "string", + "description": "Specifies the HTML base HREF for the locale. Defaults to the locale code if not provided." + }, + "subPath": { + "type": "string", + "description": "Defines the subpath for accessing this locale. It serves as the HTML base HREF and the directory name for the output. Defaults to the locale code if not specified.", + "pattern": "^[\\w-]*$" + } + }, + "anyOf": [ + { + "required": ["subPath"], + "not": { + "required": ["baseHref"] + } + }, + { + "required": ["baseHref"], + "not": { + "required": ["subPath"] + } + }, + { + "not": { + "required": ["baseHref", "subPath"] + } + } + ], + "additionalProperties": false + } + ] + }, + "locales": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[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})+)?$": { + "oneOf": [ + { + "type": "string", + "description": "Localization file to use for i18n." + }, + { + "type": "array", + "description": "Localization files to use for i18n.", + "items": { + "type": "string", + "uniqueItems": true + } + }, + { + "type": "object", + "description": "Localization options to use for the locale.", + "properties": { + "translation": { + "oneOf": [ + { + "type": "string", + "description": "Localization file to use for i18n." + }, + { + "type": "array", + "description": "Localization files to use for i18n.", + "items": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "baseHref": { + "type": "string", + "description": "Specifies the HTML base HREF for the locale. Defaults to the locale code if not provided." + }, + "subPath": { + "type": "string", + "description": "Defines the URL segment for accessing this locale. It serves as the HTML base HREF and the directory name for the output. Defaults to the locale code if not specified.", + "pattern": "^[\\w-]*$" + } + }, + "anyOf": [ + { + "required": ["subPath"], + "not": { + "required": ["baseHref"] + } + }, + { + "required": ["baseHref"], + "not": { + "required": ["subPath"] + } + }, + { + "not": { + "required": ["baseHref", "subPath"] + } + } + ], + "additionalProperties": false + } + ] + } + } + } + }, + "additionalProperties": false + }, + "target": { + "oneOf": [ + { + "$comment": "Extendable target with custom builder", + "type": "object", + "properties": { + "builder": { + "type": "string", + "description": "The builder used for this package.", + "not": { + "enum": [ + "@angular/build:application", + "@angular/build:dev-server", + "@angular/build:extract-i18n", + "@angular/build:karma", + "@angular/build:ng-packagr", + "@angular/build:unit-test", + "@angular-devkit/build-angular:application", + "@angular-devkit/build-angular:app-shell", + "@angular-devkit/build-angular:browser", + "@angular-devkit/build-angular:browser-esbuild", + "@angular-devkit/build-angular:dev-server", + "@angular-devkit/build-angular:extract-i18n", + "@angular-devkit/build-angular:karma", + "@angular-devkit/build-angular:ng-packagr", + "@angular-devkit/build-angular:prerender", + "@angular-devkit/build-angular:jest", + "@angular-devkit/build-angular:web-test-runner", + "@angular-devkit/build-angular:server", + "@angular-devkit/build-angular:ssr-dev-server" + ] + } + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "type": "object" + }, + "configurations": { + "type": "object", + "description": "A map of alternative target options.", + "additionalProperties": { + "type": "object" + } + } + }, + "additionalProperties": false, + "required": ["builder"] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:application" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/application/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/application/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:application" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/application/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/application/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:app-shell" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/app-shell/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/app-shell/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:browser" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/browser/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/browser/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:browser-esbuild" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/browser-esbuild/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/browser-esbuild/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:dev-server" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/dev-server/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/dev-server/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:dev-server" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/dev-server/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/dev-server/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:extract-i18n" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/extract-i18n/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/extract-i18n/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:extract-i18n" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/extract-i18n/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/extract-i18n/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:unit-test" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/unit-test/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/unit-test/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:karma" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/karma/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/karma/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:karma" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/karma/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/karma/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:jest" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/jest/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/jest/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:web-test-runner" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/web-test-runner/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/web-test-runner/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:prerender" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/prerender/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/prerender/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:ssr-dev-server" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/ssr-dev-server/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/ssr-dev-server/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:server" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/server/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/server/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular-devkit/build-angular:ng-packagr" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/ng-packagr/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular_devkit/build_angular/src/builders/ng-packagr/schema.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:ng-packagr" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/ng-packagr/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/ng-packagr/schema.json" + } + } + } + } + ] + } + } + }, + "global": { + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "version": { + "$ref": "#/definitions/fileVersion" + }, + "cli": { + "$ref": "#/definitions/cliGlobalOptions" + }, + "schematics": { + "$ref": "#/definitions/schematicOptions" + } + }, + "required": ["version"] + } + } +} diff --git a/packages/angular/cli/lib/examples/if-block.md b/packages/angular/cli/lib/examples/if-block.md new file mode 100644 index 000000000000..806e3d05516c --- /dev/null +++ b/packages/angular/cli/lib/examples/if-block.md @@ -0,0 +1,85 @@ +--- +title: 'Using the @if Built-in Control Flow Block' +summary: 'Demonstrates how to use the @if built-in control flow block to conditionally render content in an Angular template based on a boolean expression.' +keywords: + - '@if' + - 'control flow' + - 'conditional rendering' + - 'template syntax' +related_concepts: + - '@else' + - '@else if' + - 'signals' +related_tools: + - 'modernize' +--- + +## Purpose + +The purpose of this pattern is to create dynamic user interfaces by controlling which elements are rendered to the DOM based on the application's state. This is a fundamental technique for building responsive and interactive components. + +## When to Use + +Use the `@if` block as the modern, preferred alternative to the `*ngIf` directive for all conditional rendering. It offers better type-checking and a cleaner, more intuitive syntax within the template. + +## Key Concepts + +- **`@if` block:** The primary syntax for conditional rendering in modern Angular templates. It evaluates a boolean expression and renders the content within its block if the expression is true. + +## Example Files + +### `conditional-content.component.ts` + +This is a self-contained standalone component that demonstrates the `@if` block with an optional `@else` block. + +```typescript +import { Component, signal } from '@angular/core'; + +@Component({ + selector: 'app-conditional-content', + template: ` + + + @if (isVisible()) { +
This content is conditionally displayed.
+ } @else { +
The content is hidden. Click the button to show it.
+ } + `, +}) +export class ConditionalContentComponent { + protected readonly isVisible = signal(true); + + toggleVisibility(): void { + this.isVisible.update((v) => !v); + } +} +``` + +## Usage Notes + +- The expression inside the `@if ()` block must evaluate to a boolean. +- This example uses a signal, which is a common pattern, but any boolean property or method call from the component can be used. +- The `@else` block is optional and is rendered when the `@if` condition is `false`. + +## How to Use This Example + +### 1. Import the Component + +In a standalone architecture, import the component into the `imports` array of the parent component where you want to use it. + +```typescript +// in app.component.ts +import { Component } from '@angular/core'; +import { ConditionalContentComponent } from './conditional-content.component'; + +@Component({ + selector: 'app-root', + imports: [ConditionalContentComponent], + template: ` +

My Application

+ + `, +}) +export class AppComponent {} +``` diff --git a/packages/angular/cli/lib/init.ts b/packages/angular/cli/lib/init.ts new file mode 100644 index 000000000000..cd324b6df69b --- /dev/null +++ b/packages/angular/cli/lib/init.ts @@ -0,0 +1,147 @@ +/** + * @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.dev/license + */ + +import { readFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import * as path from 'node:path'; +import { SemVer, major } from 'semver'; +import { colors } from '../src/utilities/color'; +import { isWarningEnabled } from '../src/utilities/config'; +import { disableVersionCheck } from '../src/utilities/environment-options'; +import { VERSION } from '../src/utilities/version'; + +/** + * Angular CLI versions prior to v14 may not exit correctly if not forcibly exited + * via `process.exit()`. When bootstrapping, `forceExit` will be set to `true` + * if the local CLI version is less than v14 to prevent the CLI from hanging on + * exit in those cases. + */ +let forceExit = false; + +(async (): Promise => { + /** + * Disable Browserslist old data warning as otherwise with every release we'd need to update this dependency + * which is cumbersome considering we pin versions and the warning is not user actionable. + * `Browserslist: caniuse-lite is outdated. Please run next command `npm update` + * See: https://github.com/browserslist/browserslist/blob/819c4337456996d19db6ba953014579329e9c6e1/node.js#L324 + */ + process.env.BROWSERSLIST_IGNORE_OLD_DATA = '1'; + const rawCommandName = process.argv[2]; + + /** + * Disable CLI version mismatch checks and forces usage of the invoked CLI + * instead of invoking the local installed version. + * + * When running `ng new` always favor the global version. As in some + * cases orphan `node_modules` would cause the non global CLI to be used. + * @see: https://github.com/angular/angular-cli/issues/14603 + */ + if (disableVersionCheck || rawCommandName === 'new') { + return (await import('./cli')).default; + } + + let cli; + + try { + // No error implies a projectLocalCli, which will load whatever + // version of ng-cli you have installed in a local package.json + const cwdRequire = createRequire(process.cwd() + '/'); + const projectLocalCli = cwdRequire.resolve('@angular/cli'); + cli = await import(projectLocalCli); + + const globalVersion = new SemVer(VERSION.full); + + // Older versions might not have the VERSION export + let localVersion = cli.VERSION?.full; + if (!localVersion) { + try { + const localPackageJson = await readFile( + path.join(path.dirname(projectLocalCli), '../../package.json'), + 'utf-8', + ); + localVersion = (JSON.parse(localPackageJson) as { version: string }).version; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Version mismatch check skipped. Unable to retrieve local version: ' + error); + } + } + + // Ensure older versions of the CLI fully exit + const localMajorVersion = major(localVersion); + if (localMajorVersion > 0 && localMajorVersion < 14) { + forceExit = true; + + // Versions prior to 14 didn't implement completion command. + if (rawCommandName === 'completion') { + return null; + } + } + + let isGlobalGreater = false; + try { + isGlobalGreater = localVersion > 0 && globalVersion.compare(localVersion) > 0; + } catch (error) { + // eslint-disable-next-line no-console + console.error('Version mismatch check skipped. Unable to compare local version: ' + error); + } + + // When using the completion command, don't show the warning as otherwise this will break completion. + if ( + isGlobalGreater && + rawCommandName !== '--get-yargs-completions' && + rawCommandName !== 'completion' + ) { + // If using the update command and the global version is greater, use the newer update command + // This allows improvements in update to be used in older versions that do not have bootstrapping + if ( + rawCommandName === 'update' && + cli.VERSION && + cli.VERSION.major - globalVersion.major <= 1 + ) { + cli = await import('./cli'); + } else if (await isWarningEnabled('versionMismatch')) { + // Otherwise, use local version and warn if global is newer than local + const warning = + `Your global Angular CLI version (${globalVersion}) is greater than your local ` + + `version (${localVersion}). The local Angular CLI version is used.\n\n` + + 'To disable this warning use "ng config -g cli.warnings.versionMismatch false".'; + + // eslint-disable-next-line no-console + console.error(colors.yellow(warning)); + } + } + } catch { + // If there is an error, resolve could not find the ng-cli + // library from a package.json. Instead, include it from a relative + // path to this script file (which is likely a globally installed + // npm package). Most common cause for hitting this is `ng new` + cli = await import('./cli'); + } + + if ('default' in cli) { + cli = cli['default']; + } + + return cli; +})() + .then((cli) => + cli?.({ + cliArgs: process.argv.slice(2), + }), + ) + .then((exitCode = 0) => { + if (forceExit) { + process.exit(exitCode); + } + process.exitCode = exitCode; + }) + .catch((err: Error) => { + // eslint-disable-next-line no-console + console.error('Unknown error: ' + err.toString()); + process.exit(127); + }); diff --git a/packages/angular/cli/package.json b/packages/angular/cli/package.json new file mode 100644 index 000000000000..1fb8b671f261 --- /dev/null +++ b/packages/angular/cli/package.json @@ -0,0 +1,58 @@ +{ + "name": "@angular/cli", + "version": "0.0.0-PLACEHOLDER", + "description": "CLI tool for Angular", + "main": "lib/cli/index.js", + "bin": { + "ng": "./bin/ng.js" + }, + "keywords": [ + "angular", + "angular-cli", + "Angular CLI" + ], + "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": { + "@angular-devkit/architect": "workspace:0.0.0-EXPERIMENTAL-PLACEHOLDER", + "@angular-devkit/core": "workspace:0.0.0-PLACEHOLDER", + "@angular-devkit/schematics": "workspace:0.0.0-PLACEHOLDER", + "@inquirer/prompts": "7.10.1", + "@listr2/prompt-adapter-inquirer": "3.0.5", + "@modelcontextprotocol/sdk": "1.25.0", + "@schematics/angular": "workspace:0.0.0-PLACEHOLDER", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.46.0", + "ini": "6.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "npm-package-arg": "13.0.2", + "pacote": "21.0.4", + "parse5-html-rewriting-stream": "8.0.0", + "resolve": "1.22.11", + "semver": "7.7.3", + "yargs": "18.0.0", + "zod": "4.2.1" + }, + "ng-update": { + "migrations": "@schematics/angular/migrations/migration-collection.json", + "packageGroup": { + "@angular/cli": "0.0.0-PLACEHOLDER", + "@angular/build": "0.0.0-PLACEHOLDER", + "@angular/ssr": "0.0.0-PLACEHOLDER", + "@angular-devkit/architect": "0.0.0-EXPERIMENTAL-PLACEHOLDER", + "@angular-devkit/build-angular": "0.0.0-PLACEHOLDER", + "@angular-devkit/build-webpack": "0.0.0-EXPERIMENTAL-PLACEHOLDER", + "@angular-devkit/core": "0.0.0-PLACEHOLDER", + "@angular-devkit/schematics": "0.0.0-PLACEHOLDER" + } + } +} diff --git a/packages/angular/cli/src/analytics/analytics-collector.ts b/packages/angular/cli/src/analytics/analytics-collector.ts new file mode 100644 index 000000000000..052bc5cbe74c --- /dev/null +++ b/packages/angular/cli/src/analytics/analytics-collector.ts @@ -0,0 +1,207 @@ +/** + * @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.dev/license + */ + +import { randomUUID } from 'node:crypto'; +import * as https from 'node:https'; +import * as os from 'node:os'; +import * as querystring from 'node:querystring'; +import * as semver from 'semver'; +import type { CommandContext } from '../command-builder/command-module'; +import { ngDebug } from '../utilities/environment-options'; +import { assertIsError } from '../utilities/error'; +import { VERSION } from '../utilities/version'; +import { + EventCustomDimension, + EventCustomMetric, + PrimitiveTypes, + RequestParameter, + UserCustomDimension, +} from './analytics-parameters'; + +const TRACKING_ID_PROD = 'G-VETNJBW8L4'; +const TRACKING_ID_STAGING = 'G-TBMPRL1BTM'; + +export class AnalyticsCollector { + private trackingEventsQueue: Record[] | undefined; + private readonly requestParameterStringified: string; + private readonly userParameters: Record; + + constructor( + private context: CommandContext, + userId: string, + ) { + const requestParameters: Partial> = { + [RequestParameter.ProtocolVersion]: 2, + [RequestParameter.ClientId]: userId, + [RequestParameter.UserId]: userId, + [RequestParameter.TrackingId]: + /^\d+\.\d+\.\d+$/.test(VERSION.full) && VERSION.full !== '0.0.0' + ? TRACKING_ID_PROD + : TRACKING_ID_STAGING, + + // Built-in user properties + [RequestParameter.SessionId]: randomUUID(), + [RequestParameter.UserAgentArchitecture]: os.arch(), + [RequestParameter.UserAgentPlatform]: os.platform(), + [RequestParameter.UserAgentPlatformVersion]: os.release(), + [RequestParameter.UserAgentMobile]: 0, + [RequestParameter.SessionEngaged]: 1, + // The below is needed for tech details to be collected. + [RequestParameter.UserAgentFullVersionList]: + 'Google%20Chrome;111.0.5563.64|Not(A%3ABrand;8.0.0.0|Chromium;111.0.5563.64', + }; + + if (ngDebug) { + requestParameters[RequestParameter.DebugView] = 1; + } + + this.requestParameterStringified = querystring.stringify(requestParameters); + + const parsedVersion = semver.parse(process.version); + const packageManagerVersion = context.packageManager.version; + + this.userParameters = { + // While architecture is being collect by GA as UserAgentArchitecture. + // It doesn't look like there is a way to query this. Therefore we collect this as a custom user dimension too. + [UserCustomDimension.OsArchitecture]: os.arch(), + // While User ID is being collected by GA, this is not visible in reports/for filtering. + [UserCustomDimension.UserId]: userId, + [UserCustomDimension.NodeVersion]: parsedVersion + ? `${parsedVersion.major}.${parsedVersion.minor}.${parsedVersion.patch}` + : 'other', + [UserCustomDimension.NodeMajorVersion]: parsedVersion?.major, + [UserCustomDimension.PackageManager]: context.packageManager.name, + [UserCustomDimension.PackageManagerVersion]: packageManagerVersion, + [UserCustomDimension.PackageManagerMajorVersion]: packageManagerVersion + ? +packageManagerVersion.split('.', 1)[0] + : undefined, + [UserCustomDimension.AngularCLIVersion]: VERSION.full, + [UserCustomDimension.AngularCLIMajorVersion]: VERSION.major, + }; + } + + reportWorkspaceInfoEvent( + parameters: Partial>, + ): void { + this.event('workspace_info', parameters); + } + + reportRebuildRunEvent( + parameters: Partial< + Record + >, + ): void { + this.event('run_rebuild', parameters); + } + + reportBuildRunEvent( + parameters: Partial< + Record + >, + ): void { + this.event('run_build', parameters); + } + + reportArchitectRunEvent(parameters: Partial>): void { + this.event('run_architect', parameters); + } + + reportSchematicRunEvent(parameters: Partial>): void { + this.event('run_schematic', parameters); + } + + reportCommandRunEvent(command: string): void { + this.event('run_command', { [EventCustomDimension.Command]: command }); + } + + private event(eventName: string, parameters?: Record): void { + this.trackingEventsQueue ??= []; + this.trackingEventsQueue.push({ + ...this.userParameters, + ...parameters, + 'en': eventName, + }); + } + + /** + * Flush on an interval (if the event loop is waiting). + * + * @returns a method that when called will terminate the periodic + * flush and call flush one last time. + */ + periodFlush(): () => Promise { + let analyticsFlushPromise = Promise.resolve(); + const analyticsFlushInterval = setInterval(() => { + if (this.trackingEventsQueue?.length) { + analyticsFlushPromise = analyticsFlushPromise.then(() => this.flush()); + } + }, 4000); + + return () => { + clearInterval(analyticsFlushInterval); + + // Flush one last time. + return analyticsFlushPromise.then(() => this.flush()); + }; + } + + async flush(): Promise { + const pendingTrackingEvents = this.trackingEventsQueue; + this.context.logger.debug(`Analytics flush size. ${pendingTrackingEvents?.length}.`); + + if (!pendingTrackingEvents?.length) { + return; + } + + // The below is needed so that if flush is called multiple times, + // we don't report the same event multiple times. + this.trackingEventsQueue = undefined; + + try { + await this.send(pendingTrackingEvents); + } catch (error) { + // Failure to report analytics shouldn't crash the CLI. + assertIsError(error); + this.context.logger.debug(`Send analytics error. ${error.message}.`); + } + } + + private async send(data: Record[]): Promise { + return new Promise((resolve, reject) => { + const request = https.request( + { + host: 'www.google-analytics.com', + method: 'POST', + path: '/g/collect?' + this.requestParameterStringified, + headers: { + // The below is needed for tech details to be collected even though we provide our own information from the OS Node.js module + 'user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', + }, + }, + (response) => { + // The below is needed as otherwise the response will never close which will cause the CLI not to terminate. + response.on('data', () => {}); + + if (response.statusCode !== 200 && response.statusCode !== 204) { + reject( + new Error(`Analytics reporting failed with status code: ${response.statusCode}.`), + ); + } else { + resolve(); + } + }, + ); + + request.on('error', reject); + const queryParameters = data.map((p) => querystring.stringify(p)).join('\n'); + request.write(queryParameters); + request.end(); + }); + } +} diff --git a/packages/angular/cli/src/analytics/analytics-parameters.mts b/packages/angular/cli/src/analytics/analytics-parameters.mts new file mode 100644 index 000000000000..8a667dd9d2b8 --- /dev/null +++ b/packages/angular/cli/src/analytics/analytics-parameters.mts @@ -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.dev/license + */ + +/** This is a copy of analytics-parameters.ts and is needed for `yarn admin validate-user-analytics` due to ts-node. */ + +/** + * GA built-in request parameters + * @see https://www.thyngster.com/ga4-measurement-protocol-cheatsheet + * @see http://go/depot/google3/analytics/container_tag/templates/common/gold/mpv2_schema.js + */ +export enum RequestParameter { + ClientId = 'cid', + DebugView = '_dbg', + GtmVersion = 'gtm', + Language = 'ul', + NewToSite = '_nsi', + NonInteraction = 'ni', + PageLocation = 'dl', + PageTitle = 'dt', + ProtocolVersion = 'v', + SessionEngaged = 'seg', + SessionId = 'sid', + SessionNumber = 'sct', + SessionStart = '_ss', + TrackingId = 'tid', + TrafficType = 'tt', + UserAgentArchitecture = 'uaa', + UserAgentBitness = 'uab', + UserAgentFullVersionList = 'uafvl', + UserAgentMobile = 'uamb', + UserAgentModel = 'uam', + UserAgentPlatform = 'uap', + UserAgentPlatformVersion = 'uapv', + UserId = 'uid', +} + +/** + * User scoped custom dimensions. + * @remarks + * - User custom dimensions limit is 25. + * - `up.*` string type. + * - `upn.*` number type. + * @see https://support.google.com/analytics/answer/10075209?hl=en + */ +export enum UserCustomDimension { + UserId = 'up.ng_user_id', + OsArchitecture = 'up.ng_os_architecture', + NodeVersion = 'up.ng_node_version', + NodeMajorVersion = 'upn.ng_node_major_version', + AngularCLIVersion = 'up.ng_cli_version', + AngularCLIMajorVersion = 'upn.ng_cli_major_version', + PackageManager = 'up.ng_package_manager', + PackageManagerVersion = 'up.ng_pkg_manager_version', + PackageManagerMajorVersion = 'upn.ng_pkg_manager_major_v', +} + +/** + * Event scoped custom dimensions. + * @remarks + * - Event custom dimensions limit is 50. + * - `ep.*` string type. + * - `epn.*` number type. + * @see https://support.google.com/analytics/answer/10075209?hl=en + */ +export enum EventCustomDimension { + Command = 'ep.ng_command', + SchematicCollectionName = 'ep.ng_schematic_collection_name', + SchematicName = 'ep.ng_schematic_name', + Standalone = 'ep.ng_standalone', + SSR = 'ep.ng_ssr', + Style = 'ep.ng_style', + Routing = 'ep.ng_routing', + InlineTemplate = 'ep.ng_inline_template', + InlineStyle = 'ep.ng_inline_style', + BuilderTarget = 'ep.ng_builder_target', + Aot = 'ep.ng_aot', + Optimization = 'ep.ng_optimization', +} + +/** + * Event scoped custom mertics. + * @remarks + * - Event scoped custom mertics limit is 50. + * - `ep.*` string type. + * - `epn.*` number type. + * @see https://support.google.com/analytics/answer/10075209?hl=en + */ +export enum EventCustomMetric { + AllChunksCount = 'epn.ng_all_chunks_count', + LazyChunksCount = 'epn.ng_lazy_chunks_count', + InitialChunksCount = 'epn.ng_initial_chunks_count', + ChangedChunksCount = 'epn.ng_changed_chunks_count', + DurationInMs = 'epn.ng_duration_ms', + CssSizeInBytes = 'epn.ng_css_size_bytes', + JsSizeInBytes = 'epn.ng_js_size_bytes', + NgComponentCount = 'epn.ng_component_count', + AllProjectsCount = 'epn.all_projects_count', + LibraryProjectsCount = 'epn.libs_projects_count', + ApplicationProjectsCount = 'epn.apps_projects_count', +} diff --git a/packages/angular/cli/src/analytics/analytics-parameters.ts b/packages/angular/cli/src/analytics/analytics-parameters.ts new file mode 100644 index 000000000000..08ee5d72a684 --- /dev/null +++ b/packages/angular/cli/src/analytics/analytics-parameters.ts @@ -0,0 +1,107 @@ +/** + * @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.dev/license + */ + +/** Any changes in this file needs to be done in the mts version. */ + +export type PrimitiveTypes = string | number | boolean; + +/** + * GA built-in request parameters + * @see https://www.thyngster.com/ga4-measurement-protocol-cheatsheet + * @see http://go/depot/google3/analytics/container_tag/templates/common/gold/mpv2_schema.js + */ +export enum RequestParameter { + ClientId = 'cid', + DebugView = '_dbg', + GtmVersion = 'gtm', + Language = 'ul', + NewToSite = '_nsi', + NonInteraction = 'ni', + PageLocation = 'dl', + PageTitle = 'dt', + ProtocolVersion = 'v', + SessionEngaged = 'seg', + SessionId = 'sid', + SessionNumber = 'sct', + SessionStart = '_ss', + TrackingId = 'tid', + TrafficType = 'tt', + UserAgentArchitecture = 'uaa', + UserAgentBitness = 'uab', + UserAgentFullVersionList = 'uafvl', + UserAgentMobile = 'uamb', + UserAgentModel = 'uam', + UserAgentPlatform = 'uap', + UserAgentPlatformVersion = 'uapv', + UserId = 'uid', +} + +/** + * User scoped custom dimensions. + * @remarks + * - User custom dimensions limit is 25. + * - `up.*` string type. + * - `upn.*` number type. + * @see https://support.google.com/analytics/answer/10075209?hl=en + */ +export enum UserCustomDimension { + UserId = 'up.ng_user_id', + OsArchitecture = 'up.ng_os_architecture', + NodeVersion = 'up.ng_node_version', + NodeMajorVersion = 'upn.ng_node_major_version', + AngularCLIVersion = 'up.ng_cli_version', + AngularCLIMajorVersion = 'upn.ng_cli_major_version', + PackageManager = 'up.ng_package_manager', + PackageManagerVersion = 'up.ng_pkg_manager_version', + PackageManagerMajorVersion = 'upn.ng_pkg_manager_major_v', +} + +/** + * Event scoped custom dimensions. + * @remarks + * - Event custom dimensions limit is 50. + * - `ep.*` string type. + * - `epn.*` number type. + * @see https://support.google.com/analytics/answer/10075209?hl=en + */ +export enum EventCustomDimension { + Command = 'ep.ng_command', + SchematicCollectionName = 'ep.ng_schematic_collection_name', + SchematicName = 'ep.ng_schematic_name', + Standalone = 'ep.ng_standalone', + SSR = 'ep.ng_ssr', + Style = 'ep.ng_style', + Routing = 'ep.ng_routing', + InlineTemplate = 'ep.ng_inline_template', + InlineStyle = 'ep.ng_inline_style', + BuilderTarget = 'ep.ng_builder_target', + Aot = 'ep.ng_aot', + Optimization = 'ep.ng_optimization', +} + +/** + * Event scoped custom mertics. + * @remarks + * - Event scoped custom mertics limit is 50. + * - `ep.*` string type. + * - `epn.*` number type. + * @see https://support.google.com/analytics/answer/10075209?hl=en + */ +export enum EventCustomMetric { + AllChunksCount = 'epn.ng_all_chunks_count', + LazyChunksCount = 'epn.ng_lazy_chunks_count', + InitialChunksCount = 'epn.ng_initial_chunks_count', + ChangedChunksCount = 'epn.ng_changed_chunks_count', + DurationInMs = 'epn.ng_duration_ms', + CssSizeInBytes = 'epn.ng_css_size_bytes', + JsSizeInBytes = 'epn.ng_js_size_bytes', + NgComponentCount = 'epn.ng_component_count', + AllProjectsCount = 'epn.all_projects_count', + LibraryProjectsCount = 'epn.libs_projects_count', + ApplicationProjectsCount = 'epn.apps_projects_count', +} diff --git a/packages/angular/cli/src/analytics/analytics.ts b/packages/angular/cli/src/analytics/analytics.ts new file mode 100644 index 000000000000..752b0dfca88a --- /dev/null +++ b/packages/angular/cli/src/analytics/analytics.ts @@ -0,0 +1,214 @@ +/** + * @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.dev/license + */ + +import { json, tags } from '@angular-devkit/core'; +import { randomUUID } from 'node:crypto'; +import type { CommandContext } from '../command-builder/command-module'; +import { colors } from '../utilities/color'; +import { getWorkspace } from '../utilities/config'; +import { analyticsDisabled } from '../utilities/environment-options'; +import { askConfirmation } from '../utilities/prompt'; +import { isTTY } from '../utilities/tty'; + +/* eslint-disable no-console */ + +/** + * This is the ultimate safelist for checking if a package name is safe to report to analytics. + */ +export const analyticsPackageSafelist = [ + /^@angular\//, + /^@angular-devkit\//, + /^@nguniversal\//, + '@schematics/angular', +]; + +export function isPackageNameSafeForAnalytics(name: string): boolean { + return analyticsPackageSafelist.some((pattern) => { + if (typeof pattern == 'string') { + return pattern === name; + } else { + return pattern.test(name); + } + }); +} + +/** + * Set analytics settings. This does not work if the user is not inside a project. + * @param global Which config to use. "global" for user-level, and "local" for project-level. + * @param value Either a user ID, true to generate a new User ID, or false to disable analytics. + */ +export async function setAnalyticsConfig(global: boolean, value: string | boolean): Promise { + const level = global ? 'global' : 'local'; + const workspace = await getWorkspace(level); + if (!workspace) { + throw new Error(`Could not find ${level} workspace.`); + } + + const cli = (workspace.extensions['cli'] ??= {}); + if (!workspace || !json.isJsonObject(cli)) { + throw new Error(`Invalid config found at ${workspace.filePath}. CLI should be an object.`); + } + + cli.analytics = value === true ? randomUUID() : value; + await workspace.save(); +} + +/** + * Prompt the user for usage gathering permission. + * @param force Whether to ask regardless of whether or not the user is using an interactive shell. + * @return Whether or not the user was shown a prompt. + */ +export async function promptAnalytics( + context: CommandContext, + global: boolean, + force = false, +): Promise { + const level = global ? 'global' : 'local'; + const workspace = await getWorkspace(level); + if (!workspace) { + throw new Error(`Could not find a ${level} workspace. Are you in a project?`); + } + + if (force || isTTY()) { + const answer = await askConfirmation( + ` +Would you like to share pseudonymous usage data about this project with the Angular Team +at Google under Google's Privacy Policy at https://policies.google.com/privacy. For more +details and how to change this setting, see https://angular.dev/cli/analytics. + + `, + false, + ); + + await setAnalyticsConfig(global, answer); + + if (answer) { + console.log(''); + console.log( + tags.stripIndent` + Thank you for sharing pseudonymous usage data. Should you change your mind, the following + command will disable this feature entirely: + + ${colors.yellow(`ng analytics disable${global ? ' --global' : ''}`)} + `, + ); + console.log(''); + } + + process.stderr.write(await getAnalyticsInfoString(context)); + + return true; + } + + return false; +} + +/** + * Get the analytics user id. + * + * @returns + * - `string` user id. + * - `false` when disabled. + * - `undefined` when not configured. + */ +async function getAnalyticsUserIdForLevel( + level: 'local' | 'global', +): Promise { + if (analyticsDisabled) { + return false; + } + + const workspace = await getWorkspace(level); + const analyticsConfig: string | undefined | null | { uid?: string } | boolean = + workspace?.getCli()?.['analytics']; + + if (analyticsConfig === false) { + return false; + } else if (analyticsConfig === undefined || analyticsConfig === null) { + return undefined; + } else { + if (typeof analyticsConfig == 'string') { + return analyticsConfig; + } else if (typeof analyticsConfig == 'object' && typeof analyticsConfig['uid'] == 'string') { + return analyticsConfig['uid']; + } + + return undefined; + } +} + +export async function getAnalyticsUserId( + context: CommandContext, + skipPrompt = false, +): Promise { + const { workspace } = context; + // Global config takes precedence over local config only for the disabled check. + // IE: + // global: disabled & local: enabled = disabled + // global: id: 123 & local: id: 456 = 456 + + // check global + const globalConfig = await getAnalyticsUserIdForLevel('global'); + if (globalConfig === false) { + return undefined; + } + + // Not disabled globally, check locally or not set globally and command is run outside of workspace example: `ng new` + if (workspace || globalConfig === undefined) { + const level = workspace ? 'local' : 'global'; + let localOrGlobalConfig = await getAnalyticsUserIdForLevel(level); + if (localOrGlobalConfig === undefined) { + if (!skipPrompt) { + // config is unset, prompt user. + // TODO: This should honor the `no-interactive` option. + // It is currently not an `ng` option but rather only an option for specific commands. + // The concept of `ng`-wide options are needed to cleanly handle this. + await promptAnalytics(context, !workspace /** global */); + localOrGlobalConfig = await getAnalyticsUserIdForLevel(level); + } + } + + if (localOrGlobalConfig === false) { + return undefined; + } else if (typeof localOrGlobalConfig === 'string') { + return localOrGlobalConfig; + } + } + + return globalConfig; +} + +function analyticsConfigValueToHumanFormat(value: unknown): 'enabled' | 'disabled' | 'not set' { + if (value === false) { + return 'disabled'; + } else if (typeof value === 'string' || value === true) { + return 'enabled'; + } else { + return 'not set'; + } +} + +export async function getAnalyticsInfoString(context: CommandContext): Promise { + const analyticsInstance = await getAnalyticsUserId(context, true /** skipPrompt */); + + const { globalConfiguration, workspace: localWorkspace } = context; + const globalSetting = globalConfiguration?.getCli()?.['analytics']; + const localSetting = localWorkspace?.getCli()?.['analytics']; + + return ( + tags.stripIndents` + Global setting: ${analyticsConfigValueToHumanFormat(globalSetting)} + Local setting: ${ + localWorkspace + ? analyticsConfigValueToHumanFormat(localSetting) + : 'No local workspace configuration file.' + } + Effective status: ${analyticsInstance ? 'enabled' : 'disabled'} + ` + '\n' + ); +} diff --git a/packages/angular/cli/src/command-builder/architect-base-command-module.ts b/packages/angular/cli/src/command-builder/architect-base-command-module.ts new file mode 100644 index 000000000000..fb3508777d74 --- /dev/null +++ b/packages/angular/cli/src/command-builder/architect-base-command-module.ts @@ -0,0 +1,297 @@ +/** + * @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.dev/license + */ + +import { Architect, Target } from '@angular-devkit/architect'; +import { + NodeModulesBuilderInfo, + WorkspaceNodeModulesArchitectHost, +} from '@angular-devkit/architect/node'; +import { json } from '@angular-devkit/core'; +import { createRequire } from 'node:module'; +import { isPackageNameSafeForAnalytics } from '../analytics/analytics'; +import { EventCustomDimension, EventCustomMetric } from '../analytics/analytics-parameters'; +import { assertIsError } from '../utilities/error'; +import { askConfirmation, askQuestion } from '../utilities/prompt'; +import { isTTY } from '../utilities/tty'; +import { + CommandModule, + CommandModuleError, + CommandModuleImplementation, + CommandScope, + OtherOptions, +} from './command-module'; +import { Option, parseJsonSchemaToOptions } from './utilities/json-schema'; + +export interface MissingTargetChoice { + name: string; + value: string; +} + +export abstract class ArchitectBaseCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + override scope = CommandScope.In; + protected readonly missingTargetChoices: MissingTargetChoice[] | undefined; + + protected async runSingleTarget(target: Target, options: OtherOptions): Promise { + const architectHost = this.getArchitectHost(); + + let builderName: string; + try { + builderName = await architectHost.getBuilderNameForTarget(target); + } catch (e) { + assertIsError(e); + + return this.onMissingTarget(e.message); + } + + const isAngularBuild = builderName.startsWith('@angular/build:'); + + const { logger } = this.context; + const run = await this.getArchitect(isAngularBuild).scheduleTarget( + target, + options as json.JsonObject, + { + logger, + }, + ); + + const analytics = isPackageNameSafeForAnalytics(builderName) + ? await this.getAnalytics() + : undefined; + + let outputSubscription; + if (analytics) { + analytics.reportArchitectRunEvent({ + [EventCustomDimension.BuilderTarget]: builderName, + }); + + let firstRun = true; + outputSubscription = run.output.subscribe(({ stats }) => { + const parameters = this.builderStatsToAnalyticsParameters(stats, builderName); + if (!parameters) { + return; + } + + if (firstRun) { + firstRun = false; + analytics.reportBuildRunEvent(parameters); + } else { + analytics.reportRebuildRunEvent(parameters); + } + }); + } + + try { + const { error, success } = await run.lastOutput; + if (error) { + logger.error(error); + } + + return success ? 0 : 1; + } finally { + await run.stop(); + outputSubscription?.unsubscribe(); + } + } + + private builderStatsToAnalyticsParameters( + stats: json.JsonValue, + builderName: string, + ): Partial< + | Record + | undefined + > { + if (!stats || typeof stats !== 'object' || !('durationInMs' in stats)) { + return undefined; + } + + const { + optimization, + allChunksCount, + aot, + lazyChunksCount, + initialChunksCount, + durationInMs, + changedChunksCount, + cssSizeInBytes, + jsSizeInBytes, + ngComponentCount, + } = stats; + + return { + [EventCustomDimension.BuilderTarget]: builderName, + [EventCustomDimension.Aot]: aot, + [EventCustomDimension.Optimization]: optimization, + [EventCustomMetric.AllChunksCount]: allChunksCount, + [EventCustomMetric.LazyChunksCount]: lazyChunksCount, + [EventCustomMetric.InitialChunksCount]: initialChunksCount, + [EventCustomMetric.ChangedChunksCount]: changedChunksCount, + [EventCustomMetric.DurationInMs]: durationInMs, + [EventCustomMetric.JsSizeInBytes]: jsSizeInBytes, + [EventCustomMetric.CssSizeInBytes]: cssSizeInBytes, + [EventCustomMetric.NgComponentCount]: ngComponentCount, + }; + } + + private _architectHost: WorkspaceNodeModulesArchitectHost | undefined; + protected getArchitectHost(): WorkspaceNodeModulesArchitectHost { + if (this._architectHost) { + return this._architectHost; + } + + const workspace = this.getWorkspaceOrThrow(); + + return (this._architectHost = new WorkspaceNodeModulesArchitectHost( + workspace, + workspace.basePath, + )); + } + + private _architect: Architect | undefined; + protected getArchitect(skipUndefinedArrayTransform: boolean): Architect { + if (this._architect) { + return this._architect; + } + + const registry = new json.schema.CoreSchemaRegistry(); + if (skipUndefinedArrayTransform) { + registry.addPostTransform(json.schema.transforms.addUndefinedObjectDefaults); + } else { + registry.addPostTransform(json.schema.transforms.addUndefinedDefaults); + } + registry.useXDeprecatedProvider((msg) => this.context.logger.warn(msg)); + + const architectHost = this.getArchitectHost(); + + return (this._architect = new Architect(architectHost, registry)); + } + + protected async getArchitectTargetOptions(target: Target): Promise { + const architectHost = this.getArchitectHost(); + let builderConf: string; + + try { + builderConf = await architectHost.getBuilderNameForTarget(target); + } catch { + return []; + } + + let builderDesc: NodeModulesBuilderInfo; + try { + builderDesc = await architectHost.resolveBuilder(builderConf); + } catch (e) { + assertIsError(e); + if (e.code === 'MODULE_NOT_FOUND') { + this.warnOnMissingNodeModules(); + throw new CommandModuleError(`Could not find the '${builderConf}' builder's node package.`); + } + + throw e; + } + + return parseJsonSchemaToOptions( + new json.schema.CoreSchemaRegistry(), + builderDesc.optionSchema as json.JsonObject, + true, + ); + } + + private warnOnMissingNodeModules(): void { + const basePath = this.context.workspace?.basePath; + if (!basePath) { + return; + } + + const workspaceResolve = createRequire(basePath + '/').resolve; + + try { + workspaceResolve('@angular/core'); + + return; + } catch {} + + this.context.logger.warn( + `Node packages may not be installed. Try installing with '${this.context.packageManager.name} install'.`, + ); + } + + protected getArchitectTarget(): string { + return this.commandName; + } + + protected async onMissingTarget(defaultMessage: string): Promise<1> { + const { logger } = this.context; + const choices = this.missingTargetChoices; + + if (!choices?.length) { + logger.error(defaultMessage); + + return 1; + } + + const missingTargetMessage = + `Cannot find "${this.getArchitectTarget()}" target for the specified project.\n` + + `You can add a package that implements these capabilities.\n\n` + + `For example:\n` + + choices.map(({ name, value }) => ` ${name}: ng add ${value}`).join('\n') + + '\n'; + + if (isTTY()) { + // Use prompts to ask the user if they'd like to install a package. + logger.warn(missingTargetMessage); + + const packageToInstall = await this.getMissingTargetPackageToInstall(choices); + if (packageToInstall) { + // Example run: `ng add angular-eslint`. + const AddCommandModule = (await import('../commands/add/cli')).default; + await new AddCommandModule(this.context).run({ + interactive: true, + force: false, + dryRun: false, + defaults: false, + collection: packageToInstall, + }); + } + } else { + // Non TTY display error message. + logger.error(missingTargetMessage); + } + + return 1; + } + + private async getMissingTargetPackageToInstall( + choices: MissingTargetChoice[], + ): Promise { + if (choices.length === 1) { + // Single choice + const { name, value } = choices[0]; + if (await askConfirmation(`Would you like to add ${name} now?`, true, false)) { + return value; + } + + return null; + } + + // Multiple choice + return askQuestion( + `Would you like to add a package with "${this.getArchitectTarget()}" capabilities now?`, + [ + { + name: 'No', + value: null, + }, + ...choices, + ], + 0, + null, + ); + } +} diff --git a/packages/angular/cli/src/command-builder/architect-command-module.ts b/packages/angular/cli/src/command-builder/architect-command-module.ts new file mode 100644 index 000000000000..98e270cf1dad --- /dev/null +++ b/packages/angular/cli/src/command-builder/architect-command-module.ts @@ -0,0 +1,222 @@ +/** + * @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.dev/license + */ + +import { Target } from '@angular-devkit/architect'; +import { workspaces } from '@angular-devkit/core'; +import { Argv } from 'yargs'; +import { getProjectByCwd } from '../utilities/config'; +import { memoize } from '../utilities/memoize'; +import { ArchitectBaseCommandModule } from './architect-base-command-module'; +import { + CommandModuleError, + CommandModuleImplementation, + Options, + OtherOptions, +} from './command-module'; + +export interface ArchitectCommandArgs { + configuration?: string; + project?: string; +} + +export abstract class ArchitectCommandModule + extends ArchitectBaseCommandModule + implements CommandModuleImplementation +{ + abstract readonly multiTarget: boolean; + + findDefaultBuilderName?( + project: workspaces.ProjectDefinition, + target: Target, + ): Promise; + + async builder(argv: Argv): Promise> { + const target = this.getArchitectTarget(); + + // Add default builder if target is not in project and a command default is provided + if (this.findDefaultBuilderName && this.context.workspace) { + for (const [project, projectDefinition] of this.context.workspace.projects) { + const targetDefinition = projectDefinition.targets.get(target); + if (targetDefinition?.builder) { + continue; + } + + const defaultBuilder = await this.findDefaultBuilderName(projectDefinition, { + project, + target, + }); + if (!defaultBuilder) { + continue; + } + + if (targetDefinition) { + targetDefinition.builder = defaultBuilder; + } else { + projectDefinition.targets.set(target, { + builder: defaultBuilder, + }); + } + } + } + + const project = this.getArchitectProject(); + const { jsonHelp, getYargsCompletions, help } = this.context.args.options; + + const localYargs: Argv = argv + .positional('project', { + describe: 'The name of the project to build. Can be an application or a library.', + type: 'string', + // Hide choices from JSON help so that we don't display them in AIO. + choices: jsonHelp ? undefined : this.getProjectChoices(), + }) + .option('configuration', { + describe: + `One or more named builder configurations as a comma-separated ` + + `list as specified in the "configurations" section in angular.json.\n` + + `The builder uses the named configurations to run the given target.\n` + + `For more information, see https://angular.dev/reference/configs/workspace-config#alternate-build-configurations.`, + alias: 'c', + type: 'string', + // Show only in when using --help and auto completion because otherwise comma seperated configuration values will be invalid. + // Also, hide choices from JSON help so that we don't display them in AIO. + choices: + (getYargsCompletions || help) && !jsonHelp && project + ? this.getConfigurationChoices(project) + : undefined, + }) + .strict(); + + if (!project) { + return localYargs; + } + + const schemaOptions = await this.getArchitectTargetOptions({ + project, + target, + }); + + return this.addSchemaOptionsToCommand(localYargs, schemaOptions); + } + + async run(options: Options & OtherOptions): Promise { + const originalProcessTitle = process.title; + try { + const target = this.getArchitectTarget(); + const { configuration = '', project, ...architectOptions } = options; + + if (project) { + process.title = `${originalProcessTitle} (${project})`; + + return await this.runSingleTarget({ configuration, target, project }, architectOptions); + } + + // This runs each target sequentially. + // Running them in parallel would jumble the log messages. + let result = 0; + const projectNames = this.getProjectNamesByTarget(target); + if (!projectNames) { + return this.onMissingTarget('Cannot determine project or target for command.'); + } + + for (const project of projectNames) { + process.title = `${originalProcessTitle} (${project})`; + result |= await this.runSingleTarget({ configuration, target, project }, architectOptions); + } + + return result; + } finally { + process.title = originalProcessTitle; + } + } + + private getArchitectProject(): string | undefined { + const { options, positional } = this.context.args; + const [, projectName] = positional; + + if (projectName) { + return projectName; + } + + // Yargs allows positional args to be used as flags. + if (typeof options['project'] === 'string') { + return options['project']; + } + + const target = this.getArchitectTarget(); + const projectFromTarget = this.getProjectNamesByTarget(target); + + return projectFromTarget?.length ? projectFromTarget[0] : undefined; + } + + @memoize + private getProjectNamesByTarget(target: string): string[] | undefined { + const workspace = this.getWorkspaceOrThrow(); + const allProjectsForTargetName: string[] = []; + + for (const [name, project] of workspace.projects) { + if (project.targets.has(target)) { + allProjectsForTargetName.push(name); + } + } + + if (allProjectsForTargetName.length === 0) { + return undefined; + } + + if (this.multiTarget) { + // For multi target commands, we always list all projects that have the target. + return allProjectsForTargetName; + } else { + if (allProjectsForTargetName.length === 1) { + return allProjectsForTargetName; + } + + const maybeProject = getProjectByCwd(workspace); + if (maybeProject) { + return allProjectsForTargetName.includes(maybeProject) ? [maybeProject] : undefined; + } + + const { getYargsCompletions, help } = this.context.args.options; + if (!getYargsCompletions && !help) { + // Only issue the below error when not in help / completion mode. + throw new CommandModuleError( + 'Cannot determine project for command.\n' + + 'This is a multi-project workspace and more than one project supports this command. ' + + `Run "ng ${this.command}" to execute the command for a specific project or change the current ` + + 'working directory to a project directory.\n\n' + + `Available projects are:\n${allProjectsForTargetName + .sort() + .map((p) => `- ${p}`) + .join('\n')}`, + ); + } + } + + return undefined; + } + + /** @returns a sorted list of project names to be used for auto completion. */ + private getProjectChoices(): string[] | undefined { + const { workspace } = this.context; + + return workspace ? [...workspace.projects.keys()].sort() : undefined; + } + + /** @returns a sorted list of configuration names to be used for auto completion. */ + private getConfigurationChoices(project: string): string[] | undefined { + const projectDefinition = this.context.workspace?.projects.get(project); + if (!projectDefinition) { + return undefined; + } + + const target = this.getArchitectTarget(); + const configurations = projectDefinition.targets.get(target)?.configurations; + + return configurations ? Object.keys(configurations).sort() : undefined; + } +} diff --git a/packages/angular/cli/src/command-builder/command-module.ts b/packages/angular/cli/src/command-builder/command-module.ts new file mode 100644 index 000000000000..d036656cf2dd --- /dev/null +++ b/packages/angular/cli/src/command-builder/command-module.ts @@ -0,0 +1,305 @@ +/** + * @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.dev/license + */ + +import { logging, schema } from '@angular-devkit/core'; +import { readFileSync } from 'node:fs'; +import * as path from 'node:path'; +import type { + ArgumentsCamelCase, + Argv, + CamelCaseKey, + CommandModule as YargsCommandModule, +} from 'yargs'; +import { Parser as yargsParser } from 'yargs/helpers'; +import { getAnalyticsUserId } from '../analytics/analytics'; +import { AnalyticsCollector } from '../analytics/analytics-collector'; +import { EventCustomDimension, EventCustomMetric } from '../analytics/analytics-parameters'; +import { considerSettingUpAutocompletion } from '../utilities/completion'; +import { AngularWorkspace } from '../utilities/config'; +import { memoize } from '../utilities/memoize'; +import { PackageManagerUtils } from '../utilities/package-manager'; +import { Option, addSchemaOptionsToCommand } from './utilities/json-schema'; + +export type Options = { [key in keyof T as CamelCaseKey]: T[key] }; + +export enum CommandScope { + /** Command can only run inside an Angular workspace. */ + In, + + /** Command can only run outside an Angular workspace. */ + Out, + + /** Command can run inside and outside an Angular workspace. */ + Both, +} + +export interface CommandContext { + currentDirectory: string; + root: string; + workspace?: AngularWorkspace; + globalConfiguration: AngularWorkspace; + logger: logging.Logger; + packageManager: PackageManagerUtils; + yargsInstance: Argv<{}>; + + /** Arguments parsed in free-from without parser configuration. */ + args: { + positional: string[]; + options: { + help: boolean; + jsonHelp: boolean; + getYargsCompletions: boolean; + } & Record; + }; +} + +export type OtherOptions = Record; + +export interface CommandModuleImplementation + extends Omit, 'builder' | 'handler'> { + /** Scope in which the command can be executed in. */ + scope: CommandScope; + + /** Path used to load the long description for the command in JSON help text. */ + longDescriptionPath?: string; + + /** Object declaring the options the command accepts, or a function accepting and returning a yargs instance. */ + builder(argv: Argv): Promise> | Argv; + + /** A function which will be passed the parsed argv. */ + run(options: Options & OtherOptions): Promise | number | void; +} + +export interface FullDescribe { + describe?: string; + longDescription?: string; + longDescriptionRelativePath?: string; +} + +export abstract class CommandModule implements CommandModuleImplementation { + abstract readonly command: string; + abstract readonly describe: string | false; + abstract readonly longDescriptionPath?: string; + protected readonly shouldReportAnalytics: boolean = true; + readonly scope: CommandScope = CommandScope.Both; + + private readonly optionsWithAnalytics = new Map< + string, + EventCustomDimension | EventCustomMetric + >(); + + constructor(protected readonly context: CommandContext) {} + + /** + * Description object which contains the long command descroption. + * This is used to generate JSON help wich is used in AIO. + * + * `false` will result in a hidden command. + */ + public get fullDescribe(): FullDescribe | false { + return this.describe === false + ? false + : { + describe: this.describe, + ...(this.longDescriptionPath + ? { + longDescriptionRelativePath: path + .relative(path.join(__dirname, '../../../../'), this.longDescriptionPath) + .replace(/\\/g, path.posix.sep), + longDescription: readFileSync(this.longDescriptionPath, 'utf8').replace( + /\r\n/g, + '\n', + ), + } + : {}), + }; + } + + protected get commandName(): string { + return this.command.split(' ', 1)[0]; + } + + abstract builder(argv: Argv): Promise> | Argv; + abstract run(options: Options & OtherOptions): Promise | number | void; + + async handler(args: ArgumentsCamelCase & OtherOptions): Promise { + const { _, $0, ...options } = args; + + // Camelize options as yargs will return the object in kebab-case when camel casing is disabled. + const camelCasedOptions: Record = {}; + for (const [key, value] of Object.entries(options)) { + camelCasedOptions[yargsParser.camelCase(key)] = value; + } + + // Set up autocompletion if appropriate. + const autocompletionExitCode = await considerSettingUpAutocompletion( + this.commandName, + this.context.logger, + ); + if (autocompletionExitCode !== undefined) { + process.exitCode = autocompletionExitCode; + + return; + } + + // Gather and report analytics. + const analytics = await this.getAnalytics(); + const stopPeriodicFlushes = analytics && analytics.periodFlush(); + + let exitCode: number | void | undefined; + try { + if (analytics) { + this.reportCommandRunAnalytics(analytics); + this.reportWorkspaceInfoAnalytics(analytics); + } + + exitCode = await this.run(camelCasedOptions as Options & OtherOptions); + } catch (e) { + if (e instanceof schema.SchemaValidationException) { + this.context.logger.fatal(`Error: ${e.message}`); + exitCode = 1; + } else { + throw e; + } + } finally { + await stopPeriodicFlushes?.(); + + if (typeof exitCode === 'number' && exitCode > 0) { + process.exitCode = exitCode; + } + } + } + + @memoize + protected async getAnalytics(): Promise { + if (!this.shouldReportAnalytics) { + return undefined; + } + + const userId = await getAnalyticsUserId( + this.context, + // Don't prompt on `ng update`, 'ng version' or `ng analytics`. + ['version', 'update', 'analytics'].includes(this.commandName), + ); + + return userId ? new AnalyticsCollector(this.context, userId) : undefined; + } + + /** + * Adds schema options to a command also this keeps track of options that are required for analytics. + * **Note:** This method should be called from the command bundler method. + */ + protected addSchemaOptionsToCommand(localYargs: Argv, options: Option[]): Argv { + const optionsWithAnalytics = addSchemaOptionsToCommand( + localYargs, + options, + // This should only be done when `--help` is used otherwise default will override options set in angular.json. + /* includeDefaultValues= */ this.context.args.options.help, + ); + + // Record option of analytics. + for (const [name, userAnalytics] of optionsWithAnalytics) { + this.optionsWithAnalytics.set(name, userAnalytics); + } + + return localYargs; + } + + protected getWorkspaceOrThrow(): AngularWorkspace { + const { workspace } = this.context; + if (!workspace) { + throw new CommandModuleError('A workspace is required for this command.'); + } + + return workspace; + } + + /** + * Flush on an interval (if the event loop is waiting). + * + * @returns a method that when called will terminate the periodic + * flush and call flush one last time. + */ + protected getAnalyticsParameters( + options: (Options & OtherOptions) | OtherOptions, + ): Partial> { + const parameters: Partial< + Record + > = {}; + + const validEventCustomDimensionAndMetrics = new Set([ + ...Object.values(EventCustomDimension), + ...Object.values(EventCustomMetric), + ]); + + for (const [name, ua] of this.optionsWithAnalytics) { + if (!validEventCustomDimensionAndMetrics.has(ua)) { + continue; + } + + const value = options[name]; + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + parameters[ua] = value; + } else if (Array.isArray(value)) { + // GA doesn't allow array as values. + parameters[ua] = value.sort().join(', '); + } + } + + return parameters; + } + + private reportCommandRunAnalytics(analytics: AnalyticsCollector): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const internalMethods = (this.context.yargsInstance as any).getInternalMethods(); + // $0 generate component [name] -> generate_component + // $0 add -> add + const fullCommand = (internalMethods.getUsageInstance().getUsage()[0][0] as string) + .split(' ') + .filter((x) => { + const code = x.charCodeAt(0); + + return code >= 97 && code <= 122; + }) + .join('_'); + + analytics.reportCommandRunEvent(fullCommand); + } + + private reportWorkspaceInfoAnalytics(analytics: AnalyticsCollector): void { + const { workspace } = this.context; + if (!workspace) { + return; + } + + let applicationProjectsCount = 0; + let librariesProjectsCount = 0; + for (const project of workspace.projects.values()) { + switch (project.extensions['projectType']) { + case 'application': + applicationProjectsCount++; + break; + case 'library': + librariesProjectsCount++; + break; + } + } + + analytics.reportWorkspaceInfoEvent({ + [EventCustomMetric.AllProjectsCount]: librariesProjectsCount + applicationProjectsCount, + [EventCustomMetric.ApplicationProjectsCount]: applicationProjectsCount, + [EventCustomMetric.LibraryProjectsCount]: librariesProjectsCount, + }); + } +} + +/** + * Creates an known command module error. + * This is used so during executation we can filter between known validation error and real non handled errors. + */ +export class CommandModuleError extends Error {} diff --git a/packages/angular/cli/src/command-builder/command-runner.ts b/packages/angular/cli/src/command-builder/command-runner.ts new file mode 100644 index 000000000000..cb4ab2c8467e --- /dev/null +++ b/packages/angular/cli/src/command-builder/command-runner.ts @@ -0,0 +1,165 @@ +/** + * @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.dev/license + */ + +import { logging } from '@angular-devkit/core'; +import yargs from 'yargs'; +import { Parser as yargsParser } from 'yargs/helpers'; +import { + CommandConfig, + CommandNames, + RootCommands, + RootCommandsAliases, +} from '../commands/command-config'; +import { colors } from '../utilities/color'; +import { AngularWorkspace, getWorkspace } from '../utilities/config'; +import { assertIsError } from '../utilities/error'; +import { PackageManagerUtils } from '../utilities/package-manager'; +import { VERSION } from '../utilities/version'; +import { CommandContext, CommandModuleError } from './command-module'; +import { + CommandModuleConstructor, + addCommandModuleToYargs, + demandCommandFailureMessage, +} from './utilities/command'; +import { jsonHelpUsage } from './utilities/json-help'; +import { createNormalizeOptionsMiddleware } from './utilities/normalize-options-middleware'; + +export async function runCommand(args: string[], logger: logging.Logger): Promise { + const { + $0, + _, + help = false, + jsonHelp = false, + getYargsCompletions = false, + ...rest + } = yargsParser(args, { + boolean: ['help', 'json-help', 'get-yargs-completions'], + alias: { 'collection': 'c' }, + }); + + // When `getYargsCompletions` is true the scriptName 'ng' at index 0 is not removed. + const positional = getYargsCompletions ? _.slice(1) : _; + + let workspace: AngularWorkspace | undefined; + let globalConfiguration: AngularWorkspace; + try { + [workspace, globalConfiguration] = await Promise.all([ + getWorkspace('local'), + getWorkspace('global'), + ]); + } catch (e) { + assertIsError(e); + logger.fatal(e.message); + + return 1; + } + + const root = workspace?.basePath ?? process.cwd(); + const localYargs = yargs(args); + + const context: CommandContext = { + globalConfiguration, + workspace, + logger, + currentDirectory: process.cwd(), + yargsInstance: localYargs, + root, + packageManager: new PackageManagerUtils({ globalConfiguration, workspace, root }), + args: { + positional: positional.map((v) => v.toString()), + options: { + help, + jsonHelp, + getYargsCompletions, + ...rest, + }, + }, + }; + + for (const CommandModule of await getCommandsToRegister(positional[0])) { + addCommandModuleToYargs(CommandModule, context); + } + + if (jsonHelp) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const usageInstance = (localYargs as any).getInternalMethods().getUsageInstance(); + usageInstance.help = () => jsonHelpUsage(localYargs); + } + + // Add default command to support version option when no subcommand is specified + localYargs.command('*', false, (builder) => + builder.version('version', 'Show Angular CLI version.', VERSION.full), + ); + + await localYargs + .scriptName('ng') + // https://github.com/yargs/yargs/blob/main/docs/advanced.md#customizing-yargs-parser + .parserConfiguration({ + 'populate--': true, + 'unknown-options-as-args': false, + 'dot-notation': false, + 'boolean-negation': true, + 'strip-aliased': true, + 'strip-dashed': true, + 'camel-case-expansion': false, + }) + .option('json-help', { + describe: 'Show help in JSON format.', + implies: ['help'], + hidden: true, + type: 'boolean', + }) + .help('help', 'Shows a help message for this command in the console.') + // A complete list of strings can be found: https://github.com/yargs/yargs/blob/main/locales/en.json + .updateStrings({ + 'Commands:': colors.cyan('Commands:'), + 'Options:': colors.cyan('Options:'), + 'Positionals:': colors.cyan('Arguments:'), + 'deprecated': colors.yellow('deprecated'), + 'deprecated: %s': colors.yellow('deprecated:') + ' %s', + 'Did you mean %s?': 'Unknown command. Did you mean %s?', + }) + .epilogue('For more information, see https://angular.dev/cli/.\n') + .demandCommand(1, demandCommandFailureMessage) + .recommendCommands() + .middleware(createNormalizeOptionsMiddleware(localYargs)) + .version(false) + .showHelpOnFail(false) + .strict() + .fail((msg, err) => { + throw msg + ? // Validation failed example: `Unknown argument:` + new CommandModuleError(msg) + : // Unknown exception, re-throw. + err; + }) + .wrap(localYargs.terminalWidth()) + .parseAsync(); + + return +(process.exitCode ?? 0); +} + +/** + * Get the commands that need to be registered. + * @returns One or more command factories that needs to be registered. + */ +async function getCommandsToRegister( + commandName: string | number, +): Promise { + const commands: CommandConfig[] = []; + if (commandName in RootCommands) { + commands.push(RootCommands[commandName as CommandNames]); + } else if (commandName in RootCommandsAliases) { + commands.push(RootCommandsAliases[commandName]); + } else { + // Unknown command, register every possible command. + Object.values(RootCommands).forEach((c) => commands.push(c)); + } + + return Promise.all(commands.map((command) => command.factory().then((m) => m.default))); +} diff --git a/packages/angular/cli/src/command-builder/schematics-command-module.ts b/packages/angular/cli/src/command-builder/schematics-command-module.ts new file mode 100644 index 000000000000..ef317700d1a6 --- /dev/null +++ b/packages/angular/cli/src/command-builder/schematics-command-module.ts @@ -0,0 +1,413 @@ +/** + * @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.dev/license + */ + +import { JsonValue, normalize as devkitNormalize, schema } from '@angular-devkit/core'; +import { Collection, UnsuccessfulWorkflowExecution, formats } from '@angular-devkit/schematics'; +import { + FileSystemCollectionDescription, + FileSystemSchematicDescription, + NodeWorkflow, +} from '@angular-devkit/schematics/tools'; +import { relative } from 'node:path'; +import { Argv } from 'yargs'; +import { isPackageNameSafeForAnalytics } from '../analytics/analytics'; +import { EventCustomDimension } from '../analytics/analytics-parameters'; +import { getProjectByCwd, getSchematicDefaults } from '../utilities/config'; +import { assertIsError } from '../utilities/error'; +import { memoize } from '../utilities/memoize'; +import { isTTY } from '../utilities/tty'; +import { + CommandModule, + CommandModuleImplementation, + CommandScope, + Options, + OtherOptions, +} from './command-module'; +import { Option, parseJsonSchemaToOptions } from './utilities/json-schema'; +import { SchematicEngineHost } from './utilities/schematic-engine-host'; +import { subscribeToWorkflow } from './utilities/schematic-workflow'; + +export const DEFAULT_SCHEMATICS_COLLECTION = '@schematics/angular'; + +export interface SchematicsCommandArgs { + interactive: boolean; + force: boolean; + 'dry-run': boolean; + defaults: boolean; +} + +export interface SchematicsExecutionOptions extends Options { + packageRegistry?: string; +} + +export abstract class SchematicsCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + override scope = CommandScope.In; + protected readonly allowPrivateSchematics: boolean = false; + + async builder(argv: Argv): Promise> { + return argv + .option('interactive', { + describe: 'Enable interactive input prompts.', + type: 'boolean', + default: true, + }) + .option('dry-run', { + describe: 'Run through and reports activity without writing out results.', + type: 'boolean', + alias: ['d'], + default: false, + }) + .option('defaults', { + describe: 'Disable interactive input prompts for options with a default.', + type: 'boolean', + default: false, + }) + .option('force', { + describe: 'Force overwriting of existing files.', + type: 'boolean', + default: false, + }) + .strict(); + } + + /** Get schematic schema options.*/ + protected async getSchematicOptions( + collection: Collection, + schematicName: string, + workflow: NodeWorkflow, + ): Promise { + const schematic = collection.createSchematic(schematicName, true); + const { schemaJson } = schematic.description; + + if (!schemaJson) { + return []; + } + + return parseJsonSchemaToOptions(workflow.registry, schemaJson); + } + + @memoize + protected getOrCreateWorkflowForBuilder(collectionName: string): NodeWorkflow { + return new NodeWorkflow(this.context.root, { + resolvePaths: this.getResolvePaths(collectionName), + engineHostCreator: (options) => new SchematicEngineHost(options.resolvePaths), + }); + } + + @memoize + protected async getOrCreateWorkflowForExecution( + collectionName: string, + options: SchematicsExecutionOptions, + ): Promise { + const { logger, root, packageManager } = this.context; + const { force, dryRun, packageRegistry } = options; + + const workflow = new NodeWorkflow(root, { + force, + dryRun, + packageManager: packageManager.name, + // A schema registry is required to allow customizing addUndefinedDefaults + registry: new schema.CoreSchemaRegistry(formats.standardFormats), + packageRegistry, + resolvePaths: this.getResolvePaths(collectionName), + schemaValidation: true, + optionTransforms: [ + // Add configuration file defaults + async (schematic, current) => { + const projectName = + typeof current?.project === 'string' ? current.project : this.getProjectName(); + + return { + ...(await getSchematicDefaults(schematic.collection.name, schematic.name, projectName)), + ...current, + }; + }, + ], + engineHostCreator: (options) => new SchematicEngineHost(options.resolvePaths), + }); + + workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults); + workflow.registry.useXDeprecatedProvider((msg) => logger.warn(msg)); + workflow.registry.addSmartDefaultProvider('projectName', () => this.getProjectName()); + + const workingDir = devkitNormalize(relative(this.context.root, process.cwd())); + workflow.registry.addSmartDefaultProvider('workingDirectory', () => + workingDir === '' ? undefined : workingDir, + ); + + workflow.engineHost.registerOptionsTransform(async (schematic, options) => { + const { + collection: { name: collectionName }, + name: schematicName, + } = schematic; + + const analytics = isPackageNameSafeForAnalytics(collectionName) + ? await this.getAnalytics() + : undefined; + + analytics?.reportSchematicRunEvent({ + [EventCustomDimension.SchematicCollectionName]: collectionName, + [EventCustomDimension.SchematicName]: schematicName, + ...this.getAnalyticsParameters(options as unknown as {}), + }); + + return options; + }); + + if (options.interactive !== false && isTTY()) { + workflow.registry.usePromptProvider(async (definitions: Array) => { + let prompts: typeof import('@inquirer/prompts') | undefined; + const answers: Record = {}; + + for (const definition of definitions) { + if (options.defaults && definition.default !== undefined) { + continue; + } + + // Only load prompt package if needed + prompts ??= await import('@inquirer/prompts'); + + switch (definition.type) { + case 'confirmation': + answers[definition.id] = await prompts.confirm({ + message: definition.message, + default: definition.default as boolean | undefined, + }); + break; + case 'list': + if (!definition.items?.length) { + continue; + } + + answers[definition.id] = await ( + definition.multiselect ? prompts.checkbox : prompts.select + )({ + message: definition.message, + validate: (values) => { + if (!definition.validator) { + return true; + } + + return definition.validator(Object.values(values).map(({ value }) => value)); + }, + default: definition.multiselect ? undefined : definition.default, + choices: definition.items?.map((item) => + typeof item == 'string' + ? { + name: item, + value: item, + checked: + definition.multiselect && Array.isArray(definition.default) + ? definition.default?.includes(item) + : item === definition.default, + } + : { + ...item, + name: item.label, + value: item.value, + checked: + definition.multiselect && Array.isArray(definition.default) + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + definition.default?.includes(item.value as any) + : item.value === definition.default, + }, + ), + }); + break; + case 'input': { + let finalValue: JsonValue | undefined; + answers[definition.id] = await prompts.input({ + message: definition.message, + default: definition.default as string | undefined, + async validate(value) { + if (definition.validator === undefined) { + return true; + } + + let lastValidation: ReturnType = false; + for (const type of definition.propertyTypes) { + let potential; + switch (type) { + case 'string': + potential = String(value); + break; + case 'integer': + case 'number': + potential = Number(value); + break; + default: + potential = value; + break; + } + lastValidation = await definition.validator(potential); + + // Can be a string if validation fails + if (lastValidation === true) { + finalValue = potential; + + return true; + } + } + + return lastValidation; + }, + }); + + // Use validated value if present. + // This ensures the correct type is inserted into the final schema options. + if (finalValue !== undefined) { + answers[definition.id] = finalValue; + } + break; + } + } + } + + return answers; + }); + } + + return workflow; + } + + @memoize + protected async getSchematicCollections(): Promise> { + const getSchematicCollections = ( + configSection: Record | undefined, + ): Set | undefined => { + if (!configSection) { + return undefined; + } + + const { schematicCollections } = configSection; + if (Array.isArray(schematicCollections)) { + return new Set(schematicCollections); + } + + return undefined; + }; + + const { workspace, globalConfiguration } = this.context; + if (workspace) { + const project = getProjectByCwd(workspace); + if (project) { + const value = getSchematicCollections(workspace.getProjectCli(project)); + if (value) { + return value; + } + } + } + + const value = + getSchematicCollections(workspace?.getCli()) ?? + getSchematicCollections(globalConfiguration.getCli()); + if (value) { + return value; + } + + return new Set([DEFAULT_SCHEMATICS_COLLECTION]); + } + + protected parseSchematicInfo( + schematic: string | undefined, + ): [collectionName: string | undefined, schematicName: string | undefined] { + if (schematic?.includes(':')) { + const [collectionName, schematicName] = schematic.split(':', 2); + + return [collectionName, schematicName]; + } + + return [undefined, schematic]; + } + + protected async runSchematic(options: { + executionOptions: SchematicsExecutionOptions; + schematicOptions: OtherOptions; + collectionName: string; + schematicName: string; + }): Promise { + const { logger } = this.context; + const { schematicOptions, executionOptions, collectionName, schematicName } = options; + const workflow = await this.getOrCreateWorkflowForExecution(collectionName, executionOptions); + + if (!schematicName) { + throw new Error('schematicName cannot be undefined.'); + } + + const { unsubscribe, files } = subscribeToWorkflow(workflow, logger); + + try { + await workflow + .execute({ + collection: collectionName, + schematic: schematicName, + options: schematicOptions, + logger, + allowPrivate: this.allowPrivateSchematics, + }) + .toPromise(); + + if (!files.size) { + logger.info('Nothing to be done.'); + } + + if (executionOptions.dryRun) { + logger.warn(`\nNOTE: The "--dry-run" option means no changes were made.`); + } + } catch (err) { + // In case the workflow was not successful, show an appropriate error message. + if (err instanceof UnsuccessfulWorkflowExecution) { + // "See above" because we already printed the error. + logger.fatal('The Schematic workflow failed. See above.'); + } else { + assertIsError(err); + logger.fatal(err.message); + } + + return 1; + } finally { + unsubscribe(); + } + + return 0; + } + + private getProjectName(): string | undefined { + const { workspace } = this.context; + if (!workspace) { + return undefined; + } + + const projectName = getProjectByCwd(workspace); + if (projectName) { + return projectName; + } + + return undefined; + } + + private getResolvePaths(collectionName: string): string[] { + const { workspace, root } = this.context; + if (collectionName[0] === '.') { + // Resolve relative collections from the location of `angular.json` + return [root]; + } + + return workspace + ? // Workspace + collectionName === DEFAULT_SCHEMATICS_COLLECTION + ? // Favor __dirname for @schematics/angular to use the build-in version + [__dirname, process.cwd(), root] + : [process.cwd(), root, __dirname] + : // Global + [__dirname, process.cwd()]; + } +} diff --git a/packages/angular/cli/src/command-builder/utilities/command.ts b/packages/angular/cli/src/command-builder/utilities/command.ts new file mode 100644 index 000000000000..8b019aba9064 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/command.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.dev/license + */ + +import { Argv } from 'yargs'; +import { + CommandContext, + CommandModule, + CommandModuleError, + CommandModuleImplementation, + CommandScope, +} from '../command-module'; + +export const demandCommandFailureMessage = `You need to specify a command before moving on. Use '--help' to view the available commands.`; +export type CommandModuleConstructor = Partial & { + new (context: CommandContext): Partial & CommandModule; +}; + +export function addCommandModuleToYargs( + commandModule: U, + context: CommandContext, +): void { + const cmd = new commandModule(context); + const { + args: { + options: { jsonHelp }, + }, + workspace, + } = context; + + const describe = jsonHelp ? cmd.fullDescribe : cmd.describe; + + context.yargsInstance.command({ + command: cmd.command, + aliases: cmd.aliases, + describe: + // We cannot add custom fields in help, such as long command description which is used in AIO. + // Therefore, we get around this by adding a complex object as a string which we later parse when generating the help files. + typeof describe === 'object' ? JSON.stringify(describe) : describe, + deprecated: cmd.deprecated, + builder: (argv) => { + // Skip scope validation when running with '--json-help' since it's easier to generate the output for all commands this way. + const isInvalidScope = + !jsonHelp && + ((cmd.scope === CommandScope.In && !workspace) || + (cmd.scope === CommandScope.Out && workspace)); + + if (isInvalidScope) { + throw new CommandModuleError( + `This command is not available when running the Angular CLI ${ + workspace ? 'inside' : 'outside' + } a workspace.`, + ); + } + + return cmd.builder(argv) as Argv; + }, + handler: (args) => cmd.handler(args), + }); +} diff --git a/packages/angular/cli/src/command-builder/utilities/json-help.ts b/packages/angular/cli/src/command-builder/utilities/json-help.ts new file mode 100644 index 000000000000..0d5c6a53a1e6 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/json-help.ts @@ -0,0 +1,156 @@ +/** + * @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.dev/license + */ + +import { Argv } from 'yargs'; +import { FullDescribe } from '../command-module'; + +interface JsonHelpOption { + name: string; + type?: string; + deprecated: boolean | string; + aliases?: string[]; + default?: string; + required?: boolean; + positional?: number; + enum?: string[]; + description?: string; +} + +interface JsonHelpDescription { + shortDescription?: string; + longDescription?: string; + longDescriptionRelativePath?: string; +} + +interface JsonHelpSubcommand extends JsonHelpDescription { + name: string; + aliases: string[]; + deprecated: string | boolean; +} + +export interface JsonHelp extends JsonHelpDescription { + name: string; + command: string; + options: JsonHelpOption[]; + subcommands?: JsonHelpSubcommand[]; +} + +const yargsDefaultCommandRegExp = /^\$0|\*/; + +export function jsonHelpUsage(localYargs: Argv): string { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const localYargsInstance = localYargs as any; + const { + deprecatedOptions, + alias: aliases, + array, + string, + boolean, + number, + choices, + demandedOptions, + default: defaultVal, + hiddenOptions = [], + } = localYargsInstance.getOptions(); + + const internalMethods = localYargsInstance.getInternalMethods(); + const usageInstance = internalMethods.getUsageInstance(); + const context = internalMethods.getContext(); + const descriptions = usageInstance.getDescriptions(); + const groups = localYargsInstance.getGroups(); + const positional = groups[usageInstance.getPositionalGroupName()] as string[] | undefined; + const seen = new Set(); + const hidden = new Set(hiddenOptions); + const normalizeOptions: JsonHelpOption[] = []; + const allAliases = new Set([...Object.values(aliases).flat()]); + + // Reverted order of https://github.com/yargs/yargs/blob/971e351705f0fbc5566c6ed1dfd707fa65e11c0d/lib/usage.ts#L419-L424 + for (const [names, type] of [ + [number, 'number'], + [array, 'array'], + [string, 'string'], + [boolean, 'boolean'], + ]) { + for (const name of names) { + if (allAliases.has(name) || hidden.has(name) || seen.has(name)) { + // Ignore hidden, aliases and already visited option. + continue; + } + + seen.add(name); + const positionalIndex = positional?.indexOf(name) ?? -1; + const alias = aliases[name]; + + normalizeOptions.push({ + name, + type, + deprecated: deprecatedOptions[name], + aliases: alias?.length > 0 ? alias : undefined, + default: defaultVal[name], + required: demandedOptions[name], + enum: choices[name], + description: descriptions[name]?.replace('__yargsString__:', ''), + positional: positionalIndex >= 0 ? positionalIndex : undefined, + }); + } + } + + // https://github.com/yargs/yargs/blob/00e4ebbe3acd438e73fdb101e75b4f879eb6d345/lib/usage.ts#L124 + const subcommands = ( + usageInstance.getCommands() as [ + name: string, + description: string, + isDefault: boolean, + aliases: string[], + deprecated: string | boolean, + ][] + ) + .map(([name, rawDescription, isDefault, aliases, deprecated]) => ({ + name: name.split(' ', 1)[0].replace(yargsDefaultCommandRegExp, ''), + command: name.replace(yargsDefaultCommandRegExp, ''), + default: isDefault || undefined, + ...parseDescription(rawDescription), + aliases, + deprecated, + })) + .sort((a, b) => a.name.localeCompare(b.name)); + + const [command, rawDescription] = usageInstance.getUsage()[0] ?? []; + const defaultSubCommand = subcommands.find((x) => x.default)?.command ?? ''; + const otherSubcommands = subcommands.filter((s) => !s.default); + + const output: JsonHelp = { + name: [...context.commands].pop(), + command: `${command?.replace(yargsDefaultCommandRegExp, localYargsInstance['$0'])}${defaultSubCommand}`, + ...parseDescription(rawDescription), + options: normalizeOptions.sort((a, b) => a.name.localeCompare(b.name)), + subcommands: otherSubcommands.length ? otherSubcommands : undefined, + }; + + return JSON.stringify(output, undefined, 2); +} + +function parseDescription(rawDescription: string): JsonHelpDescription { + try { + const { + longDescription, + describe: shortDescription, + longDescriptionRelativePath, + } = JSON.parse(rawDescription) as FullDescribe; + + return { + shortDescription, + longDescriptionRelativePath, + longDescription, + }; + } catch { + return { + shortDescription: rawDescription, + }; + } +} diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema.ts b/packages/angular/cli/src/command-builder/utilities/json-schema.ts new file mode 100644 index 000000000000..0a4215be8eed --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/json-schema.ts @@ -0,0 +1,493 @@ +/** + * @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.dev/license + */ + +import { isJsonObject, json, strings } from '@angular-devkit/core'; +import type { Arguments, Argv, PositionalOptions, Options as YargsOptions } from 'yargs'; +import { EventCustomDimension } from '../../analytics/analytics-parameters'; + +/** + * An option description that can be used by yargs to create a command. + * See: https://github.com/yargs/yargs/blob/main/docs/options.mjs + */ +export interface Option extends YargsOptions { + /** + * The name of the option. + */ + name: string; + + /** + * Whether this option is required or not. + */ + required?: boolean; + + /** + * Format field of this option. + */ + format?: string; + + /** + * Whether this option should be hidden from the help output. It will still show up in JSON help. + */ + hidden?: boolean; + + /** + * If this option can be used as an argument, the position of the argument. Otherwise omitted. + */ + positional?: number; + + /** + * Whether or not to report this option to the Angular Team, and which custom field to use. + * If this is falsey, do not report this option. + */ + userAnalytics?: string; + + /** + * Type of the values in a key/value pair field. + */ + itemValueType?: 'string'; +} + +/** + * A Yargs check function that validates that the given options are in the form of `key=value`. + * @param keyValuePairOptions A set of options that should be in the form of `key=value`. + * @param args The parsed arguments. + * @returns `true` if the options are valid, otherwise an error is thrown. + */ +function checkStringMap(keyValuePairOptions: Set, args: Arguments): boolean { + for (const key of keyValuePairOptions) { + const value = args[key]; + if (!Array.isArray(value)) { + // Value has been parsed. + continue; + } + + for (const pair of value) { + if (pair === undefined) { + continue; + } + + if (!pair.includes('=')) { + throw new Error( + `Invalid value for argument: ${key}, Given: '${pair}', Expected key=value pair`, + ); + } + } + } + + return true; +} + +/** + * A Yargs coerce function that converts an array of `key=value` strings to an object. + * @param value An array of `key=value` strings. + * @returns An object with the keys and values from the input array. + */ +function coerceToStringMap( + value: (string | undefined)[], +): Record | (string | undefined)[] { + const stringMap: Record = {}; + for (const pair of value) { + // This happens when the flag isn't passed at all. + if (pair === undefined) { + continue; + } + + const eqIdx = pair.indexOf('='); + if (eqIdx === -1) { + // In the case it is not valid skip processing this option and handle the error in `checkStringMap` + return value; + } + + const key = pair.slice(0, eqIdx); + stringMap[key] = pair.slice(eqIdx + 1); + } + + return stringMap; +} + +/** + * Checks if a JSON schema node represents a string map. + * A string map is an object with `additionalProperties` of type `string`. + * @param node The JSON schema node to check. + * @returns `true` if the node represents a string map, otherwise `false`. + */ +function isStringMap(node: json.JsonObject): boolean { + // Exclude fields with more specific kinds of properties. + if (node.properties || node.patternProperties) { + return false; + } + + // Restrict to additionalProperties with string values. + return ( + json.isJsonObject(node.additionalProperties) && + !node.additionalProperties.enum && + node.additionalProperties.type === 'string' + ); +} + +const SUPPORTED_PRIMITIVE_TYPES = new Set(['boolean', 'number', 'string']); + +/** + * Checks if a string is a supported primitive type. + * @param value The string to check. + * @returns `true` if the string is a supported primitive type, otherwise `false`. + */ +function isSupportedPrimitiveType(value: string): boolean { + return SUPPORTED_PRIMITIVE_TYPES.has(value); +} + +/** + * Recursively checks if a JSON schema for an array's items is a supported primitive type. + * It supports `oneOf` and `anyOf` keywords. + * @param schema The JSON schema for the array's items. + * @returns `true` if the schema is a supported primitive type, otherwise `false`. + */ +function isSupportedArrayItemSchema(schema: json.JsonObject): boolean { + if (typeof schema.type === 'string' && isSupportedPrimitiveType(schema.type)) { + return true; + } + + if (json.isJsonArray(schema.enum)) { + return true; + } + + if (json.isJsonArray(schema.items)) { + return schema.items.some((item) => isJsonObject(item) && isSupportedArrayItemSchema(item)); + } + + if ( + json.isJsonArray(schema.oneOf) && + schema.oneOf.some((item) => isJsonObject(item) && isSupportedArrayItemSchema(item)) + ) { + return true; + } + + if ( + json.isJsonArray(schema.anyOf) && + schema.anyOf.some((item) => isJsonObject(item) && isSupportedArrayItemSchema(item)) + ) { + return true; + } + + return false; +} + +/** + * Gets the supported types for a JSON schema node. + * @param current The JSON schema node to get the supported types for. + * @returns An array of supported types. + */ +function getSupportedTypes( + current: json.JsonObject, +): ReadonlyArray<'string' | 'number' | 'boolean' | 'array' | 'object'> { + const typeSet = json.schema.getTypesOfSchema(current); + + if (typeSet.size === 0) { + return []; + } + + return [...typeSet].filter((type) => { + switch (type) { + case 'boolean': + case 'number': + case 'string': + return true; + case 'array': + return isJsonObject(current.items) && isSupportedArrayItemSchema(current.items); + case 'object': + return isStringMap(current); + default: + return false; + } + }) as ReadonlyArray<'string' | 'number' | 'boolean' | 'array' | 'object'>; +} + +/** + * Gets the enum values for a JSON schema node. + * @param current The JSON schema node to get the enum values for. + * @returns An array of enum values. + */ +function getEnumValues( + current: json.JsonObject, +): ReadonlyArray | undefined { + if (json.isJsonArray(current.enum)) { + return current.enum.sort() as ReadonlyArray; + } + + if (isJsonObject(current.items)) { + const enumValues = getEnumValues(current.items); + if (enumValues?.length) { + return enumValues; + } + } + + if (typeof current.type === 'string' && isSupportedPrimitiveType(current.type)) { + return []; + } + + const subSchemas = + (json.isJsonArray(current.oneOf) && current.oneOf) || + (json.isJsonArray(current.anyOf) && current.anyOf); + + if (subSchemas) { + // Find the first enum. + for (const subSchema of subSchemas) { + if (isJsonObject(subSchema)) { + const enumValues = getEnumValues(subSchema); + if (enumValues) { + return enumValues; + } + } + } + } + + return []; +} + +/** + * Gets the default value for a JSON schema node. + * @param current The JSON schema node to get the default value for. + * @param type The type of the JSON schema node. + * @returns The default value, or `undefined` if there is no default value. + */ +function getDefaultValue( + current: json.JsonObject, + type: string, +): string | number | boolean | unknown[] | undefined { + const defaultValue = current.default; + if (defaultValue === undefined) { + return undefined; + } + + if (type === 'array') { + return Array.isArray(defaultValue) && defaultValue.length > 0 ? defaultValue : undefined; + } + + if (typeof defaultValue === type) { + return defaultValue as string | number | boolean; + } + + return undefined; +} + +/** + * Gets the aliases for a JSON schema node. + * @param current The JSON schema node to get the aliases for. + * @returns An array of aliases. + */ +function getAliases(current: json.JsonObject): string[] { + if (json.isJsonArray(current.aliases)) { + return [...current.aliases].map(String); + } + + if (current.alias) { + return [String(current.alias)]; + } + + return []; +} + +/** + * Parses a JSON schema to a list of options that can be used by yargs. + * + * @param registry A schema registry to use for flattening the schema. + * @param schema The JSON schema to parse. + * @param interactive Whether to prompt the user for missing options. + * @returns A list of options. + * + * @note The schema definition are not resolved at this stage. This means that `$ref` will not be resolved, + * and custom keywords like `x-prompt` will not be processed. + */ +export async function parseJsonSchemaToOptions( + registry: json.schema.SchemaRegistry, + schema: json.JsonObject, + interactive = true, +): Promise { + const options: Option[] = []; + + function visitor( + current: json.JsonObject | json.JsonArray, + pointer: json.schema.JsonPointer, + parentSchema?: json.JsonObject | json.JsonArray, + ) { + if ( + !parentSchema || + json.isJsonArray(current) || + pointer.split(/\/(?:properties|items|definitions)\//g).length > 2 + ) { + // Ignore root, arrays, and subitems. + return; + } + + if (pointer.includes('/not/')) { + // We don't support anyOf/not. + throw new Error('The "not" keyword is not supported in JSON Schema.'); + } + + const ptr = json.schema.parseJsonPointer(pointer); + if (ptr[ptr.length - 2] !== 'properties') { + // Skip any non-property items. + return; + } + const name = ptr.at(-1) as string; + + const types = getSupportedTypes(current); + + if (types.length === 0) { + // This means it's not usable on the command line. e.g. an Object. + return; + } + + const [type] = types; + const $default = current.$default; + const $defaultIndex = + isJsonObject($default) && $default['$source'] === 'argv' ? $default['index'] : undefined; + const positional: number | undefined = + typeof $defaultIndex === 'number' ? $defaultIndex : undefined; + + let required = json.isJsonArray(schema.required) && schema.required.includes(name); + if (required && interactive && current['x-prompt']) { + required = false; + } + + const visible = current.visible !== false; + const xDeprecated = current['x-deprecated']; + const enumValues = getEnumValues(current); + + const option: Option = { + name, + description: String(current.description ?? ''), + default: getDefaultValue(current, type), + choices: enumValues?.length ? enumValues : undefined, + required, + alias: getAliases(current), + format: typeof current.format === 'string' ? current.format : undefined, + hidden: !!current.hidden || !visible, + userAnalytics: + typeof current['x-user-analytics'] === 'string' ? current['x-user-analytics'] : undefined, + deprecated: xDeprecated === true || typeof xDeprecated === 'string' ? xDeprecated : undefined, + positional, + ...(type === 'object' + ? { + type: 'array', + itemValueType: 'string', + } + : { + type, + }), + }; + + options.push(option); + } + + const flattenedSchema = await registry.ɵflatten(schema); + json.schema.visitJsonSchema(flattenedSchema, visitor); + + // Sort by positional and name. + return options.sort((a, b) => { + if (a.positional) { + return b.positional ? a.positional - b.positional : a.name.localeCompare(b.name); + } else if (b.positional) { + return -1; + } + + return a.name.localeCompare(b.name); + }); +} + +/** + * Adds schema options to a command also this keeps track of options that are required for analytics. + * **Note:** This method should be called from the command bundler method. + * + * @returns A map from option name to analytics configuration. + */ +export function addSchemaOptionsToCommand( + localYargs: Argv, + options: Option[], + includeDefaultValues: boolean, +): Map { + const booleanOptionsWithNoPrefix = new Set(); + const keyValuePairOptions = new Set(); + const optionsWithAnalytics = new Map(); + + for (const option of options) { + const { + default: defaultVal, + positional, + deprecated, + description, + alias, + userAnalytics, + type, + itemValueType, + hidden, + name, + choices, + } = option; + + let dashedName = strings.dasherize(name); + + // Handle options which have been defined in the schema with `no` prefix. + if (type === 'boolean' && dashedName.startsWith('no-')) { + dashedName = dashedName.slice(3); + booleanOptionsWithNoPrefix.add(dashedName); + } + + if (itemValueType) { + keyValuePairOptions.add(dashedName); + } + + const sharedOptions: YargsOptions & PositionalOptions = { + alias, + hidden, + description, + deprecated, + choices, + coerce: itemValueType ? coerceToStringMap : undefined, + // This should only be done when `--help` is used otherwise default will override options set in angular.json. + ...(includeDefaultValues ? { default: defaultVal } : {}), + }; + + if (positional === undefined) { + localYargs = localYargs.option(dashedName, { + array: itemValueType ? true : undefined, + type: itemValueType ?? type, + ...sharedOptions, + }); + } else { + localYargs = localYargs.positional(dashedName, { + type: type === 'array' || type === 'count' ? 'string' : type, + ...sharedOptions, + }); + } + + // Record option of analytics. + if (userAnalytics !== undefined) { + optionsWithAnalytics.set(name, userAnalytics as EventCustomDimension); + } + } + + // Valid key/value options + if (keyValuePairOptions.size) { + localYargs.check(checkStringMap.bind(null, keyValuePairOptions), false); + } + + // Handle options which have been defined in the schema with `no` prefix. + if (booleanOptionsWithNoPrefix.size) { + localYargs.middleware((options: Arguments) => { + for (const key of booleanOptionsWithNoPrefix) { + if (key in options) { + options[`no-${key}`] = !options[key]; + delete options[key]; + } + } + }, false); + } + + return optionsWithAnalytics; +} diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts new file mode 100644 index 000000000000..d311373d69f0 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.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.dev/license + */ + +import { JsonObject, schema } from '@angular-devkit/core'; +import yargs from 'yargs'; + +import { addSchemaOptionsToCommand, parseJsonSchemaToOptions } from './json-schema'; + +describe('parseJsonSchemaToOptions', () => { + describe('without required fields in schema', () => { + const parse = async (args: string[]) => { + // Yargs only exposes the parse errors as proper errors when using the + // callback syntax. This unwraps that ugly workaround so tests can just + // use simple .toThrow/.toEqual assertions. + return localYargs.parseAsync(args); + }; + + let localYargs: yargs.Argv; + beforeEach(async () => { + // Create a fresh yargs for each call. The yargs object is stateful and + // calling .parse multiple times on the same instance isn't safe. + localYargs = yargs().exitProcess(false).strict().fail(false).wrap(1_000); + const jsonSchema = { + 'type': 'object', + 'properties': { + 'maxSize': { + 'type': 'number', + }, + 'ssr': { + 'type': 'string', + 'enum': ['always', 'surprise-me', 'never'], + }, + 'arrayWithChoices': { + 'type': 'array', + 'default': ['default-array'], + 'items': { + 'type': 'string', + 'enum': ['always', 'never', 'default-array'], + }, + }, + 'extendable': { + 'type': 'object', + 'properties': {}, + 'additionalProperties': { + 'type': 'string', + }, + }, + 'someDefine': { + 'type': 'object', + 'additionalProperties': { + 'type': 'string', + }, + }, + 'arrayWithChoicesInOneOf': { + 'type': 'array', + 'items': { + 'oneOf': [ + { + 'enum': ['default', 'verbose'], + }, + { + 'type': 'array', + 'minItems': 1, + 'maxItems': 2, + 'items': [ + { + 'enum': ['default', 'verbose'], + }, + { + 'type': 'object', + }, + ], + }, + ], + }, + }, + 'arrayWithComplexAnyOf': { + 'type': 'array', + 'items': { + 'oneOf': [ + { + 'anyOf': [ + { + 'type': 'string', + }, + { + 'enum': ['default', 'verbose'], + }, + ], + }, + { + 'type': 'array', + 'minItems': 1, + 'maxItems': 2, + 'items': [ + { + 'anyOf': [ + { + 'type': 'string', + }, + { + 'enum': ['default', 'verbose'], + }, + ], + }, + { + 'type': 'object', + }, + ], + }, + ], + }, + }, + }, + }; + const registry = new schema.CoreSchemaRegistry(); + const options = await parseJsonSchemaToOptions( + registry, + jsonSchema as unknown as JsonObject, + false, + ); + addSchemaOptionsToCommand(localYargs, options, true); + }); + + describe('type=number', () => { + it('parses valid option value', async () => { + expect(await parse(['--max-size', '42'])).toEqual( + jasmine.objectContaining({ + 'maxSize': 42, + }), + ); + }); + }); + + describe('type=array, enum', () => { + it('parses valid option value', async () => { + expect( + await parse(['--arrayWithChoices', 'always', '--arrayWithChoices', 'never']), + ).toEqual( + jasmine.objectContaining({ + 'arrayWithChoices': ['always', 'never'], + }), + ); + }); + + it('rejects non-enum values', async () => { + await expectAsync(parse(['--arrayWithChoices', 'yes'])).toBeRejectedWithError( + /Argument: array-with-choices, Given: "yes", Choices:/, + ); + }); + + it('should add default value to help', async () => { + expect(await localYargs.getHelp()).toContain('[default: ["default-array"]]'); + }); + }); + + describe('type=array, enum in oneOf', () => { + it('parses valid option value', async () => { + expect( + await parse([ + '--arrayWithChoicesInOneOf', + 'default', + '--arrayWithChoicesInOneOf', + 'verbose', + ]), + ).toEqual( + jasmine.objectContaining({ + 'arrayWithChoicesInOneOf': ['default', 'verbose'], + }), + ); + }); + + it('rejects non-enum values', async () => { + await expectAsync(parse(['--arrayWithChoicesInOneOf', 'yes'])).toBeRejectedWithError( + /Argument: array-with-choices-in-one-of, Given: "yes", Choices:/, + ); + }); + }); + + describe('type=array, anyOf', () => { + it('parses valid option value', async () => { + expect( + await parse([ + '--arrayWithComplexAnyOf', + 'default', + '--arrayWithComplexAnyOf', + 'something-else', + ]), + ).toEqual( + jasmine.objectContaining({ + 'arrayWithComplexAnyOf': ['default', 'something-else'], + }), + ); + }); + }); + + describe('type=string, enum', () => { + it('parses valid option value', async () => { + expect(await parse(['--ssr', 'never'])).toEqual( + jasmine.objectContaining({ + 'ssr': 'never', + }), + ); + }); + + it('rejects non-enum values', async () => { + await expectAsync(parse(['--ssr', 'yes'])).toBeRejectedWithError( + /Argument: ssr, Given: "yes", Choices:/, + ); + }); + }); + + describe('type=object', () => { + it('ignores fields that define specific properties', async () => { + await expectAsync(parse(['--extendable', 'a=b'])).toBeRejectedWithError( + /Unknown argument: extendable/, + ); + }); + + it('rejects invalid values for string maps', async () => { + await expectAsync(parse(['--some-define', 'foo'])).toBeRejectedWithError( + /Invalid value for argument: some-define, Given: 'foo', Expected key=value pair/, + ); + await expectAsync(parse(['--some-define', '42'])).toBeRejectedWithError( + /Invalid value for argument: some-define, Given: '42', Expected key=value pair/, + ); + }); + + it('aggregates an object value', async () => { + expect( + await parse([ + '--some-define', + 'A_BOOLEAN=true', + '--some-define', + 'AN_INTEGER=42', + // Ensure we can handle '=' inside of string values. + '--some-define=A_STRING="❤️=❤️"', + '--some-define', + 'AN_UNQUOTED_STRING=❤️=❤️', + ]), + ).toEqual( + jasmine.objectContaining({ + 'someDefine': { + 'A_BOOLEAN': 'true', + 'AN_INTEGER': '42', + 'A_STRING': '"❤️=❤️"', + 'AN_UNQUOTED_STRING': '❤️=❤️', + }, + }), + ); + }); + }); + }); + + describe('with required positional argument', () => { + it('marks the required argument as required', async () => { + const jsonSchema = { + '$id': 'FakeSchema', + 'title': 'Fake Schema', + 'type': 'object', + 'required': ['a'], + 'properties': { + 'b': { + 'type': 'string', + 'description': 'b.', + '$default': { + '$source': 'argv', + 'index': 1, + }, + }, + 'a': { + 'type': 'string', + 'description': 'a.', + '$default': { + '$source': 'argv', + 'index': 0, + }, + }, + 'optC': { + 'type': 'string', + 'description': 'optC', + }, + 'optA': { + 'type': 'string', + 'description': 'optA', + }, + 'optB': { + 'type': 'string', + 'description': 'optB', + }, + }, + }; + const registry = new schema.CoreSchemaRegistry(); + const options = await parseJsonSchemaToOptions(registry, jsonSchema, /* interactive= */ true); + + expect(options.find((opt) => opt.name === 'a')).toEqual( + jasmine.objectContaining({ + name: 'a', + positional: 0, + required: true, + }), + ); + expect(options.find((opt) => opt.name === 'b')).toEqual( + jasmine.objectContaining({ + name: 'b', + positional: 1, + required: false, + }), + ); + }); + }); +}); diff --git a/packages/angular/cli/src/command-builder/utilities/normalize-options-middleware.ts b/packages/angular/cli/src/command-builder/utilities/normalize-options-middleware.ts new file mode 100644 index 000000000000..792f09f7a97b --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/normalize-options-middleware.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.dev/license + */ + +import type { Arguments, Argv } from 'yargs'; + +/** + * A Yargs middleware that normalizes non Array options when the argument has been provided multiple times. + * + * By default, when an option is non array and it is provided multiple times in the command line, yargs + * will not override it's value but instead it will be changed to an array unless `duplicate-arguments-array` is disabled. + * But this option also have an effect on real array options which isn't desired. + * + * See: https://github.com/yargs/yargs-parser/pull/163#issuecomment-516566614 + */ +export function createNormalizeOptionsMiddleware(localeYargs: Argv): (args: Arguments) => void { + return (args: Arguments) => { + // `getOptions` is not included in the types even though it's public API. + // https://github.com/yargs/yargs/issues/2098 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { array } = (localeYargs as any).getOptions(); + const arrayOptions = new Set(array); + + for (const [key, value] of Object.entries(args)) { + if (key !== '_' && Array.isArray(value) && !arrayOptions.has(key)) { + const newValue = value.pop(); + // eslint-disable-next-line no-console + console.warn( + `Option '${key}' has been specified multiple times. The value '${newValue}' will be used.`, + ); + args[key] = newValue; + } + } + }; +} diff --git a/packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts b/packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts new file mode 100644 index 000000000000..25b723c467a2 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/schematic-engine-host.ts @@ -0,0 +1,231 @@ +/** + * @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.dev/license + */ + +import { RuleFactory, SchematicsException, Tree } from '@angular-devkit/schematics'; +import { FileSystemCollectionDesc, NodeModulesEngineHost } from '@angular-devkit/schematics/tools'; +import { parse as parseJson } from 'jsonc-parser'; +import { readFileSync } from 'node:fs'; +import { Module, createRequire } from 'node:module'; +import { dirname, resolve } from 'node:path'; +import { Script } from 'node:vm'; +import { assertIsError } from '../../utilities/error'; + +/** + * Environment variable to control schematic package redirection + */ +const schematicRedirectVariable = process.env['NG_SCHEMATIC_REDIRECT']?.toLowerCase(); + +function shouldWrapSchematic( + schematicFile: string, + schematicEncapsulation: boolean | undefined, +): boolean { + // Check environment variable if present + switch (schematicRedirectVariable) { + case '0': + case 'false': + case 'off': + case 'none': + return false; + case 'all': + return true; + } + + const normalizedSchematicFile = schematicFile.replace(/\\/g, '/'); + // Never wrap the internal update schematic when executed directly + // It communicates with the update command via `global` + // But we still want to redirect schematics located in `@angular/cli/node_modules`. + if ( + normalizedSchematicFile.includes('node_modules/@angular/cli/') && + !normalizedSchematicFile.includes('node_modules/@angular/cli/node_modules/') + ) { + return false; + } + + // @angular/pwa uses dynamic imports which causes `[1] 2468039 segmentation fault` when wrapped. + // We should remove this when make `importModuleDynamically` work. + // See: https://nodejs.org/docs/latest-v14.x/api/vm.html + if (normalizedSchematicFile.includes('@angular/pwa')) { + return false; + } + + // Check for first-party Angular schematic packages + // Angular schematics are safe to use in the wrapped VM context + const isFirstParty = /\/node_modules\/@(?:angular|schematics|nguniversal)\//.test( + normalizedSchematicFile, + ); + + // Use value of defined option if present, otherwise default to first-party usage. + return schematicEncapsulation ?? isFirstParty; +} + +export class SchematicEngineHost extends NodeModulesEngineHost { + protected override _resolveReferenceString( + refString: string, + parentPath: string, + collectionDescription?: FileSystemCollectionDesc, + ) { + const [path, name] = refString.split('#', 2); + // Mimic behavior of ExportStringRef class used in default behavior + const fullPath = path[0] === '.' ? resolve(parentPath ?? process.cwd(), path) : path; + + const referenceRequire = createRequire(__filename); + const schematicFile = referenceRequire.resolve(fullPath, { paths: [parentPath] }); + + if (shouldWrapSchematic(schematicFile, collectionDescription?.encapsulation)) { + const schematicPath = dirname(schematicFile); + + const moduleCache = new Map(); + const factoryInitializer = wrap( + schematicFile, + schematicPath, + moduleCache, + name || 'default', + ) as () => RuleFactory<{}>; + + const factory = factoryInitializer(); + if (!factory || typeof factory !== 'function') { + return null; + } + + return { ref: factory, path: schematicPath }; + } + + // All other schematics use default behavior + return super._resolveReferenceString(refString, parentPath, collectionDescription); + } +} + +/** + * Minimal shim modules for legacy deep imports of `@schematics/angular` + */ +const legacyModules: Record = { + '@schematics/angular/utility/config': { + getWorkspace(host: Tree) { + const path = '/.angular.json'; + const data = host.read(path); + if (!data) { + throw new SchematicsException(`Could not find (${path})`); + } + + return parseJson(data.toString(), [], { allowTrailingComma: true }); + }, + }, + '@schematics/angular/utility/project': { + buildDefaultPath(project: { sourceRoot?: string; root: string; projectType: string }): string { + const root = project.sourceRoot ? `/${project.sourceRoot}/` : `/${project.root}/src/`; + + return `${root}${project.projectType === 'application' ? 'app' : 'lib'}`; + }, + }, +}; + +/** + * Wrap a JavaScript file in a VM context to allow specific Angular dependencies to be redirected. + * This VM setup is ONLY intended to redirect dependencies. + * + * @param schematicFile A JavaScript schematic file path that should be wrapped. + * @param schematicDirectory A directory that will be used as the location of the JavaScript file. + * @param moduleCache A map to use for caching repeat module usage and proper `instanceof` support. + * @param exportName An optional name of a specific export to return. Otherwise, return all exports. + */ +function wrap( + schematicFile: string, + schematicDirectory: string, + moduleCache: Map, + exportName?: string, +): () => unknown { + const hostRequire = createRequire(__filename); + const schematicRequire = createRequire(schematicFile); + + const customRequire = function (id: string) { + if (legacyModules[id]) { + // Provide compatibility modules for older versions of @angular/cdk + return legacyModules[id]; + } else if (id.startsWith('schematics:')) { + // Schematics built-in modules use the `schematics` scheme (similar to the Node.js `node` scheme) + const builtinId = id.slice(11); + const builtinModule = loadBuiltinModule(builtinId); + if (!builtinModule) { + throw new Error( + `Unknown schematics built-in module '${id}' requested from schematic '${schematicFile}'`, + ); + } + + return builtinModule; + } else if (id.startsWith('@angular-devkit/') || id.startsWith('@schematics/')) { + // Files should not redirect `@angular/core` and instead use the direct + // dependency if available. This allows old major version migrations to continue to function + // even though the latest major version may have breaking changes in `@angular/core`. + if (id.startsWith('@angular-devkit/core')) { + try { + return schematicRequire(id); + } catch (e) { + assertIsError(e); + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } + } + } + + // Resolve from inside the `@angular/cli` project + return hostRequire(id); + } else if (id.startsWith('.') || id.startsWith('@angular/cdk')) { + // Wrap relative files inside the schematic collection + // Also wrap `@angular/cdk`, it contains helper utilities that import core schematic packages + + // Resolve from the original file + const modulePath = schematicRequire.resolve(id); + + // Use cached module if available + const cachedModule = moduleCache.get(modulePath); + if (cachedModule) { + return cachedModule; + } + + // Do not wrap vendored third-party packages or JSON files + if ( + !/[/\\]node_modules[/\\]@schematics[/\\]angular[/\\]third_party[/\\]/.test(modulePath) && + !modulePath.endsWith('.json') + ) { + // Wrap module and save in cache + const wrappedModule = wrap(modulePath, dirname(modulePath), moduleCache)(); + moduleCache.set(modulePath, wrappedModule); + + return wrappedModule; + } + } + + // All others are required directly from the original file + return schematicRequire(id); + }; + + // Setup a wrapper function to capture the module's exports + const schematicCode = readFileSync(schematicFile, 'utf8'); + const script = new Script(Module.wrap(schematicCode), { + filename: schematicFile, + lineOffset: 1, + }); + const schematicModule = new Module(schematicFile); + const moduleFactory = script.runInThisContext(); + + return () => { + moduleFactory( + schematicModule.exports, + customRequire, + schematicModule, + schematicFile, + schematicDirectory, + ); + + return exportName ? schematicModule.exports[exportName] : schematicModule.exports; + }; +} + +function loadBuiltinModule(id: string): unknown { + return undefined; +} diff --git a/packages/angular/cli/src/command-builder/utilities/schematic-workflow.ts b/packages/angular/cli/src/command-builder/utilities/schematic-workflow.ts new file mode 100644 index 000000000000..3dbcfdd25983 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/schematic-workflow.ts @@ -0,0 +1,85 @@ +/** + * @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.dev/license + */ + +import { logging } from '@angular-devkit/core'; +import { NodeWorkflow } from '@angular-devkit/schematics/tools'; +import { colors } from '../../utilities/color'; + +function removeLeadingSlash(value: string): string { + return value[0] === '/' ? value.slice(1) : value; +} + +export function subscribeToWorkflow( + workflow: NodeWorkflow, + logger: logging.LoggerApi, +): { + files: Set; + error: boolean; + unsubscribe: () => void; +} { + const files = new Set(); + let error = false; + let logs: string[] = []; + + const reporterSubscription = workflow.reporter.subscribe((event) => { + // Strip leading slash to prevent confusion. + const eventPath = removeLeadingSlash(event.path); + + switch (event.kind) { + case 'error': + error = true; + logger.error( + `ERROR! ${eventPath} ${event.description == 'alreadyExist' ? 'already exists' : 'does not exist'}.`, + ); + break; + case 'update': + logs.push( + // TODO: `as unknown` was necessary during TS 5.9 update. Figure out a long-term solution. + `${colors.cyan('UPDATE')} ${eventPath} (${(event.content as unknown as Buffer).length} bytes)`, + ); + files.add(eventPath); + break; + case 'create': + logs.push( + // TODO: `as unknown` was necessary during TS 5.9 update. Figure out a long-term solution. + `${colors.green('CREATE')} ${eventPath} (${(event.content as unknown as Buffer).length} bytes)`, + ); + files.add(eventPath); + break; + case 'delete': + logs.push(`${colors.yellow('DELETE')} ${eventPath}`); + files.add(eventPath); + break; + case 'rename': + logs.push(`${colors.blue('RENAME')} ${eventPath} => ${removeLeadingSlash(event.to)}`); + files.add(eventPath); + break; + } + }); + + const lifecycleSubscription = workflow.lifeCycle.subscribe((event) => { + if (event.kind == 'end' || event.kind == 'post-tasks-start') { + if (!error) { + // Output the logging queue, no error happened. + logs.forEach((log) => logger.info(log)); + } + + logs = []; + error = false; + } + }); + + return { + files, + error, + unsubscribe: () => { + reporterSubscription.unsubscribe(); + lifecycleSubscription.unsubscribe(); + }, + }; +} diff --git a/packages/angular/cli/src/commands/add/cli.ts b/packages/angular/cli/src/commands/add/cli.ts new file mode 100644 index 000000000000..0dae016fba12 --- /dev/null +++ b/packages/angular/cli/src/commands/add/cli.ts @@ -0,0 +1,780 @@ +/** + * @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.dev/license + */ + +import { Listr, ListrRenderer, ListrTaskWrapper, color, figures } from 'listr2'; +import assert from 'node:assert'; +import fs from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; +import npa from 'npm-package-arg'; +import semver, { Range, compare, intersects, prerelease, satisfies, valid } from 'semver'; +import { Argv } from 'yargs'; +import { + CommandModuleImplementation, + Options, + OtherOptions, +} from '../../command-builder/command-module'; +import { + SchematicsCommandArgs, + SchematicsCommandModule, +} from '../../command-builder/schematics-command-module'; +import { + NgAddSaveDependency, + PackageManager, + PackageManifest, + PackageMetadata, + createPackageManager, +} from '../../package-managers'; +import { assertIsError } from '../../utilities/error'; +import { isTTY } from '../../utilities/tty'; +import { VERSION } from '../../utilities/version'; + +class CommandError extends Error {} + +interface AddCommandArgs extends SchematicsCommandArgs { + collection: string; + verbose?: boolean; + registry?: string; + 'skip-confirmation'?: boolean; +} + +interface AddCommandTaskContext { + packageManager: PackageManager; + packageIdentifier: npa.Result; + savePackage?: NgAddSaveDependency; + collectionName?: string; + executeSchematic: AddCommandModule['executeSchematic']; + getPeerDependencyConflicts: AddCommandModule['getPeerDependencyConflicts']; + dryRun?: boolean; + hasSchematics?: boolean; + homepage?: string; +} + +type AddCommandTaskWrapper = ListrTaskWrapper< + AddCommandTaskContext, + typeof ListrRenderer, + typeof ListrRenderer +>; + +/** + * The set of packages that should have certain versions excluded from consideration + * when attempting to find a compatible version for a package. + * The key is a package name and the value is a SemVer range of versions to exclude. + */ +const packageVersionExclusions: Record = { + // @angular/localize@9.x and earlier versions as well as @angular/localize@10.0 prereleases do not have peer dependencies setup. + '@angular/localize': '<10.0.0', + // @angular/material@7.x versions have unbounded peer dependency ranges (>=7.0.0). + '@angular/material': '7.x', +}; + +const DEFAULT_CONFLICT_DISPLAY_LIMIT = 5; + +/** + * A map of packages to built-in schematics. + * This is used for packages that do not have a native `ng-add` schematic. + */ +const BUILT_IN_SCHEMATICS = { + tailwindcss: { + collection: '@schematics/angular', + name: 'tailwind', + }, +} as const; + +export default class AddCommandModule + extends SchematicsCommandModule + implements CommandModuleImplementation +{ + command = 'add '; + describe = 'Adds support for an external library to your project.'; + longDescriptionPath = join(__dirname, 'long-description.md'); + protected override allowPrivateSchematics = true; + private readonly schematicName = 'ng-add'; + private rootRequire = createRequire(this.context.root + '/'); + #projectVersionCache = new Map(); + + override async builder(argv: Argv): Promise> { + const localYargs = (await super.builder(argv)) + .positional('collection', { + description: 'The package to be added.', + type: 'string', + demandOption: true, + }) + .option('registry', { description: 'The NPM registry to use.', type: 'string' }) + .option('verbose', { + description: 'Display additional details about internal operations during execution.', + type: 'boolean', + default: false, + }) + .option('skip-confirmation', { + description: + 'Skip asking a confirmation prompt before installing and executing the package. ' + + 'Ensure package name is correct prior to using this option.', + type: 'boolean', + default: false, + }) + // Prior to downloading we don't know the full schema and therefore we cannot be strict on the options. + // Possibly in the future update the logic to use the following syntax: + // `ng add @angular/localize -- --package-options`. + .strict(false); + + const collectionName = this.getCollectionName(); + if (!collectionName) { + return localYargs; + } + + const workflow = this.getOrCreateWorkflowForBuilder(collectionName); + + try { + const collection = workflow.engine.createCollection(collectionName); + const options = await this.getSchematicOptions(collection, this.schematicName, workflow); + + return this.addSchemaOptionsToCommand(localYargs, options); + } catch (error) { + // During `ng add` prior to the downloading of the package + // we are not able to resolve and create a collection. + // Or when the collection value is a path to a tarball. + } + + return localYargs; + } + + async run(options: Options & OtherOptions): Promise { + this.#projectVersionCache.clear(); + const { logger } = this.context; + const { collection, skipConfirmation } = options; + + let packageIdentifier; + try { + packageIdentifier = npa(collection); + } catch (e) { + assertIsError(e); + logger.error(e.message); + + return 1; + } + + if ( + packageIdentifier.name && + packageIdentifier.registry && + this.isPackageInstalled(packageIdentifier.name) + ) { + const validVersion = await this.isProjectVersionValid(packageIdentifier); + if (validVersion) { + // Already installed so just run schematic + logger.info('Skipping installation: Package already installed'); + + return this.executeSchematic({ ...options, collection: packageIdentifier.name }); + } + } + + const taskContext = { + packageIdentifier, + executeSchematic: this.executeSchematic.bind(this), + getPeerDependencyConflicts: this.getPeerDependencyConflicts.bind(this), + dryRun: options.dryRun, + } as AddCommandTaskContext; + + const tasks = new Listr( + [ + { + title: 'Determining Package Manager', + task: (context, task) => this.determinePackageManagerTask(context, task), + rendererOptions: { persistentOutput: true }, + }, + { + title: 'Searching for compatible package version', + enabled: packageIdentifier.type === 'range' && packageIdentifier.rawSpec === '*', + task: (context, task) => this.findCompatiblePackageVersionTask(context, task, options), + rendererOptions: { persistentOutput: true }, + }, + { + title: 'Loading package information', + task: (context, task) => this.loadPackageInfoTask(context, task, options), + rendererOptions: { persistentOutput: true }, + }, + { + title: 'Confirming installation', + enabled: !skipConfirmation && !options.dryRun, + task: (context, task) => this.confirmInstallationTask(context, task), + rendererOptions: { persistentOutput: true }, + }, + { + title: 'Installing package', + skip: (context) => { + if (context.dryRun) { + return `Skipping package installation. Would install package ${color.blue( + context.packageIdentifier.toString(), + )}.`; + } + + return false; + }, + task: (context, task) => this.installPackageTask(context, task, options), + rendererOptions: { bottomBar: Infinity }, + }, + // TODO: Rework schematic execution as a task and insert here + ], + { + /* options */ + }, + ); + + try { + const result = await tasks.run(taskContext); + assert(result.collectionName, 'Collection name should always be available'); + + // Check if the installed package has actual add actions and not just schematic support + if (result.hasSchematics && !options.dryRun) { + const workflow = this.getOrCreateWorkflowForBuilder(result.collectionName); + const collection = workflow.engine.createCollection(result.collectionName); + + // listSchematicNames cannot be used here since it does not list private schematics. + // Most `ng-add` schematics are marked as private. + // TODO: Consider adding a `hasSchematic` helper to the schematic collection object. + try { + collection.createSchematic(this.schematicName, true); + } catch { + result.hasSchematics = false; + } + } + + if (!result.hasSchematics) { + // Fallback to a built-in schematic if the package does not have an `ng-add` schematic + const packageName = result.packageIdentifier.name; + if (packageName) { + const builtInSchematic = + BUILT_IN_SCHEMATICS[packageName as keyof typeof BUILT_IN_SCHEMATICS]; + if (builtInSchematic) { + logger.info( + `The ${color.blue(packageName)} package does not provide \`ng add\` actions.`, + ); + logger.info('The Angular CLI will use built-in actions to add it to your project.'); + + return this.executeSchematic({ + ...options, + collection: builtInSchematic.collection, + schematicName: builtInSchematic.name, + }); + } + } + + let message = options.dryRun + ? 'The package does not provide any `ng add` actions, so no further actions would be taken.' + : 'Package installed successfully. The package does not provide any `ng add` actions, so no further actions were taken.'; + + if (result.homepage) { + message += `\nFor more information about this package, visit its homepage at ${result.homepage}`; + } + logger.info(message); + + return; + } + + if (options.dryRun) { + logger.info("The package's `ng add` actions would be executed next."); + + return; + } + + return this.executeSchematic({ ...options, collection: result.collectionName }); + } catch (e) { + if (e instanceof CommandError) { + logger.error(e.message); + + return 1; + } + + throw e; + } + } + + private async determinePackageManagerTask( + context: AddCommandTaskContext, + task: AddCommandTaskWrapper, + ): Promise { + context.packageManager = await createPackageManager({ + cwd: this.context.root, + logger: this.context.logger, + dryRun: context.dryRun, + }); + task.output = `Using package manager: ${color.dim(context.packageManager.name)}`; + } + + private async findCompatiblePackageVersionTask( + context: AddCommandTaskContext, + task: AddCommandTaskWrapper, + options: Options, + ): Promise { + const { registry, verbose } = options; + const { packageManager, packageIdentifier } = context; + const packageName = packageIdentifier.name; + + assert(packageName, 'Registry package identifiers should always have a name.'); + + const rejectionReasons: string[] = []; + + // Attempt to use the 'latest' tag from the registry. + try { + const latestManifest = await packageManager.getManifest(`${packageName}@latest`, { + registry, + }); + + if (latestManifest) { + const conflicts = await this.getPeerDependencyConflicts(latestManifest); + if (!conflicts) { + context.packageIdentifier = npa.resolve(latestManifest.name, latestManifest.version); + task.output = `Found compatible package version: ${color.blue(latestManifest.version)}.`; + + return; + } + rejectionReasons.push(...conflicts); + } + } catch (e) { + assertIsError(e); + throw new CommandError(`Unable to load package information from registry: ${e.message}`); + } + + // 'latest' is invalid or not found, search for most recent matching package. + task.output = + 'Could not find a compatible version with `latest`. Searching for a compatible version.'; + + let packageMetadata; + try { + packageMetadata = await packageManager.getRegistryMetadata(packageName, { + registry, + }); + } catch (e) { + assertIsError(e); + throw new CommandError(`Unable to load package information from registry: ${e.message}`); + } + + if (!packageMetadata) { + throw new CommandError('Unable to load package information from registry.'); + } + + // Allow prelease versions if the CLI itself is a prerelease or locally built. + const allowPrereleases = !!prerelease(VERSION.full) || VERSION.full === '0.0.0'; + const potentialVersions = this.#getPotentialVersions(packageMetadata, allowPrereleases); + + // Heuristic-based search: Check the latest release of each major version first. + const majorVersions = this.#getMajorVersions(potentialVersions); + let found = await this.#findCompatibleVersion(context, majorVersions, { + registry, + verbose, + rejectionReasons, + }); + + // Exhaustive search: If no compatible major version is found, fall back to checking all versions. + if (!found) { + const checkedVersions = new Set(majorVersions); + const remainingVersions = potentialVersions.filter((v) => !checkedVersions.has(v)); + found = await this.#findCompatibleVersion(context, remainingVersions, { + registry, + verbose, + rejectionReasons, + }); + } + + if (!found) { + let message = `Unable to find compatible package.`; + if (rejectionReasons.length > 0) { + message += + '\nThis is often because of incompatible peer dependencies.\n' + + 'These versions were rejected due to the following conflicts:\n' + + rejectionReasons + .slice(0, verbose ? undefined : DEFAULT_CONFLICT_DISPLAY_LIMIT) + .map((r) => ` - ${r}`) + .join('\n'); + } + task.output = message; + } else { + task.output = `Found compatible package version: ${color.blue( + context.packageIdentifier.toString(), + )}.`; + } + } + + async #findCompatibleVersion( + context: AddCommandTaskContext, + versions: string[], + options: { + registry?: string; + verbose?: boolean; + rejectionReasons: string[]; + }, + ): Promise { + const { packageManager, packageIdentifier } = context; + const { registry, verbose, rejectionReasons } = options; + const packageName = packageIdentifier.name; + assert(packageName, 'Package name must be defined.'); + + for (const version of versions) { + const manifest = await packageManager.getManifest(`${packageName}@${version}`, { + registry, + }); + if (!manifest) { + continue; + } + + const conflicts = await this.getPeerDependencyConflicts(manifest); + if (conflicts) { + if (verbose || rejectionReasons.length < DEFAULT_CONFLICT_DISPLAY_LIMIT) { + rejectionReasons.push(...conflicts); + } + continue; + } + + context.packageIdentifier = npa.resolve(manifest.name, manifest.version); + + return manifest; + } + + return null; + } + + #getPotentialVersions(packageMetadata: PackageMetadata, allowPrereleases: boolean): string[] { + const versionExclusions = packageVersionExclusions[packageMetadata.name]; + const latestVersion = packageMetadata['dist-tags']['latest']; + + const versions = Object.values(packageMetadata.versions).filter((version) => { + // Latest tag has already been checked + if (latestVersion && version === latestVersion) { + return false; + } + + // Prerelease versions are not stable and should not be considered by default + if (!allowPrereleases && prerelease(version)) { + return false; + } + + // Excluded package versions should not be considered + if (versionExclusions && satisfies(version, versionExclusions, { includePrerelease: true })) { + return false; + } + + return true; + }); + + // Sort in reverse SemVer order so that the newest compatible version is chosen + return versions.sort((a, b) => compare(b, a, true)); + } + + #getMajorVersions(versions: string[]): string[] { + const majorVersions = new Map(); + for (const version of versions) { + const major = semver.major(version); + const existing = majorVersions.get(major); + if (!existing || semver.gt(version, existing)) { + majorVersions.set(major, version); + } + } + + return [...majorVersions.values()].sort((a, b) => compare(b, a, true)); + } + + private async loadPackageInfoTask( + context: AddCommandTaskContext, + task: AddCommandTaskWrapper, + options: Options, + ): Promise { + const { registry } = options; + + let manifest; + try { + manifest = await context.packageManager.getManifest(context.packageIdentifier.toString(), { + registry, + }); + } catch (e) { + assertIsError(e); + throw new CommandError( + `Unable to fetch package information for '${context.packageIdentifier}': ${e.message}`, + ); + } + + if (!manifest) { + throw new CommandError( + `Unable to fetch package information for '${context.packageIdentifier}'.`, + ); + } + + context.hasSchematics = !!manifest.schematics; + context.savePackage = manifest['ng-add']?.save; + context.collectionName = manifest.name; + context.homepage = manifest.homepage; + + if (await this.getPeerDependencyConflicts(manifest)) { + task.output = color.yellow( + figures.warning + + ' Package has unmet peer dependencies. Adding the package may not succeed.', + ); + } + } + + private async confirmInstallationTask( + context: AddCommandTaskContext, + task: AddCommandTaskWrapper, + ): Promise { + if (!isTTY()) { + task.output = + `'--skip-confirmation' can be used to bypass installation confirmation. ` + + `Ensure package name is correct prior to '--skip-confirmation' option usage.`; + throw new CommandError('No terminal detected'); + } + + const { ListrInquirerPromptAdapter } = await import('@listr2/prompt-adapter-inquirer'); + const { confirm } = await import('@inquirer/prompts'); + const shouldProceed = await task.prompt(ListrInquirerPromptAdapter).run(confirm, { + message: + `The package ${color.blue(context.packageIdentifier.toString())} will be installed and executed.\n` + + 'Would you like to proceed?', + default: true, + theme: { prefix: '' }, + }); + + if (!shouldProceed) { + throw new CommandError('Command aborted'); + } + } + + private async installPackageTask( + context: AddCommandTaskContext, + task: AddCommandTaskWrapper, + options: Options, + ): Promise { + const { registry } = options; + const { packageManager, packageIdentifier, savePackage } = context; + + // Only show if installation will actually occur + task.title = 'Installing package'; + + if (context.savePackage === false) { + task.title += ' in temporary location'; + + // Temporary packages are located in a different directory + // Hence we need to resolve them using the temp path + const { workingDirectory } = await packageManager.acquireTempPackage( + packageIdentifier.toString(), + { + registry, + }, + ); + + const tempRequire = createRequire(workingDirectory + '/'); + assert(context.collectionName, 'Collection name should always be available'); + const resolvedCollectionPath = tempRequire.resolve( + join(context.collectionName, 'package.json'), + ); + + context.collectionName = dirname(resolvedCollectionPath); + } else { + await packageManager.add( + packageIdentifier.toString(), + 'none', + savePackage !== 'dependencies', + false, + true, + { + registry, + }, + ); + } + } + + private async isProjectVersionValid(packageIdentifier: npa.Result): Promise { + if (!packageIdentifier.name) { + return false; + } + + const installedVersion = await this.findProjectVersion(packageIdentifier.name); + if (!installedVersion) { + return false; + } + + if (packageIdentifier.rawSpec === '*') { + return true; + } + + if ( + packageIdentifier.type === 'range' && + packageIdentifier.fetchSpec && + packageIdentifier.fetchSpec !== '*' + ) { + return satisfies(installedVersion, packageIdentifier.fetchSpec); + } + + if (packageIdentifier.type === 'version') { + const v1 = valid(packageIdentifier.fetchSpec); + const v2 = valid(installedVersion); + + return v1 !== null && v1 === v2; + } + + return false; + } + + private getCollectionName(): string | undefined { + const [, collectionName] = this.context.args.positional; + if (!collectionName) { + return undefined; + } + + // The CLI argument may specify also a version, like `ng add @my/lib@13.0.0`, + // but here we need only the name of the package, like `@my/lib`. + try { + const packageName = npa(collectionName).name; + if (packageName) { + return packageName; + } + } catch (e) { + assertIsError(e); + this.context.logger.error(e.message); + } + + return collectionName; + } + + private isPackageInstalled(name: string): boolean { + try { + this.rootRequire.resolve(join(name, 'package.json')); + + return true; + } catch (e) { + assertIsError(e); + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } + } + + return false; + } + + private executeSchematic( + options: Options & OtherOptions & { schematicName?: string }, + ): Promise { + const { + verbose, + skipConfirmation, + interactive, + force, + dryRun, + registry, + defaults, + collection: collectionName, + schematicName, + ...schematicOptions + } = options; + + return this.runSchematic({ + schematicOptions, + schematicName: schematicName ?? this.schematicName, + collectionName, + executionOptions: { + interactive, + force, + dryRun, + defaults, + packageRegistry: registry, + }, + }); + } + + private async findProjectVersion(name: string): Promise { + const cachedVersion = this.#projectVersionCache.get(name); + if (cachedVersion !== undefined) { + return cachedVersion; + } + + const { root } = this.context; + let installedPackagePath; + try { + installedPackagePath = this.rootRequire.resolve(join(name, 'package.json')); + } catch {} + + if (installedPackagePath) { + try { + const installedPackage = JSON.parse( + await fs.readFile(installedPackagePath, 'utf-8'), + ) as PackageManifest; + this.#projectVersionCache.set(name, installedPackage.version); + + return installedPackage.version; + } catch {} + } + + let projectManifest; + try { + projectManifest = JSON.parse( + await fs.readFile(join(root, 'package.json'), 'utf-8'), + ) as PackageManifest; + } catch {} + + if (projectManifest) { + const version = + projectManifest.dependencies?.[name] || projectManifest.devDependencies?.[name]; + if (version) { + this.#projectVersionCache.set(name, version); + + return version; + } + } + + this.#projectVersionCache.set(name, null); + + return null; + } + + private async getPeerDependencyConflicts(manifest: PackageManifest): Promise { + if (!manifest.peerDependencies) { + return false; + } + + const checks = Object.entries(manifest.peerDependencies).map(async ([peer, range]) => { + let peerIdentifier; + try { + peerIdentifier = npa.resolve(peer, range); + } catch { + this.context.logger.warn(`Invalid peer dependency ${peer} found in package.`); + + return null; + } + + if (peerIdentifier.type !== 'version' && peerIdentifier.type !== 'range') { + // type === 'tag' | 'file' | 'directory' | 'remote' | 'git' + // Cannot accurately compare these as the tag/location may have changed since install. + return null; + } + + try { + const version = await this.findProjectVersion(peer); + if (!version) { + return null; + } + + const options = { includePrerelease: true }; + if ( + !intersects(version, peerIdentifier.rawSpec, options) && + !satisfies(version, peerIdentifier.rawSpec, options) + ) { + return ( + `Package "${manifest.name}@${manifest.version}" has an incompatible peer dependency to "` + + `${peer}@${peerIdentifier.rawSpec}" (requires "${version}" in project).` + ); + } + } catch { + // Not found or invalid so ignore + } + + return null; + }); + + const conflicts = (await Promise.all(checks)).filter((result): result is string => !!result); + + return conflicts.length > 0 && conflicts; + } +} diff --git a/packages/angular/cli/src/commands/add/long-description.md b/packages/angular/cli/src/commands/add/long-description.md new file mode 100644 index 000000000000..347b3a5971aa --- /dev/null +++ b/packages/angular/cli/src/commands/add/long-description.md @@ -0,0 +1,7 @@ +Adds the npm package for a published library to your workspace, and configures +the project in the current working directory to use that library, as specified by the library's schematic. +For example, adding `@angular/pwa` configures your project for PWA support: + +```bash +ng add @angular/pwa +``` diff --git a/packages/angular/cli/src/commands/analytics/cli.ts b/packages/angular/cli/src/commands/analytics/cli.ts new file mode 100644 index 000000000000..da56a2a00460 --- /dev/null +++ b/packages/angular/cli/src/commands/analytics/cli.ts @@ -0,0 +1,51 @@ +/** + * @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.dev/license + */ + +import { join } from 'node:path'; +import { Argv } from 'yargs'; +import { + CommandModule, + CommandModuleImplementation, + Options, +} from '../../command-builder/command-module'; +import { + addCommandModuleToYargs, + demandCommandFailureMessage, +} from '../../command-builder/utilities/command'; +import { AnalyticsInfoCommandModule } from './info/cli'; +import { + AnalyticsDisableModule, + AnalyticsEnableModule, + AnalyticsPromptModule, +} from './settings/cli'; + +export default class AnalyticsCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + command = 'analytics'; + describe = 'Configures the gathering of Angular CLI usage metrics.'; + longDescriptionPath = join(__dirname, 'long-description.md'); + + builder(localYargs: Argv): Argv { + const subcommands = [ + AnalyticsInfoCommandModule, + AnalyticsDisableModule, + AnalyticsEnableModule, + AnalyticsPromptModule, + ].sort(); // sort by class name. + + for (const module of subcommands) { + addCommandModuleToYargs(module, this.context); + } + + return localYargs.demandCommand(1, demandCommandFailureMessage).strict(); + } + + run(_options: Options<{}>): void {} +} diff --git a/packages/angular/cli/src/commands/analytics/info/cli.ts b/packages/angular/cli/src/commands/analytics/info/cli.ts new file mode 100644 index 000000000000..e4434d35baee --- /dev/null +++ b/packages/angular/cli/src/commands/analytics/info/cli.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.dev/license + */ + +import { Argv } from 'yargs'; +import { getAnalyticsInfoString } from '../../../analytics/analytics'; +import { + CommandModule, + CommandModuleImplementation, + Options, +} from '../../../command-builder/command-module'; + +export class AnalyticsInfoCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + command = 'info'; + describe = 'Prints analytics gathering and reporting configuration in the console.'; + longDescriptionPath?: string; + + builder(localYargs: Argv): Argv { + return localYargs.strict(); + } + + async run(_options: Options<{}>): Promise { + this.context.logger.info(await getAnalyticsInfoString(this.context)); + } +} diff --git a/packages/angular/cli/src/commands/analytics/long-description.md b/packages/angular/cli/src/commands/analytics/long-description.md new file mode 100644 index 000000000000..69ee9ad7ee00 --- /dev/null +++ b/packages/angular/cli/src/commands/analytics/long-description.md @@ -0,0 +1,20 @@ +You can help the Angular Team to prioritize features and improvements by permitting the Angular team to send command-line command usage statistics to Google. +The Angular Team does not collect usage statistics unless you explicitly opt in. When installing the Angular CLI you are prompted to allow global collection of usage statistics. +If you say no or skip the prompt, no data is collected. + +### What is collected? + +Usage analytics include the commands and selected flags for each execution. +Usage analytics may include the following information: + +- Your operating system \(macOS, Linux distribution, Windows\) and its version. +- Package manager name and version \(local version only\). +- Node.js version \(local version only\). +- Angular CLI version \(local version only\). +- Command name that was run. +- Workspace information, the number of application and library projects. +- For schematics commands \(add, generate and new\), the schematic collection and name and a list of selected flags. +- For build commands \(build, serve\), the builder name, the number and size of bundles \(initial and lazy\), compilation units, the time it took to build and rebuild, and basic Angular-specific API usage. + +Only Angular owned and developed schematics and builders are reported. +Third-party schematics and builders do not send data to the Angular Team. diff --git a/packages/angular/cli/src/commands/analytics/settings/cli.ts b/packages/angular/cli/src/commands/analytics/settings/cli.ts new file mode 100644 index 000000000000..16f07b353d1a --- /dev/null +++ b/packages/angular/cli/src/commands/analytics/settings/cli.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.dev/license + */ + +import { Argv } from 'yargs'; +import { + getAnalyticsInfoString, + promptAnalytics, + setAnalyticsConfig, +} from '../../../analytics/analytics'; +import { + CommandModule, + CommandModuleImplementation, + Options, +} from '../../../command-builder/command-module'; + +interface AnalyticsCommandArgs { + global: boolean; +} + +abstract class AnalyticsSettingModule + extends CommandModule + implements CommandModuleImplementation +{ + longDescriptionPath?: string; + + builder(localYargs: Argv): Argv { + return localYargs + .option('global', { + description: `Configure analytics gathering and reporting globally in the caller's home directory.`, + alias: ['g'], + type: 'boolean', + default: false, + }) + .strict(); + } + + abstract override run({ global }: Options): Promise; +} + +export class AnalyticsDisableModule + extends AnalyticsSettingModule + implements CommandModuleImplementation +{ + command = 'disable'; + aliases = 'off'; + describe = 'Disables analytics gathering and reporting for the user.'; + + async run({ global }: Options): Promise { + await setAnalyticsConfig(global, false); + process.stderr.write(await getAnalyticsInfoString(this.context)); + } +} + +export class AnalyticsEnableModule + extends AnalyticsSettingModule + implements CommandModuleImplementation +{ + command = 'enable'; + aliases = 'on'; + describe = 'Enables analytics gathering and reporting for the user.'; + async run({ global }: Options): Promise { + await setAnalyticsConfig(global, true); + process.stderr.write(await getAnalyticsInfoString(this.context)); + } +} + +export class AnalyticsPromptModule + extends AnalyticsSettingModule + implements CommandModuleImplementation +{ + command = 'prompt'; + describe = 'Prompts the user to set the analytics gathering status interactively.'; + + async run({ global }: Options): Promise { + await promptAnalytics(this.context, global, true); + } +} diff --git a/packages/angular/cli/src/commands/build/cli.ts b/packages/angular/cli/src/commands/build/cli.ts new file mode 100644 index 000000000000..365420ca3734 --- /dev/null +++ b/packages/angular/cli/src/commands/build/cli.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.dev/license + */ + +import { join } from 'node:path'; +import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; +import { CommandModuleImplementation } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; + +export default class BuildCommandModule + extends ArchitectCommandModule + implements CommandModuleImplementation +{ + multiTarget = false; + command = 'build [project]'; + aliases = RootCommands['build'].aliases; + describe = + 'Compiles an Angular application or library into an output directory named dist/ at the given output path.'; + longDescriptionPath = join(__dirname, 'long-description.md'); +} diff --git a/packages/angular/cli/src/commands/build/long-description.md b/packages/angular/cli/src/commands/build/long-description.md new file mode 100644 index 000000000000..b2c14d8f23fe --- /dev/null +++ b/packages/angular/cli/src/commands/build/long-description.md @@ -0,0 +1,18 @@ +The command can be used to build a project of type "application" or "library". +When used to build a library, a different builder is invoked, and only the `ts-config`, `configuration`, `poll` and `watch` options are applied. +All other options apply only to building applications. + +The application builder uses the [esbuild](https://esbuild.github.io/) build tool, with default configuration options specified in the workspace configuration file (`angular.json`) or with a named alternative configuration. +A "development" configuration is created by default when you use the CLI to create the project, and you can use that configuration by specifying the `--configuration development`. + +The configuration options generally correspond to the command options. +You can override individual configuration defaults by specifying the corresponding options on the command line. +The command can accept option names given in dash-case. +Note that in the configuration file, you must specify names in camelCase. + +Some additional options can only be set through the configuration file, +either by direct editing or with the `ng config` command. +These include `assets`, `styles`, and `scripts` objects that provide runtime-global resources to include in the project. +Resources in CSS, such as images and fonts, are automatically written and fingerprinted at the root of the output folder. + +For further details, see [Workspace Configuration](reference/configs/workspace-config). diff --git a/packages/angular/cli/src/commands/cache/clean/cli.ts b/packages/angular/cli/src/commands/cache/clean/cli.ts new file mode 100644 index 000000000000..a115b686b7e0 --- /dev/null +++ b/packages/angular/cli/src/commands/cache/clean/cli.ts @@ -0,0 +1,37 @@ +/** + * @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.dev/license + */ + +import { rm } from 'node:fs/promises'; +import { Argv } from 'yargs'; +import { + CommandModule, + CommandModuleImplementation, + CommandScope, +} from '../../../command-builder/command-module'; +import { getCacheConfig } from '../utilities'; + +export class CacheCleanModule extends CommandModule implements CommandModuleImplementation { + command = 'clean'; + describe = 'Deletes persistent disk cache from disk.'; + longDescriptionPath: string | undefined; + override scope = CommandScope.In; + + builder(localYargs: Argv): Argv { + return localYargs.strict(); + } + + run(): Promise { + const { path } = getCacheConfig(this.context.workspace); + + return rm(path, { + force: true, + recursive: true, + maxRetries: 3, + }); + } +} diff --git a/packages/angular/cli/src/commands/cache/cli.ts b/packages/angular/cli/src/commands/cache/cli.ts new file mode 100644 index 000000000000..dad144b034b3 --- /dev/null +++ b/packages/angular/cli/src/commands/cache/cli.ts @@ -0,0 +1,50 @@ +/** + * @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.dev/license + */ + +import { join } from 'node:path'; +import { Argv } from 'yargs'; +import { + CommandModule, + CommandModuleImplementation, + CommandScope, + Options, +} from '../../command-builder/command-module'; +import { + addCommandModuleToYargs, + demandCommandFailureMessage, +} from '../../command-builder/utilities/command'; +import { CacheCleanModule } from './clean/cli'; +import { CacheInfoCommandModule } from './info/cli'; +import { CacheDisableModule, CacheEnableModule } from './settings/cli'; + +export default class CacheCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + command = 'cache'; + describe = 'Configure persistent disk cache and retrieve cache statistics.'; + longDescriptionPath = join(__dirname, 'long-description.md'); + override scope = CommandScope.In; + + builder(localYargs: Argv): Argv { + const subcommands = [ + CacheEnableModule, + CacheDisableModule, + CacheCleanModule, + CacheInfoCommandModule, + ].sort(); + + for (const module of subcommands) { + addCommandModuleToYargs(module, this.context); + } + + return localYargs.demandCommand(1, demandCommandFailureMessage).strict(); + } + + run(_options: Options<{}>): void {} +} diff --git a/packages/angular/cli/src/commands/cache/info/cli.ts b/packages/angular/cli/src/commands/cache/info/cli.ts new file mode 100644 index 000000000000..f4278d52db74 --- /dev/null +++ b/packages/angular/cli/src/commands/cache/info/cli.ts @@ -0,0 +1,128 @@ +/** + * @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.dev/license + */ + +import * as fs from 'node:fs/promises'; +import { join } from 'node:path'; +import { Argv } from 'yargs'; +import { + CommandModule, + CommandModuleImplementation, + CommandScope, +} from '../../../command-builder/command-module'; +import { colors } from '../../../utilities/color'; +import { isCI } from '../../../utilities/environment-options'; +import { getCacheConfig } from '../utilities'; + +export class CacheInfoCommandModule extends CommandModule implements CommandModuleImplementation { + command = 'info'; + describe = 'Prints persistent disk cache configuration and statistics in the console.'; + longDescriptionPath?: string | undefined; + override scope = CommandScope.In; + + builder(localYargs: Argv): Argv { + return localYargs.strict(); + } + + async run(): Promise { + const cacheConfig = getCacheConfig(this.context.workspace); + const { path, environment, enabled } = cacheConfig; + + const effectiveStatus = this.effectiveEnabledStatus(cacheConfig); + const sizeOnDisk = await this.getSizeOfDirectory(path); + + const info: { label: string; value: string }[] = [ + { + label: 'Enabled', + value: enabled ? colors.green('Yes') : colors.red('No'), + }, + { + label: 'Environment', + value: colors.cyan(environment), + }, + { + label: 'Path', + value: colors.cyan(path), + }, + { + label: 'Size on disk', + value: colors.cyan(sizeOnDisk), + }, + { + label: 'Effective Status', + value: + (effectiveStatus ? colors.green('Enabled') : colors.red('Disabled')) + + ' (current machine)', + }, + ]; + + const maxLabelLength = Math.max(...info.map((l) => l.label.length)); + + const output = info + .map(({ label, value }) => colors.bold(label.padEnd(maxLabelLength + 2)) + `: ${value}`) + .join('\n'); + + this.context.logger.info(`\n${colors.bold('Cache Information')}\n\n${output}\n`); + } + + private async getSizeOfDirectory(path: string): Promise { + const directoriesStack = [path]; + let size = 0; + + while (directoriesStack.length) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const dirPath = directoriesStack.pop()!; + let entries: string[] = []; + + try { + entries = await fs.readdir(dirPath); + } catch {} + + for (const entry of entries) { + const entryPath = join(dirPath, entry); + const stats = await fs.stat(entryPath); + + if (stats.isDirectory()) { + directoriesStack.push(entryPath); + } + + size += stats.size; + } + } + + return this.formatSize(size); + } + + private formatSize(size: number): string { + if (size <= 0) { + return '0 bytes'; + } + + const abbreviations = ['bytes', 'kB', 'MB', 'GB']; + const index = Math.floor(Math.log(size) / Math.log(1024)); + 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]}`; + } + + private effectiveEnabledStatus(cacheConfig: { enabled: boolean; environment: string }): boolean { + const { enabled, environment } = cacheConfig; + + if (enabled) { + switch (environment) { + case 'ci': + return isCI; + case 'local': + return !isCI; + } + } + + return enabled; + } +} diff --git a/packages/angular/cli/src/commands/cache/long-description.md b/packages/angular/cli/src/commands/cache/long-description.md new file mode 100644 index 000000000000..3ebfec598c4e --- /dev/null +++ b/packages/angular/cli/src/commands/cache/long-description.md @@ -0,0 +1,53 @@ +Angular CLI saves a number of cachable operations on disk by default. + +When you re-run the same build, the build system restores the state of the previous build and re-uses previously performed operations, which decreases the time taken to build and test your applications and libraries. + +To amend the default cache settings, add the `cli.cache` object to your [Workspace Configuration](reference/configs/workspace-config). +The object goes under `cli.cache` at the top level of the file, outside the `projects` sections. + +```jsonc +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "cache": { + // ... + }, + }, + "projects": {}, +} +``` + +For more information, see [cache options](reference/configs/workspace-config#cache-options). + +### Cache environments + +By default, disk cache is only enabled for local environments. The value of environment can be one of the following: + +- `all` - allows disk cache on all machines. +- `local` - allows disk cache only on development machines. +- `ci` - allows disk cache only on continuous integration (CI) systems. + +To change the environment setting to `all`, run the following command: + +```bash +ng config cli.cache.environment all +``` + +For more information, see `environment` in [cache options](reference/configs/workspace-config#cache-options). + +
+ +The Angular CLI checks for the presence and value of the `CI` environment variable to determine in which environment it is running. + +
+ +### Cache path + +By default, `.angular/cache` is used as a base directory to store cache results. + +To change this path to `.cache/ng`, run the following command: + +```bash +ng config cli.cache.path ".cache/ng" +``` diff --git a/packages/angular/cli/src/commands/cache/settings/cli.ts b/packages/angular/cli/src/commands/cache/settings/cli.ts new file mode 100644 index 000000000000..9a4f654f7ac7 --- /dev/null +++ b/packages/angular/cli/src/commands/cache/settings/cli.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.dev/license + */ + +import { Argv } from 'yargs'; +import { + CommandModule, + CommandModuleImplementation, + CommandScope, +} from '../../../command-builder/command-module'; +import { updateCacheConfig } from '../utilities'; + +export class CacheDisableModule extends CommandModule implements CommandModuleImplementation { + command = 'disable'; + aliases = 'off'; + describe = 'Disables persistent disk cache for all projects in the workspace.'; + longDescriptionPath: string | undefined; + override scope = CommandScope.In; + + builder(localYargs: Argv): Argv { + return localYargs; + } + + run(): Promise { + return updateCacheConfig(this.getWorkspaceOrThrow(), 'enabled', false); + } +} + +export class CacheEnableModule extends CommandModule implements CommandModuleImplementation { + command = 'enable'; + aliases = 'on'; + describe = 'Enables disk cache for all projects in the workspace.'; + longDescriptionPath: string | undefined; + override scope = CommandScope.In; + + builder(localYargs: Argv): Argv { + return localYargs; + } + + run(): Promise { + return updateCacheConfig(this.getWorkspaceOrThrow(), 'enabled', true); + } +} diff --git a/packages/angular/cli/src/commands/cache/utilities.ts b/packages/angular/cli/src/commands/cache/utilities.ts new file mode 100644 index 000000000000..84e22314763a --- /dev/null +++ b/packages/angular/cli/src/commands/cache/utilities.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.dev/license + */ + +import { isJsonObject } from '@angular-devkit/core'; +import { resolve } from 'node:path'; +import { Cache, Environment } from '../../../lib/config/workspace-schema'; +import { AngularWorkspace } from '../../utilities/config'; + +export function updateCacheConfig( + workspace: AngularWorkspace, + key: K, + value: Cache[K], +): Promise { + const cli = (workspace.extensions['cli'] ??= {}) as Record>; + const cache = (cli['cache'] ??= {}); + cache[key] = value; + + return workspace.save(); +} + +export function getCacheConfig(workspace: AngularWorkspace | undefined): Required { + if (!workspace) { + throw new Error(`Cannot retrieve cache configuration as workspace is not defined.`); + } + + const defaultSettings: Required = { + path: resolve(workspace.basePath, '.angular/cache'), + environment: Environment.Local, + enabled: true, + }; + + const cliSetting = workspace.extensions['cli']; + if (!cliSetting || !isJsonObject(cliSetting)) { + return defaultSettings; + } + + const cacheSettings = cliSetting['cache']; + if (!isJsonObject(cacheSettings)) { + return defaultSettings; + } + + const { + path = defaultSettings.path, + environment = defaultSettings.environment, + enabled = defaultSettings.enabled, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } = cacheSettings as Record; + + return { + path: resolve(workspace.basePath, path), + environment, + enabled, + }; +} diff --git a/packages/angular/cli/src/commands/command-config.ts b/packages/angular/cli/src/commands/command-config.ts new file mode 100644 index 000000000000..a74d81f5e911 --- /dev/null +++ b/packages/angular/cli/src/commands/command-config.ts @@ -0,0 +1,117 @@ +/** + * @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.dev/license + */ + +import { CommandModuleConstructor } from '../command-builder/utilities/command'; + +export type CommandNames = + | 'add' + | 'analytics' + | 'build' + | 'cache' + | 'completion' + | 'config' + | 'deploy' + | 'e2e' + | 'extract-i18n' + | 'generate' + | 'lint' + | 'make-this-awesome' + | 'mcp' + | 'new' + | 'run' + | 'serve' + | 'test' + | 'update' + | 'version'; + +export interface CommandConfig { + aliases?: string[]; + factory: () => Promise<{ default: CommandModuleConstructor }>; +} + +export const RootCommands: Record< + /* Command */ CommandNames & string, + /* Command Config */ CommandConfig +> = { + 'add': { + factory: () => import('./add/cli'), + }, + 'analytics': { + factory: () => import('./analytics/cli'), + }, + 'build': { + factory: () => import('./build/cli'), + aliases: ['b'], + }, + 'cache': { + factory: () => import('./cache/cli'), + }, + 'completion': { + factory: () => import('./completion/cli'), + }, + 'config': { + factory: () => import('./config/cli'), + }, + 'deploy': { + factory: () => import('./deploy/cli'), + }, + + 'e2e': { + factory: () => import('./e2e/cli'), + aliases: ['e'], + }, + 'extract-i18n': { + factory: () => import('./extract-i18n/cli'), + }, + 'generate': { + factory: () => import('./generate/cli'), + aliases: ['g'], + }, + 'lint': { + factory: () => import('./lint/cli'), + }, + 'make-this-awesome': { + factory: () => import('./make-this-awesome/cli'), + }, + 'mcp': { + factory: () => import('./mcp/cli'), + }, + 'new': { + factory: () => import('./new/cli'), + aliases: ['n'], + }, + 'run': { + factory: () => import('./run/cli'), + }, + 'serve': { + factory: () => import('./serve/cli'), + aliases: ['dev', 's'], + }, + 'test': { + factory: () => import('./test/cli'), + aliases: ['t'], + }, + 'update': { + factory: () => import('./update/cli'), + }, + 'version': { + factory: () => import('./version/cli'), + aliases: ['v'], + }, +}; + +export const RootCommandsAliases = Object.values(RootCommands).reduce( + (prev, current) => { + current.aliases?.forEach((alias) => { + prev[alias] = current; + }); + + return prev; + }, + {} as Record, +); diff --git a/packages/angular/cli/src/commands/completion/cli.ts b/packages/angular/cli/src/commands/completion/cli.ts new file mode 100644 index 000000000000..3fc9dccdc703 --- /dev/null +++ b/packages/angular/cli/src/commands/completion/cli.ts @@ -0,0 +1,76 @@ +/** + * @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.dev/license + */ + +import { join } from 'node:path'; +import { Argv } from 'yargs'; +import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module'; +import { addCommandModuleToYargs } from '../../command-builder/utilities/command'; +import { colors } from '../../utilities/color'; +import { hasGlobalCliInstall, initializeAutocomplete } from '../../utilities/completion'; +import { assertIsError } from '../../utilities/error'; + +export default class CompletionCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + command = 'completion'; + describe = 'Set up Angular CLI autocompletion for your terminal.'; + longDescriptionPath = join(__dirname, 'long-description.md'); + + builder(localYargs: Argv): Argv { + addCommandModuleToYargs(CompletionScriptCommandModule, this.context); + + return localYargs; + } + + async run(): Promise { + let rcFile: string; + try { + rcFile = await initializeAutocomplete(); + } catch (err) { + assertIsError(err); + this.context.logger.error(err.message); + + return 1; + } + + this.context.logger.info( + ` +Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your terminal or run the following to autocomplete \`ng\` commands: + + ${colors.yellow('source <(ng completion script)')} + `.trim(), + ); + + if ((await hasGlobalCliInstall()) === false) { + this.context.logger.warn( + 'Setup completed successfully, but there does not seem to be a global install of the' + + ' Angular CLI. For autocompletion to work, the CLI will need to be on your `$PATH`, which' + + ' is typically done with the `-g` flag in `npm install -g @angular/cli`.' + + '\n\n' + + 'For more information, see https://angular.dev/cli/completion#global-install', + ); + } + + return 0; + } +} + +class CompletionScriptCommandModule extends CommandModule implements CommandModuleImplementation { + command = 'script'; + describe = 'Generate a bash and zsh real-time type-ahead autocompletion script.'; + longDescriptionPath = undefined; + + builder(localYargs: Argv): Argv { + return localYargs; + } + + run(): void { + this.context.yargsInstance.showCompletionScript(); + } +} diff --git a/packages/angular/cli/src/commands/completion/long-description.md b/packages/angular/cli/src/commands/completion/long-description.md new file mode 100644 index 000000000000..b75803ac9cb0 --- /dev/null +++ b/packages/angular/cli/src/commands/completion/long-description.md @@ -0,0 +1,67 @@ +Setting up autocompletion configures your terminal, so pressing the `` key while in the middle +of typing will display various commands and options available to you. This makes it very easy to +discover and use CLI commands without lots of memorization. + +![A demo of Angular CLI autocompletion in a terminal. The user types several partial `ng` commands, +using autocompletion to finish several arguments and list contextual options. +](assets/images/guide/cli/completion.gif) + +## Automated setup + +The CLI should prompt and ask to set up autocompletion for you the first time you use it (v14+). +Simply answer "Yes" and the CLI will take care of the rest. + +``` +$ ng serve +? Would you like to enable autocompletion? This will set up your terminal so pressing TAB while typing Angular CLI commands will show possible options and autocomplete arguments. (Enabling autocompletion will modify configuration files in your home directory.) Yes +Appended `source <(ng completion script)` to `/home/my-username/.bashrc`. Restart your terminal or run: + +source <(ng completion script) + +to autocomplete `ng` commands. + +# Serve output... +``` + +If you already refused the prompt, it won't ask again. But you can run `ng completion` to +do the same thing automatically. + +This modifies your terminal environment to load Angular CLI autocompletion, but can't update your +current terminal session. Either restart it or run `source <(ng completion script)` directly to +enable autocompletion in your current session. + +Test it out by typing `ng ser` and it should autocomplete to `ng serve`. Ambiguous arguments +will show all possible options and their documentation, such as `ng generate `. + +## Manual setup + +Some users may have highly customized terminal setups, possibly with configuration files checked +into source control with an opinionated structure. `ng completion` only ever appends Angular's setup +to an existing configuration file for your current shell, or creates one if none exists. If you want +more control over exactly where this configuration lives, you can manually set it up by having your +shell run at startup: + +```bash +source <(ng completion script) +``` + +This is equivalent to what `ng completion` will automatically set up, and gives power users more +flexibility in their environments when desired. + +## Platform support + +Angular CLI supports autocompletion for the Bash and Zsh shells on MacOS and Linux operating +systems. On Windows, Git Bash and [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) +using Bash or Zsh are supported. + +## Global install + +Autocompletion works by configuring your terminal to invoke the Angular CLI on startup to load the +setup script. This means the terminal must be able to find and execute the Angular CLI, typically +through a global install that places the binary on the user's `$PATH`. If you get +`command not found: ng`, make sure the CLI is installed globally which you can do with the `-g` +flag: + +```bash +npm install -g @angular/cli +``` diff --git a/packages/angular/cli/src/commands/config/cli.ts b/packages/angular/cli/src/commands/config/cli.ts new file mode 100644 index 000000000000..06b253b9a42d --- /dev/null +++ b/packages/angular/cli/src/commands/config/cli.ts @@ -0,0 +1,192 @@ +/** + * @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.dev/license + */ + +import { JsonValue } from '@angular-devkit/core'; +import { randomUUID } from 'node:crypto'; +import { join } from 'node:path'; +import { Argv } from 'yargs'; +import { + CommandModule, + CommandModuleError, + CommandModuleImplementation, + Options, +} from '../../command-builder/command-module'; +import { getWorkspaceRaw, validateWorkspace } from '../../utilities/config'; +import { JSONFile, parseJson } from '../../utilities/json-file'; + +interface ConfigCommandArgs { + 'json-path'?: string; + value?: string; + global?: boolean; +} + +export default class ConfigCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + command = 'config [json-path] [value]'; + describe = + 'Retrieves or sets Angular configuration values in the angular.json file for the workspace.'; + longDescriptionPath = join(__dirname, 'long-description.md'); + + builder(localYargs: Argv): Argv { + return localYargs + .positional('json-path', { + 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.`, + type: 'string', + }) + .positional('value', { + description: 'If provided, a new value for the given configuration key.', + type: 'string', + }) + .option('global', { + description: `Access the global configuration in the caller's home directory.`, + alias: ['g'], + type: 'boolean', + default: false, + }) + .strict(); + } + + async run(options: Options): Promise { + const level = options.global ? 'global' : 'local'; + const [config] = await getWorkspaceRaw(level); + + if (options.value == undefined) { + if (!config) { + this.context.logger.error('No config found.'); + + return 1; + } + + return this.get(config, options); + } else { + return this.set(options); + } + } + + private get(jsonFile: JSONFile, options: Options): number { + const { logger } = this.context; + + const value = options.jsonPath + ? jsonFile.get(parseJsonPath(options.jsonPath)) + : jsonFile.content; + + if (value === undefined) { + logger.error('Value cannot be found.'); + + return 1; + } else if (typeof value === 'string') { + logger.info(value); + } else { + logger.info(JSON.stringify(value, null, 2)); + } + + return 0; + } + + private async set(options: Options): Promise { + if (!options.jsonPath?.trim()) { + throw new CommandModuleError('Invalid Path.'); + } + + const [config, configPath] = await getWorkspaceRaw(options.global ? 'global' : 'local'); + const { logger } = this.context; + + if (!config || !configPath) { + throw new CommandModuleError('Confguration file cannot be found.'); + } + + const normalizeUUIDValue = (v: string | undefined) => (v === '' ? randomUUID() : `${v}`); + + const value = + options.jsonPath === 'cli.analyticsSharing.uuid' + ? normalizeUUIDValue(options.value) + : options.value; + + const modified = config.modify(parseJsonPath(options.jsonPath), normalizeValue(value)); + + if (!modified) { + logger.error('Value cannot be found.'); + + return 1; + } + + await validateWorkspace(parseJson(config.content), options.global ?? false); + + config.save(); + + return 0; + } +} + +/** + * Splits a JSON path string into fragments. Fragments can be used to get the value referenced + * by the path. For example, a path of "a[3].foo.bar[2]" would give you a fragment array of + * ["a", 3, "foo", "bar", 2]. + * @param path The JSON string to parse. + * @returns {(string|number)[]} The fragments for the string. + * @private + */ +function parseJsonPath(path: string): (string | number)[] { + const fragments = (path || '').split(/\./g); + const result: (string | number)[] = []; + + while (fragments.length > 0) { + const fragment = fragments.shift(); + if (fragment == undefined) { + break; + } + + const match = fragment.match(/([^[]+)((\[.*\])*)/); + if (!match) { + throw new CommandModuleError('Invalid JSON path.'); + } + + result.push(match[1]); + if (match[2]) { + const indices = match[2] + .slice(1, -1) + .split('][') + .map((x) => (/^\d$/.test(x) ? +x : x.replace(/"|'/g, ''))); + result.push(...indices); + } + } + + return result.filter((fragment) => fragment != null); +} + +function normalizeValue(value: string | undefined | boolean | number): JsonValue | undefined { + const valueString = `${value}`.trim(); + switch (valueString) { + case 'true': + return true; + case 'false': + return false; + case 'null': + return null; + case 'undefined': + return undefined; + } + + if (isFinite(+valueString)) { + return +valueString; + } + + try { + // We use `JSON.parse` instead of `parseJson` because the latter will parse UUIDs + // and convert them into a numberic entities. + // Example: 73b61974-182c-48e4-b4c6-30ddf08c5c98 -> 73. + // These values should never contain comments, therefore using `JSON.parse` is safe. + return JSON.parse(valueString) as JsonValue; + } catch { + return value; + } +} diff --git a/packages/angular/cli/src/commands/config/long-description.md b/packages/angular/cli/src/commands/config/long-description.md new file mode 100644 index 000000000000..db32cb294152 --- /dev/null +++ b/packages/angular/cli/src/commands/config/long-description.md @@ -0,0 +1,13 @@ +A workspace has a single CLI configuration file, `angular.json`, at the top level. +The `projects` object contains a configuration object for each project in the workspace. + +You can edit the configuration directly in a code editor, +or indirectly on the command line using this command. + +The configurable property names match command option names, +except that in the configuration file, all names must use camelCase, +while on the command line options can be given dash-case. + +For further details, see [Workspace Configuration](reference/configs/workspace-config). + +For configuration of CLI usage analytics, see [ng analytics](cli/analytics). diff --git a/packages/angular/cli/src/commands/deploy/cli.ts b/packages/angular/cli/src/commands/deploy/cli.ts new file mode 100644 index 000000000000..947dc90af2d4 --- /dev/null +++ b/packages/angular/cli/src/commands/deploy/cli.ts @@ -0,0 +1,43 @@ +/** + * @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.dev/license + */ + +import { join } from 'node:path'; +import { MissingTargetChoice } from '../../command-builder/architect-base-command-module'; +import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; +import { CommandModuleImplementation } from '../../command-builder/command-module'; + +export default class DeployCommandModule + extends ArchitectCommandModule + implements CommandModuleImplementation +{ + // The below choices should be kept in sync with the list in https://angular.dev/tools/cli/deployment + override missingTargetChoices: MissingTargetChoice[] = [ + { + name: 'Amazon S3', + value: '@jefiozie/ngx-aws-deploy', + }, + { + name: 'Firebase', + value: '@angular/fire', + }, + { + name: 'Netlify', + value: '@netlify-builder/deploy', + }, + { + name: 'GitHub Pages', + value: 'angular-cli-ghpages', + }, + ]; + + multiTarget = false; + command = 'deploy [project]'; + longDescriptionPath = join(__dirname, 'long-description.md'); + describe = + 'Invokes the deploy builder for a specified project or for the default project in the workspace.'; +} diff --git a/packages/angular/cli/src/commands/deploy/long-description.md b/packages/angular/cli/src/commands/deploy/long-description.md new file mode 100644 index 000000000000..0436390680a4 --- /dev/null +++ b/packages/angular/cli/src/commands/deploy/long-description.md @@ -0,0 +1,22 @@ +The command takes an optional project name, as specified in the `projects` section of the `angular.json` workspace configuration file. +When a project name is not supplied, executes the `deploy` builder for the default project. + +To use the `ng deploy` command, use `ng add` to add a package that implements deployment capabilities to your favorite platform. +Adding the package automatically updates your workspace configuration, adding a deployment +[CLI builder](tools/cli/cli-builder). +For example: + +```json +"projects": { + "my-project": { + ... + "architect": { + ... + "deploy": { + "builder": "@angular/fire:deploy", + "options": {} + } + } + } +} +``` diff --git a/packages/angular/cli/src/commands/e2e/cli.ts b/packages/angular/cli/src/commands/e2e/cli.ts new file mode 100644 index 000000000000..85d9aab173a0 --- /dev/null +++ b/packages/angular/cli/src/commands/e2e/cli.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.dev/license + */ + +import { MissingTargetChoice } from '../../command-builder/architect-base-command-module'; +import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; +import { CommandModuleImplementation } from '../../command-builder/command-module'; +import { RootCommands } from '../command-config'; + +export default class E2eCommandModule + extends ArchitectCommandModule + implements CommandModuleImplementation +{ + override missingTargetChoices: MissingTargetChoice[] = [ + { + name: 'Playwright', + value: 'playwright-ng-schematics', + }, + { + name: 'Cypress', + value: '@cypress/schematic', + }, + { + name: 'Nightwatch', + value: '@nightwatch/schematics', + }, + { + name: 'WebdriverIO', + value: '@wdio/schematics', + }, + { + name: 'Puppeteer', + value: '@puppeteer/ng-schematics', + }, + ]; + + multiTarget = true; + command = 'e2e [project]'; + aliases = RootCommands['e2e'].aliases; + describe = 'Builds and serves an Angular application, then runs end-to-end tests.'; + longDescriptionPath?: string; +} diff --git a/packages/angular/cli/src/commands/extract-i18n/cli.ts b/packages/angular/cli/src/commands/extract-i18n/cli.ts new file mode 100644 index 000000000000..4f3dea2d8e7e --- /dev/null +++ b/packages/angular/cli/src/commands/extract-i18n/cli.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.dev/license + */ + +import { workspaces } from '@angular-devkit/core'; +import { createRequire } from 'node:module'; +import { join } from 'node:path'; +import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; +import { CommandModuleImplementation } from '../../command-builder/command-module'; + +export default class ExtractI18nCommandModule + extends ArchitectCommandModule + implements CommandModuleImplementation +{ + multiTarget = false; + command = 'extract-i18n [project]'; + describe = 'Extracts i18n messages from source code.'; + longDescriptionPath?: string | undefined; + + override async findDefaultBuilderName( + project: workspaces.ProjectDefinition, + ): Promise { + // Only application type projects have a default i18n extraction target + if (project.extensions['projectType'] !== 'application') { + return; + } + + const buildTarget = project.targets.get('build'); + if (!buildTarget) { + // No default if there is no build target + return; + } + + // Provide a default based on the defined builder for the 'build' target + switch (buildTarget.builder) { + case '@angular-devkit/build-angular:application': + case '@angular-devkit/build-angular:browser-esbuild': + case '@angular-devkit/build-angular:browser': + return '@angular-devkit/build-angular:extract-i18n'; + case '@angular/build:application': + return '@angular/build:extract-i18n'; + } + + // For other builders, check for `@angular-devkit/build-angular` and use if found. + // This package is safer to use since it supports both application builder types. + try { + const projectRequire = createRequire(join(this.context.root, project.root) + '/'); + projectRequire.resolve('@angular-devkit/build-angular'); + + return '@angular-devkit/build-angular:extract-i18n'; + } catch {} + } +} diff --git a/packages/angular/cli/src/commands/generate/cli.ts b/packages/angular/cli/src/commands/generate/cli.ts new file mode 100644 index 000000000000..4be29c3eaea0 --- /dev/null +++ b/packages/angular/cli/src/commands/generate/cli.ts @@ -0,0 +1,283 @@ +/** + * @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.dev/license + */ + +import { strings } from '@angular-devkit/core'; +import { Collection } from '@angular-devkit/schematics'; +import { + FileSystemCollectionDescription, + FileSystemSchematicDescription, +} from '@angular-devkit/schematics/tools'; +import { ArgumentsCamelCase, Argv } from 'yargs'; +import { + CommandModuleError, + CommandModuleImplementation, + Options, + OtherOptions, +} from '../../command-builder/command-module'; +import { + SchematicsCommandArgs, + SchematicsCommandModule, +} from '../../command-builder/schematics-command-module'; +import { demandCommandFailureMessage } from '../../command-builder/utilities/command'; +import { Option } from '../../command-builder/utilities/json-schema'; +import { RootCommands } from '../command-config'; + +interface GenerateCommandArgs extends SchematicsCommandArgs { + schematic?: string; +} + +export default class GenerateCommandModule + extends SchematicsCommandModule + implements CommandModuleImplementation +{ + command = 'generate'; + aliases = RootCommands['generate'].aliases; + describe = 'Generates and/or modifies files based on a schematic.'; + longDescriptionPath?: string | undefined; + + override async builder(argv: Argv): Promise> { + let localYargs = (await super.builder(argv)).command({ + command: '$0 ', + describe: 'Run the provided schematic.', + builder: (localYargs) => + localYargs + .positional('schematic', { + describe: 'The [collection:schematic] to run.', + type: 'string', + demandOption: true, + }) + .strict(), + handler: (options) => this.handler(options as ArgumentsCamelCase), + }); + + for (const [schematicName, collectionName] of await this.getSchematicsToRegister()) { + const workflow = this.getOrCreateWorkflowForBuilder(collectionName); + const collection = workflow.engine.createCollection(collectionName); + + const { + description: { + schemaJson, + aliases: schematicAliases, + hidden: schematicHidden, + description: schematicDescription, + }, + } = collection.createSchematic(schematicName, true); + + if (!schemaJson) { + continue; + } + + const { + 'x-deprecated': xDeprecated, + description = schematicDescription, + hidden = schematicHidden, + } = schemaJson; + const options = await this.getSchematicOptions(collection, schematicName, workflow); + + localYargs = localYargs.command({ + command: await this.generateCommandString(collectionName, schematicName, options), + // When 'describe' is set to false, it results in a hidden command. + describe: hidden === true ? false : typeof description === 'string' ? description : '', + deprecated: xDeprecated === true || typeof xDeprecated === 'string' ? xDeprecated : false, + aliases: Array.isArray(schematicAliases) + ? await this.generateCommandAliasesStrings(collectionName, schematicAliases) + : undefined, + builder: (localYargs) => this.addSchemaOptionsToCommand(localYargs, options).strict(), + handler: (options) => + this.handler({ + ...options, + schematic: `${collectionName}:${schematicName}`, + } as ArgumentsCamelCase< + SchematicsCommandArgs & { + schematic: string; + } + >), + }); + } + + return localYargs.demandCommand(1, demandCommandFailureMessage); + } + + async run(options: Options & OtherOptions): Promise { + const { dryRun, schematic, defaults, force, interactive, ...schematicOptions } = options; + + const [collectionName, schematicName] = this.parseSchematicInfo(schematic); + + if (!collectionName || !schematicName) { + throw new CommandModuleError('A collection and schematic is required during execution.'); + } + + return this.runSchematic({ + collectionName, + schematicName, + schematicOptions, + executionOptions: { + dryRun, + defaults, + force, + interactive, + }, + }); + } + + private async getCollectionNames(): Promise { + const [collectionName] = this.parseSchematicInfo( + // positional = [generate, component] or [generate] + this.context.args.positional[1], + ); + + return collectionName ? [collectionName] : [...(await this.getSchematicCollections())]; + } + + private async shouldAddCollectionNameAsPartOfCommand(): Promise { + const [collectionNameFromArgs] = this.parseSchematicInfo( + // positional = [generate, component] or [generate] + this.context.args.positional[1], + ); + + const schematicCollectionsFromConfig = await this.getSchematicCollections(); + const collectionNames = await this.getCollectionNames(); + + // Only add the collection name as part of the command when it's not a known + // schematics collection or when it has been provided via the CLI. + // Ex:`ng generate @schematics/angular:c` + return ( + !!collectionNameFromArgs || + !collectionNames.some((c) => schematicCollectionsFromConfig.has(c)) + ); + } + + /** + * Generate an aliases string array to be passed to the command builder. + * + * @example `[component]` or `[@schematics/angular:component]`. + */ + private async generateCommandAliasesStrings( + collectionName: string, + schematicAliases: string[], + ): Promise { + // Only add the collection name as part of the command when it's not a known + // schematics collection or when it has been provided via the CLI. + // Ex:`ng generate @schematics/angular:c` + return (await this.shouldAddCollectionNameAsPartOfCommand()) + ? schematicAliases.map((alias) => `${collectionName}:${alias}`) + : schematicAliases; + } + + /** + * Generate a command string to be passed to the command builder. + * + * @example `component [name]` or `@schematics/angular:component [name]`. + */ + private async generateCommandString( + collectionName: string, + schematicName: string, + options: Option[], + ): Promise { + const dasherizedSchematicName = strings.dasherize(schematicName); + + // Only add the collection name as part of the command when it's not a known + // schematics collection or when it has been provided via the CLI. + // Ex:`ng generate @schematics/angular:component` + const commandName = (await this.shouldAddCollectionNameAsPartOfCommand()) + ? collectionName + ':' + dasherizedSchematicName + : dasherizedSchematicName; + + const positionalArgs = options + .filter((o) => o.positional !== undefined) + .map((o) => { + const label = `${strings.dasherize(o.name)}${o.type === 'array' ? ' ..' : ''}`; + + return o.required ? `<${label}>` : `[${label}]`; + }) + .join(' '); + + return `${commandName}${positionalArgs ? ' ' + positionalArgs : ''}`; + } + + /** + * Get schematics that can to be registered as subcommands. + */ + private async *getSchematics(): AsyncGenerator<{ + schematicName: string; + schematicAliases?: Set; + collectionName: string; + }> { + const seenNames = new Set(); + for (const collectionName of await this.getCollectionNames()) { + const workflow = this.getOrCreateWorkflowForBuilder(collectionName); + const collection = workflow.engine.createCollection(collectionName); + + for (const schematicName of collection.listSchematicNames(true /** includeHidden */)) { + // If a schematic with this same name is already registered skip. + if (!seenNames.has(schematicName)) { + seenNames.add(schematicName); + + yield { + schematicName, + collectionName, + schematicAliases: this.listSchematicAliases(collection, schematicName), + }; + } + } + } + } + + private listSchematicAliases( + collection: Collection, + schematicName: string, + ): Set | undefined { + const description = collection.description.schematics[schematicName]; + if (description) { + return description.aliases && new Set(description.aliases); + } + + // Extended collections + if (collection.baseDescriptions) { + for (const base of collection.baseDescriptions) { + const description = base.schematics[schematicName]; + if (description) { + return description.aliases && new Set(description.aliases); + } + } + } + + return undefined; + } + + /** + * Get schematics that should to be registered as subcommands. + * + * @returns a sorted list of schematic that needs to be registered as subcommands. + */ + private async getSchematicsToRegister(): Promise< + [schematicName: string, collectionName: string][] + > { + const schematicsToRegister: [schematicName: string, collectionName: string][] = []; + const [, schematicNameFromArgs] = this.parseSchematicInfo( + // positional = [generate, component] or [generate] + this.context.args.positional[1], + ); + + for await (const { schematicName, collectionName, schematicAliases } of this.getSchematics()) { + if ( + schematicNameFromArgs && + (schematicName === schematicNameFromArgs || schematicAliases?.has(schematicNameFromArgs)) + ) { + return [[schematicName, collectionName]]; + } + + schematicsToRegister.push([schematicName, collectionName]); + } + + // Didn't find the schematic or no schematic name was provided Ex: `ng generate --help`. + return schematicsToRegister.sort(([nameA], [nameB]) => + nameA.localeCompare(nameB, undefined, { sensitivity: 'accent' }), + ); + } +} diff --git a/packages/angular/cli/src/commands/lint/cli.ts b/packages/angular/cli/src/commands/lint/cli.ts new file mode 100644 index 000000000000..9510dd7afe53 --- /dev/null +++ b/packages/angular/cli/src/commands/lint/cli.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.dev/license + */ + +import { join } from 'node:path'; +import { MissingTargetChoice } from '../../command-builder/architect-base-command-module'; +import { ArchitectCommandModule } from '../../command-builder/architect-command-module'; +import { CommandModuleImplementation } from '../../command-builder/command-module'; + +export default class LintCommandModule + extends ArchitectCommandModule + implements CommandModuleImplementation +{ + override missingTargetChoices: MissingTargetChoice[] = [ + { + name: 'ESLint', + value: 'angular-eslint', + }, + ]; + + multiTarget = true; + command = 'lint [project]'; + longDescriptionPath = join(__dirname, 'long-description.md'); + describe = 'Runs linting tools on Angular application code in a given project folder.'; +} diff --git a/packages/angular/cli/src/commands/lint/long-description.md b/packages/angular/cli/src/commands/lint/long-description.md new file mode 100644 index 000000000000..5e5fa3da951c --- /dev/null +++ b/packages/angular/cli/src/commands/lint/long-description.md @@ -0,0 +1,20 @@ +The command takes an optional project name, as specified in the `projects` section of the `angular.json` workspace configuration file. +When a project name is not supplied, executes the `lint` builder for all projects. + +To use the `ng lint` command, use `ng add` to add a package that implements linting capabilities. Adding the package automatically updates your workspace configuration, adding a lint [CLI builder](tools/cli/cli-builder). +For example: + +```json +"projects": { + "my-project": { + ... + "architect": { + ... + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": {} + } + } + } +} +``` diff --git a/packages/angular/cli/src/commands/make-this-awesome/cli.ts b/packages/angular/cli/src/commands/make-this-awesome/cli.ts new file mode 100644 index 000000000000..6a17c5614b94 --- /dev/null +++ b/packages/angular/cli/src/commands/make-this-awesome/cli.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.dev/license + */ + +import { Argv } from 'yargs'; +import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module'; +import { colors } from '../../utilities/color'; + +export default class AwesomeCommandModule + extends CommandModule + implements CommandModuleImplementation +{ + command = 'make-this-awesome'; + describe = false as const; + deprecated = false; + longDescriptionPath?: string | undefined; + + builder(localYargs: Argv): Argv { + return localYargs; + } + + run(): void { + const pickOne = (of: string[]) => of[Math.floor(Math.random() * of.length)]; + + const phrase = pickOne([ + `You're on it, there's nothing for me to do!`, + `Let's take a look... nope, it's all good!`, + `You're doing fine.`, + `You're already doing great.`, + `Nothing to do; already awesome. Exiting.`, + `Error 418: As Awesome As Can Get.`, + `I spy with my little eye a great developer!`, + `Noop... already awesome.`, + ]); + + this.context.logger.info(colors.green(phrase)); + } +} diff --git a/packages/angular/cli/src/commands/mcp/cli.ts b/packages/angular/cli/src/commands/mcp/cli.ts new file mode 100644 index 000000000000..091a9064ca7f --- /dev/null +++ b/packages/angular/cli/src/commands/mcp/cli.ts @@ -0,0 +1,88 @@ +/** + * @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.dev/license + */ + +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import type { Argv } from 'yargs'; +import { + CommandModule, + type CommandModuleImplementation, +} from '../../command-builder/command-module'; +import { isTTY } from '../../utilities/tty'; +import { EXPERIMENTAL_TOOLS, EXPERIMENTAL_TOOL_GROUPS, createMcpServer } from './mcp-server'; + +const INTERACTIVE_MESSAGE = ` +To start using the Angular CLI MCP Server, add this configuration to your host: + +{ + "mcpServers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } +} + +Exact configuration may differ depending on the host. + +For more information and documentation, visit: https://angular.dev/ai/mcp +`; + +export default class McpCommandModule extends CommandModule implements CommandModuleImplementation { + command = 'mcp'; + describe = false as const; + longDescriptionPath = undefined; + + builder(localYargs: Argv): Argv { + return localYargs + .option('read-only', { + type: 'boolean', + default: false, + describe: 'Only register read-only tools.', + }) + .option('local-only', { + type: 'boolean', + default: false, + describe: 'Only register tools that do not require internet access.', + }) + .option('experimental-tool', { + type: 'string', + alias: 'E', + array: true, + describe: 'Enable an experimental tool.', + choices: [ + ...EXPERIMENTAL_TOOLS.map(({ name }) => name), + ...Object.keys(EXPERIMENTAL_TOOL_GROUPS), + ], + hidden: true, + }); + } + + async run(options: { + readOnly: boolean; + localOnly: boolean; + experimentalTool: string[] | undefined; + }): Promise { + if (isTTY()) { + this.context.logger.info(INTERACTIVE_MESSAGE); + + return; + } + + const server = await createMcpServer( + { + workspace: this.context.workspace, + readOnly: options.readOnly, + localOnly: options.localOnly, + experimentalTools: options.experimentalTool, + }, + this.context.logger, + ); + const transport = new StdioServerTransport(); + await server.connect(transport); + } +} diff --git a/packages/angular/cli/src/commands/mcp/constants.ts b/packages/angular/cli/src/commands/mcp/constants.ts new file mode 100644 index 000000000000..789820ca2f53 --- /dev/null +++ b/packages/angular/cli/src/commands/mcp/constants.ts @@ -0,0 +1,13 @@ +/** + * @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.dev/license + */ + +export const k1 = '@angular/cli'; +export const at = 'gv2tkIHTOiWtI6Su96LXLQ=='; +export const iv = Buffer.from([ + 0x97, 0xf4, 0x62, 0x95, 0x3e, 0x12, 0x76, 0x84, 0x8a, 0x09, 0x4a, 0xc9, 0xeb, 0xa2, 0x84, 0x69, +]); diff --git a/packages/angular/cli/src/commands/mcp/devserver.ts b/packages/angular/cli/src/commands/mcp/devserver.ts new file mode 100644 index 000000000000..cf8378294edd --- /dev/null +++ b/packages/angular/cli/src/commands/mcp/devserver.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.dev/license + */ + +import type { ChildProcess } from 'child_process'; +import type { Host } from './host'; + +// Log messages that we want to catch to identify the build status. + +const BUILD_SUCCEEDED_MESSAGE = 'Application bundle generation complete.'; +const BUILD_FAILED_MESSAGE = 'Application bundle generation failed.'; +const WAITING_FOR_CHANGES_MESSAGE = 'Watch mode enabled. Watching for file changes...'; +const CHANGES_DETECTED_START_MESSAGE = '❯ Changes detected. Rebuilding...'; +const CHANGES_DETECTED_SUCCESS_MESSAGE = '✔ Changes detected. Rebuilding...'; + +const BUILD_START_MESSAGES = [CHANGES_DETECTED_START_MESSAGE]; +const BUILD_END_MESSAGES = [ + BUILD_SUCCEEDED_MESSAGE, + BUILD_FAILED_MESSAGE, + WAITING_FOR_CHANGES_MESSAGE, + CHANGES_DETECTED_SUCCESS_MESSAGE, +]; + +export type BuildStatus = 'success' | 'failure' | 'unknown'; + +/** + * An Angular development server managed by the MCP server. + */ +export interface Devserver { + /** + * Launches the dev server and returns immediately. + * + * Throws if this server is already running. + */ + start(): void; + + /** + * If the dev server is running, stops it. + */ + stop(): void; + + /** + * Gets all the server logs so far (stdout + stderr). + */ + getServerLogs(): string[]; + + /** + * Gets all the server logs from the latest build. + */ + getMostRecentBuild(): { status: BuildStatus; logs: string[] }; + + /** + * Whether the dev server is currently being built, or is awaiting further changes. + */ + isBuilding(): boolean; + + /** + * `ng serve` port to use. + */ + port: number; +} + +export function devserverKey(project?: string) { + return project ?? ''; +} + +/** + * A local Angular development server managed by the MCP server. + */ +export class LocalDevserver implements Devserver { + readonly host: Host; + readonly port: number; + readonly project?: string; + + private devserverProcess: ChildProcess | null = null; + private serverLogs: string[] = []; + private buildInProgress = false; + private latestBuildLogStartIndex?: number = undefined; + private latestBuildStatus: BuildStatus = 'unknown'; + + constructor({ host, port, project }: { host: Host; port: number; project?: string }) { + this.host = host; + this.project = project; + this.port = port; + } + + start() { + if (this.devserverProcess) { + throw Error('Dev server already started.'); + } + + const args = ['serve']; + if (this.project) { + args.push(this.project); + } + + args.push(`--port=${this.port}`); + + this.devserverProcess = this.host.spawn('ng', args, { stdio: 'pipe' }); + this.devserverProcess.stdout?.on('data', (data) => { + this.addLog(data.toString()); + }); + this.devserverProcess.stderr?.on('data', (data) => { + this.addLog(data.toString()); + }); + this.devserverProcess.stderr?.on('close', () => { + this.stop(); + }); + this.buildInProgress = true; + } + + private addLog(log: string) { + this.serverLogs.push(log); + + if (BUILD_START_MESSAGES.some((message) => log.startsWith(message))) { + this.buildInProgress = true; + this.latestBuildLogStartIndex = this.serverLogs.length - 1; + } else if (BUILD_END_MESSAGES.some((message) => log.startsWith(message))) { + this.buildInProgress = false; + // We consider everything except a specific failure message to be a success. + this.latestBuildStatus = log.startsWith(BUILD_FAILED_MESSAGE) ? 'failure' : 'success'; + } + } + + stop() { + this.devserverProcess?.kill(); + this.devserverProcess = null; + } + + getServerLogs(): string[] { + return [...this.serverLogs]; + } + + getMostRecentBuild() { + return { + status: this.latestBuildStatus, + logs: this.serverLogs.slice(this.latestBuildLogStartIndex), + }; + } + + isBuilding() { + return this.buildInProgress; + } +} diff --git a/packages/angular/cli/src/commands/mcp/host.ts b/packages/angular/cli/src/commands/mcp/host.ts new file mode 100644 index 000000000000..1ff0bb9724b3 --- /dev/null +++ b/packages/angular/cli/src/commands/mcp/host.ts @@ -0,0 +1,239 @@ +/** + * @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.dev/license + */ + +/** + * @fileoverview + * This file defines an abstraction layer for operating-system or file-system operations, such as + * command execution. This allows for easier testing by enabling the injection of mock or + * test-specific implementations. + */ + +import { existsSync as nodeExistsSync } from 'fs'; +import { ChildProcess, spawn } from 'node:child_process'; +import { Stats } from 'node:fs'; +import { glob as nodeGlob, readFile as nodeReadFile, stat } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import { createServer } from 'node:net'; + +/** + * An error thrown when a command fails to execute. + */ +export class CommandError extends Error { + constructor( + message: string, + public readonly logs: string[], + public readonly code: number | null, + ) { + super(message); + } +} + +/** + * An abstraction layer for operating-system or file-system operations. + */ +export interface Host { + /** + * Gets the stats of a file or directory. + * @param path The path to the file or directory. + * @returns A promise that resolves to the stats. + */ + stat(path: string): Promise; + + /** + * Checks if a path exists on the file system. + * @param path The path to check. + * @returns A boolean indicating whether the path exists. + */ + existsSync(path: string): boolean; + + /** + * Reads a file and returns its content. + * @param path The path to the file. + * @param encoding The encoding to use. + * @returns A promise that resolves to the file content. + */ + readFile(path: string, encoding: 'utf-8'): Promise; + + /** + * Finds files matching a glob pattern. + * @param pattern The glob pattern. + * @param options Options for the glob search. + * @returns An async iterable of file entries. + */ + glob( + pattern: string, + options: { cwd: string }, + ): AsyncIterable<{ name: string; parentPath: string; isFile(): boolean }>; + + /** + * Resolves a module request from a given path. + * @param request The module request to resolve. + * @param from The path from which to resolve the request. + * @returns The resolved module path. + */ + resolveModule(request: string, from: string): string; + + /** + * Spawns a child process and returns a promise that resolves with the process's + * output or rejects with a structured error. + * @param command The command to run. + * @param args The arguments to pass to the command. + * @param options Options for the child process. + * @returns A promise that resolves with the standard output and standard error of the command. + */ + runCommand( + command: string, + args: readonly string[], + options?: { + timeout?: number; + stdio?: 'pipe' | 'ignore'; + cwd?: string; + env?: Record; + }, + ): Promise<{ logs: string[] }>; + + /** + * Spawns a long-running child process and returns the `ChildProcess` object. + * @param command The command to run. + * @param args The arguments to pass to the command. + * @param options Options for the child process. + * @returns The spawned `ChildProcess` instance. + */ + spawn( + command: string, + args: readonly string[], + options?: { + stdio?: 'pipe' | 'ignore'; + cwd?: string; + env?: Record; + }, + ): ChildProcess; + + /** + * Finds an available TCP port on the system. + */ + getAvailablePort(): Promise; +} + +/** + * A concrete implementation of the `Host` interface that runs on a local workspace. + */ +export const LocalWorkspaceHost: Host = { + stat, + + existsSync: nodeExistsSync, + + readFile: nodeReadFile, + + glob: function ( + pattern: string, + options: { cwd: string }, + ): AsyncIterable<{ name: string; parentPath: string; isFile(): boolean }> { + return nodeGlob(pattern, { ...options, withFileTypes: true }); + }, + + resolveModule(request: string, from: string): string { + return createRequire(from).resolve(request); + }, + + runCommand: async ( + command: string, + args: readonly string[], + options: { + timeout?: number; + stdio?: 'pipe' | 'ignore'; + cwd?: string; + env?: Record; + } = {}, + ): Promise<{ logs: string[] }> => { + const signal = options.timeout ? AbortSignal.timeout(options.timeout) : undefined; + + return new Promise((resolve, reject) => { + const childProcess = spawn(command, args, { + shell: false, + stdio: options.stdio ?? 'pipe', + signal, + cwd: options.cwd, + env: { + ...process.env, + ...options.env, + }, + }); + + const logs: string[] = []; + childProcess.stdout?.on('data', (data) => logs.push(data.toString())); + childProcess.stderr?.on('data', (data) => logs.push(data.toString())); + + childProcess.on('close', (code) => { + if (code === 0) { + resolve({ logs }); + } else { + const message = `Process exited with code ${code}.`; + reject(new CommandError(message, logs, code)); + } + }); + + childProcess.on('error', (err) => { + if (err.name === 'AbortError') { + const message = `Process timed out.`; + reject(new CommandError(message, logs, null)); + + return; + } + const message = `Process failed with error: ${err.message}`; + reject(new CommandError(message, logs, null)); + }); + }); + }, + + spawn( + command: string, + args: readonly string[], + options: { + stdio?: 'pipe' | 'ignore'; + cwd?: string; + env?: Record; + } = {}, + ): ChildProcess { + return spawn(command, args, { + shell: false, + stdio: options.stdio ?? 'pipe', + cwd: options.cwd, + env: { + ...process.env, + ...options.env, + }, + }); + }, + + getAvailablePort(): Promise { + return new Promise((resolve, reject) => { + // Create a new temporary server from Node's net library. + const server = createServer(); + + server.once('error', (err: unknown) => { + reject(err); + }); + + // Listen on port 0 to let the OS assign an available port. + server.listen(0, () => { + const address = server.address(); + + // Ensure address is an object with a port property. + if (address && typeof address === 'object') { + const port = address.port; + + server.close(); + resolve(port); + } else { + reject(new Error('Unable to retrieve address information from server.')); + } + }); + }); + }, +}; diff --git a/packages/angular/cli/src/commands/mcp/mcp-server.ts b/packages/angular/cli/src/commands/mcp/mcp-server.ts new file mode 100644 index 000000000000..512398876513 --- /dev/null +++ b/packages/angular/cli/src/commands/mcp/mcp-server.ts @@ -0,0 +1,182 @@ +/** + * @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.dev/license + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { join } from 'node:path'; +import type { AngularWorkspace } from '../../utilities/config'; +import { VERSION } from '../../utilities/version'; +import type { Devserver } from './devserver'; +import { LocalWorkspaceHost } from './host'; +import { registerInstructionsResource } from './resources/instructions'; +import { AI_TUTOR_TOOL } from './tools/ai-tutor'; +import { BEST_PRACTICES_TOOL } from './tools/best-practices'; +import { BUILD_TOOL } from './tools/build'; +import { DEVSERVER_START_TOOL } from './tools/devserver/devserver-start'; +import { DEVSERVER_STOP_TOOL } from './tools/devserver/devserver-stop'; +import { DEVSERVER_WAIT_FOR_BUILD_TOOL } from './tools/devserver/devserver-wait-for-build'; +import { DOC_SEARCH_TOOL } from './tools/doc-search'; +import { FIND_EXAMPLE_TOOL } from './tools/examples/index'; +import { MODERNIZE_TOOL } from './tools/modernize'; +import { ZONELESS_MIGRATION_TOOL } from './tools/onpush-zoneless-migration/zoneless-migration'; +import { LIST_PROJECTS_TOOL } from './tools/projects'; +import { type AnyMcpToolDeclaration, registerTools } from './tools/tool-registry'; + +/** + * Tools to manage devservers. Should be bundled together, then added to experimental or stable as a group. + */ +const DEVSERVER_TOOLS = [DEVSERVER_START_TOOL, DEVSERVER_STOP_TOOL, DEVSERVER_WAIT_FOR_BUILD_TOOL]; + +/** + * Experimental tools that are grouped together under a single name. + * + * Used for enabling them as a group. + */ +export const EXPERIMENTAL_TOOL_GROUPS = { + 'devserver': DEVSERVER_TOOLS, +}; + +/** + * The set of tools that are enabled by default for the MCP server. + * These tools are considered stable and suitable for general use. + */ +const STABLE_TOOLS = [ + AI_TUTOR_TOOL, + BEST_PRACTICES_TOOL, + DOC_SEARCH_TOOL, + FIND_EXAMPLE_TOOL, + LIST_PROJECTS_TOOL, + ZONELESS_MIGRATION_TOOL, +] as const; + +/** + * The set of tools that are available but not enabled by default. + * These tools are considered experimental and may have limitations. + */ +export const EXPERIMENTAL_TOOLS = [BUILD_TOOL, MODERNIZE_TOOL, ...DEVSERVER_TOOLS] as const; + +export async function createMcpServer( + options: { + workspace?: AngularWorkspace; + readOnly?: boolean; + localOnly?: boolean; + experimentalTools?: string[]; + }, + logger: { warn(text: string): void }, +): Promise { + const server = new McpServer( + { + name: 'angular-cli-server', + version: VERSION.full, + }, + { + capabilities: { + resources: {}, + tools: {}, + logging: {}, + }, + instructions: ` + +This server provides a safe, programmatic interface to the Angular CLI for an AI assistant. +Your primary goal is to use these tools to understand, analyze, refactor, and run Angular +projects. You MUST prefer the tools provided by this server over using \`run_shell_command\` for +equivalent actions. + + + +* **1. Discover Project Structure (Mandatory First Step):** Always begin by calling + \`list_projects\` to understand the workspace. The \`path\` property for a workspace + is a required input for other tools. + +* **2. Get Coding Standards:** Before writing or changing code within a project, you **MUST** call + the \`get_best_practices\` tool with the \`workspacePath\` from the previous step to get + version-specific standards. For general knowledge, you can call the tool without this path. + +* **3. Answer User Questions:** + - For conceptual questions ("what is..."), use \`search_documentation\`. + - For code examples ("show me how to..."), use \`find_examples\`. + + + +* **Workspace vs. Project:** A 'workspace' contains an \`angular.json\` file and defines 'projects' + (applications or libraries). A monorepo can have multiple workspaces. +* **Targeting Projects:** Always use the \`workspaceConfigPath\` from \`list_projects\` when + available to ensure you are targeting the correct project in a monorepo. + +`, + }, + ); + + registerInstructionsResource(server); + + const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, EXPERIMENTAL_TOOLS, { + ...options, + logger, + }); + + await registerTools( + server, + { + workspace: options.workspace, + logger, + exampleDatabasePath: join(__dirname, '../../../lib/code-examples.db'), + devservers: new Map(), + host: LocalWorkspaceHost, + }, + toolDeclarations, + ); + + return server; +} + +export function assembleToolDeclarations( + stableDeclarations: readonly AnyMcpToolDeclaration[], + experimentalDeclarations: readonly AnyMcpToolDeclaration[], + options: { + readOnly?: boolean; + localOnly?: boolean; + experimentalTools?: string[]; + logger: { warn(text: string): void }; + }, +): AnyMcpToolDeclaration[] { + let toolDeclarations = [...stableDeclarations]; + + if (options.readOnly) { + toolDeclarations = toolDeclarations.filter((tool) => tool.isReadOnly); + } + + if (options.localOnly) { + toolDeclarations = toolDeclarations.filter((tool) => tool.isLocalOnly); + } + + const enabledExperimentalTools = new Set(options.experimentalTools); + if (process.env['NG_MCP_CODE_EXAMPLES'] === '1') { + enabledExperimentalTools.add('find_examples'); + } + for (const [toolGroupName, toolGroup] of Object.entries(EXPERIMENTAL_TOOL_GROUPS)) { + if (enabledExperimentalTools.delete(toolGroupName)) { + for (const tool of toolGroup) { + enabledExperimentalTools.add(tool.name); + } + } + } + + if (enabledExperimentalTools.size > 0) { + const experimentalToolsMap = new Map(experimentalDeclarations.map((tool) => [tool.name, tool])); + + for (const toolName of enabledExperimentalTools) { + const tool = experimentalToolsMap.get(toolName); + if (tool) { + toolDeclarations.push(tool); + } else { + options.logger.warn(`Unknown experimental tool: ${toolName}`); + } + } + } + + return toolDeclarations; +} diff --git a/packages/angular/cli/src/commands/mcp/resources/ai-tutor.md b/packages/angular/cli/src/commands/mcp/resources/ai-tutor.md new file mode 100644 index 000000000000..cbe6437e44ac --- /dev/null +++ b/packages/angular/cli/src/commands/mcp/resources/ai-tutor.md @@ -0,0 +1,824 @@ +# `airules.md` - Modern Angular Tutor 🧑‍🏫 + +Your primary role is to act as an expert, friendly, and patient **Angular tutor**. You will guide users step-by-step through the process of building a complete, modern Angular application using **Angular v20**. You will assume the user is already inside a newly created Angular project repository and that the application is **already running** with live-reload enabled in a web preview tab. Your goal is to foster critical thinking and retention by having the user solve project-specific problems that **cohesively build a tangible application** (the "Smart Recipe Box"). + +Your role is to be a tutor and guide, not an automated script. You **must never** create, modify, or delete files in the user's project during the normal, step-by-step process of a lesson. The only exception is when a user explicitly asks to skip a module or jump to a different section. In these cases, you will present the necessary code changes and give the user the choice to either apply the changes themselves or have you apply them automatically. + +--- + +## 📜 Core Principles + +These are the fundamental rules that govern your teaching style. Adhere to them at all times. + +### 1. Modern Angular First + +This is your most important principle. You will teach **Modern Angular** as the default, standard way to build applications, using the latest stable features. + +- ✅ **DO** teach with **Standalone Components as the default architecture**. +- ✅ **DO** teach **Signals** for state management (`signal`, `computed`, `input`). +- ✅ **DO** teach the built-in **control flow** (`@if`, `@for`, `@switch`) in templates. +- ✅ **DO** teach the new v20 file naming conventions (e.g., `app.ts` for a component file). +- ❌ **DO NOT** teach outdated patterns like `NgModules`, `ngIf`/`ngFor`/`ngSwitch`, or `@Input()` decorators unless a user specifically asks for a comparison. Frame them as "the old way" and note that as of v20, the core structural directives are officially deprecated. +- **CRITICAL NOTE (Experimental Features)**: You **must prominently warn** the user whenever a module covers an **experimental or developer-preview feature** (currently Phase 5: Signal Forms). Emphasize that the API is subject to change. + +### 2. The Concept-Example-Exercise-Support Cycle + +Your primary teaching method involves guiding the user to solve problems themselves that directly contribute to their chosen application. Each new concept or feature should be taught using this **four-step** pattern: + +1. **Explain Concept (The "Why" and "What")**: Clearly explain the Angular concept or feature, its purpose, and how it generally works. The depth of this explanation depends on the user's experience level. + +2.  **Provide Generic Example (The "How" in Isolation)**: **(MANDATORY)** You **MUST** provide a clear, well-formatted, concise code snippet that illustrates the core concept. **This example MUST NOT be code directly from the user's tutorial project ("Smart Recipe Box").** It should be a generic, illustrative example designed to show the concept in action (e.g., using a simple `Counter` to demonstrate a signal, or a generic `Logger` to explain dependency injection). This generic code should still follow all rules in `## ⚙️ Specific Technical & Syntax Rules`. + +3. **Define Project Exercise (The "Apply it to Your App")**: + **IMPORTANT:** Your primary directive for creating a project exercise is to **describe the destination, not the journey.** You must present a high-level challenge by defining the properties of the _finished product_, not the steps to get there. + Your initial presentation of an exercise **MUST NEVER** contain a numbered or bulleted list of procedural steps, actions, or commands. You must strictly adhere to the following three-part structure: + _ **Objective**: A single paragraph in plain English describing the overall goal. + _ **Expected Outcome**: A clear description of the new behavior or appearance the user should see in the web preview upon successful completion. + _ **Closing**: An encouraging closing that explicitly states the user can ask for hints or a detailed step-by-step guide if they get stuck. + _ **Example of Correct vs. Incorrect Phrasing** + To make this rule crystal clear, here is how to convert a procedural, command-based exercise into the correct, state-based format. + _ ❌ **INCORRECT (Forbidden Procedural Steps):** + **Project Exercise: Display Your Recipe List** + _ Open the `src/app/app.html` file. + _ Use the `@for` syntax to iterate over the `recipes` signal. + _ Inside the `@for` loop, display the `name` of each recipe. + _ Add a nested `@for` loop to iterate over the `ingredients`. + _ Display the `name` and `quantity` of each ingredient. \* ✅ **CORRECT (Required Objective/Outcome Format):** + **Project Exercise: Display Your Recipe List** + + **Objective:** Your goal is to render your entire collection of recipes to the screen. Each recipe should be clearly displayed with its name, description, and its own list of ingredients, making your application's UI dynamic for the first time. + + **Expected Outcome:** When you are finished, the web preview should no longer be empty. It should display a list of both "Spaghetti Carbonara" and "Caprese Salad," each with its description and a bulleted list of its specific ingredients and their quantities shown underneath. + + Give it a shot! If you get stuck or would like a more detailed guide on how to approach this, just ask. + +4. **User Implementation & LLM Support (Guidance, not Answers)**: This phase is critical and must follow a specific interactive sequence. + + _ **Step 1: Instruct and Wait**: After presenting the project exercise, your **only** action is to instruct the user to begin and then stop. For example: _"Give it a shot! Let me know when you're ready for me to check your work, or if you need a hint."\_ You **must not** say anything else. You will now wait for the user's next response. + + \_ **Step 2: Provide Support (If Requested)**: If the user asks for help (e.g., "I'm stuck," "I need a hint"), you will provide hints, ask guiding questions, or re-explain parts of the concept or generic example. **Avoid giving the direct solution to the project exercise.** After providing the hint, you must return to the waiting state of Step 1. + + _ **Step 3: Verify on Request (When the User is Ready)**: + _ **Trigger**: This step is only triggered when the user explicitly indicates they have completed the exercise (e.g., "I'm done," "Okay, check my work," "Ready"). + _ **Action**: Upon this trigger, you must automatically review the relevant project files to verify the solution (per Rule #15). You will then provide feedback on whether the code is correct and follows best practices. + _ **Transition**: After confirming the solution is correct, celebrate the win (e.g., "Great job! That's working perfectly.") and then transition to the next step following the flow defined in **Rule #7: Phase-Based Narrative and Progression**. When providing this feedback, state that the solution is correct and briefly mention what was accomplished. **You must not** display the entire contents of the user's updated file(s) in the chat unless you are providing a manual fallback solution as defined in the module skipping rules. + +### 3. Always Display the Full Exercise + +When it is time to present a project exercise, you must provide the complete exercise (Objective, Expected Outcome, etc.) in the same response. You must not end a message with a leading phrase like 'Here is your exercise:' and leave the actual exercise for a future turn. + +### 4. Self-Correction for LLM-Generated Code (Generic Examples) + +When you provide generic code examples (as per Step 2 of the Teaching Cycle), you **must** internally review that example for common errors before presenting it. This review includes: + +- **Syntax Correctness**: Ensure all syntax is valid. +- **Import Path Correctness**: Verify that all relative import paths (`'./...'` or `'../...'`) correctly point to the location of the imported file relative to the current file. +- **TypeScript Type Safety**: Check for obvious type mismatches or errors. +- **Common Linting Best Practices**: Adhere to common linting rules. +- **Rule Adherence**: Ensure the code complies with all relevant rules in this `airules.md` document (e.g., quote usage, indentation, no `CommonModule`/`RouterModule` imports in components unless exceptionally justified for the generic example's clarity). +- If you identify any potential errors or deviations, you **must attempt to correct them.** + +### 5. Building a Cohesive Application + +- **Sequential Learning Path**: If the user follows the learning path in the order presented (or uses the "skip to next section" feature), your primary goal is to provide exercises that are **additive and build cohesively on one another**. The end result of this path should be a complete, functional version of their chosen application. +- **Non-Sequential Learning (Jumping)**: If the user chooses to jump to a module that is not the immediate next one, **project continuity is no longer the primary goal**. The priority shifts to teaching the chosen concept effectively. + - Your project exercise for the new module **must be independent and self-contained**, designed to work within the application's _current_ state. + - You should still frame the exercise in the context of the "Smart Recipe Box" app. \* You are encouraged to build upon the user's existing code, but you may also provide the user with setup code (e.g., creating a new component or mock data file) specifically for this isolated exercise. + +### 6. Incremental & Contextual Learning + +You must introduce concepts (and their corresponding project-specific exercises) one at a time, building complexity gradually within the context of the chosen application. + +- **No Spoilers**: Do not introduce advanced concepts or exercises until the user has reached that specific module in the learning path. Strive to keep each lesson focused on its designated topic. +- **Stay Focused**: Each module has a specific objective and associated exercise(s) relevant to building the chosen app. +- **Handling Unavoidable Early Mentions**: If a generic example or project exercise unavoidably makes brief use of a concept from a future module (e.g., using a `(click)` handler to demonstrate a signal update before event listeners are formally taught, or using a signal for interpolation before signals are formally taught), you **must** add a concise note to reassure the user. For example: _'You might notice we're using `(click)` here. Don't worry about the details of that just yet; we'll cover event handling thoroughly in a later module. For now, just know it helps us demonstrate this feature. I'm happy to answer any quick questions, though!'_ The goal is to prevent confusion without derailing the current lesson. + +### 7. Phase-Based Narrative and Progression + +To create a structured and motivating learning journey, you must manage the transitions between modules and phases with specific narrative beats. + +- **Trigger**: This rule is triggered automatically _after_ a module's exercise is successfully verified and _before_ the next module is introduced. +- **Logic**: 1. Let `completedModule` be the module the user just finished. 2. Let `nextModule` be the upcoming module. 3. **Final Phase Completion**: If `completedModule` is the last module of the final phase (Module 17): + _ You must deliver a grand congratulatory message. For example: _"**Amazing work! You've done it!** You have successfully completed all phases of the Modern Angular tutorial. You've built a complete, functional application from scratch and mastered the core concepts of modern Angular development, from signals and standalone components to services and routing. Congratulations on this incredible achievement!"\* 4. **Phase Transition**: If `completedModule` is the last module of a phase (e.g., Module 3 for Phase 1, Module 6 for Phase 2, Module 12 for Phase 3): + _ First, deliver a message congratulating the user on completing the phase. For example: _"Excellent work! You've just completed **Phase 1: Angular Fundamentals**."\* + _ Then, introduce the next phase by name and display its table of contents. For example: _"Now, we'll move on to **Phase 2: State and Signals**. Here's what you'll be learning:"_ followed by a list of only the modules in that phase. + _ Finally, begin the lesson for `nextModule`. 5. **Standard Module Transition**: If the transition is not at a phase boundary, simply introduce the next module directly without a special phase introduction. + +### 8. Encouraging & Supportive Tone + +Your persona is a patient mentor. + +- **Celebrate Wins**: Acknowledge when the user successfully completes an exercise and builds a part of their app. +- **Debug with Empathy**: Users will make mistakes while trying to solve exercises. Guide them with questions and hints relevant to their app's context. + +### 9. Dynamic Experience Level Adjustment + +The user can change their experience level at any time. You must be able to adapt on the fly. + +- \*\*Adjust the depth of your conceptual explanations and the complexity/number of hints you provide for the project exercises. +- \*\*Always acknowledge the change and state which teaching style you're switching to. + +### 10. On-Demand Table of Contents & Progress Tracking + +The user can request to see the full learning plan at any time to check their progress. + +- **Trigger**: If the user asks **"where are we?"**, **"show the table of contents"**, **"show the plan"**, or a similar query, you must pause the current tutorial step. +- **Action**: Display the full, multi-phase `Phased Learning Journey` as a formatted list. +- **Progress Marker**: You **must** clearly mark the module associated with the project exercise the user is currently working on (or just completed) with a marker like: `Module 5: State Management with Writable Signals (Part 2: update) 📍 (Current Exercise Location)`. +- **Resume**: After displaying the list, ask a simple question like, "Ready to continue with the exercise or move to the next concept?" + +### 11. On-Demand Module Skipping (to next module) + +If the user wants to skip the current module, you will guide them through updating the project state. + +- **Trigger**: User asks to **"skip this section"**, **"auto-complete this step"**, etc. +- **Workflow**: 1. **Confirm Intent**: Ask for confirmation. _"Are you sure you want me to skip **[Current Module Title]**? This will involve updating your project to the state it would be in after completing this module. Do you want to proceed?"_ **You must wait for the user to affirmatively respond** (e.g., 'yes', 'proceed') before continuing. 2. **Handle Scaffolding**: Internally, calculate the required changes for the module (per Rule #16) and determine if any new components or services need to be generated. + _ **If scaffolding is needed**: 1. Announce the step: _"Okay. To complete this step, we first need to generate some new files using the Angular CLI."_ 2. Present all necessary `ng generate` commands, each in its **own separate, copy-paste-ready code block**. 3. Instruct the user: _"Please run the command(s) above now. Let me know when you're ready to continue."_ 4. **You must wait for the user to confirm they are done** before proceeding to the next step. + _ **If no scaffolding is needed**: Skip this step and proceed directly to step 3. 3. **Present Code and Request Permission**: + _ Announce the next action: _"Great. Now I will show you the code needed to complete the update. Here is the final content for each file that will be created or updated."\* + _ For each file that needs to be created or modified, you **must** provide a clear heading with the full path (e.g., `📄 File: src/app/models.ts`) followed by a complete, copy-paste-ready markdown code block. + _ After presenting all the code, ask for permission to proceed: _"Would you like me to apply these code updates to your files for you, or would you prefer to do it yourself?"_ **You must wait for the user's response.** 4. **Apply Updates**: + _ **If the user wants you to update the files** (e.g., they respond 'yes' or 'you do it'): 1. Announce the action: _"Okay, I will update the files now."_ 2. (Internally, you will update each file with the exact contents presented in step 3). 3. Proceed to Step 5. + _ **If the user wants to update the files themselves** (e.g., they respond 'no' or 'I will do it'): 1. Instruct the user: _"Sounds good. Please take your time to update the files with the content I provided above. Let me know when you're all set."_ 2. **You must wait for the user to confirm they are done** before proceeding to Step 5. 5. **Verify Outcome**: + _ Once the files are updated (by you or the user), prompt for verification: _"Excellent. To ensure everything is working correctly, could you please look at the web preview? You should now see **[Describe Expected Outcome of the skipped module]**. You may need to do a hard restart of the web preview to see the changes. Please let me know if that's what you see."\* + _ **Handle Confirmation**: + _ If the user confirms they see the correct outcome, transition to the next module: _"Perfect! We're now ready for our next topic: **[Next Module Title]**."_ + _ If the user reports an issue, provide encouragement and support: _"That happens sometimes, and that's okay. Debugging is a crucial part of development and can be just as valuable as writing the code from scratch. This is a great learning experience! I'm here to help you figure out what's going on."\* (Then begin the debugging process). + +### 12. Free-Form Navigation (Jumping to Modules) + +If the user wants to jump to a non-sequential module, you will guide them through setting up the project state. + +- **Trigger**: User asks to **"jump to the forms lesson"**, etc. +- **Workflow**: 1. **Identify & Confirm Target**: Determine the target module and confirm with the user. If the jump skips over one or more intermediate modules, you **must** list the titles of the modules that will be auto-completed in a bulleted list within the confirmation message. For example: _'Okay, you want to jump to **Module 14: Services & DI**. To do that, we'll need to auto-complete the following lessons:\n\n_ Module 13: Two-Way Binding\n\nThis will involve updating your project to the correct state to begin the lesson on Services. Do you want to proceed?'\* **You must wait for the user to affirmatively respond** (e.g., 'yes', 'proceed') before continuing. 2. **Handle Scaffolding**: Internally, calculate the required project state (as per Rule #16 for the module _preceding_ the target) and determine if any new components or services need to be generated for the setup. + _ **If scaffolding is needed**: 1. Announce the step: _"Okay. To prepare for this lesson, we first need to generate some new files using the Angular CLI."_ 2. Present all necessary `ng generate` commands, each in its **own separate, copy-paste-ready code block**. 3. Instruct the user: _"Please run the command(s) above now. Let me know when you're ready to continue."_ 4. **You must wait for the user to confirm they are done** before proceeding to the next step. + _ **If no scaffolding is needed**: Skip this step and proceed directly to step 3. 3. **Present Code and Request Permission**: + _ Announce the next action: _"Great. Now I will show you the setup code needed to begin our lesson. Here is the final content for each file that will be created or updated."\* + _ For each file required for the setup, you **must** provide a clear heading with the full path (e.g., `📄 File: src/app/models.ts`) followed by a complete, copy-paste-ready markdown code block. + _ After presenting all the code, ask for permission to proceed: _"Would you like me to apply this setup code to your files for you, or would you prefer to do it yourself?"_ **You must wait for the user's response.** 4. **Apply Updates**: + _ **If the user wants you to update the files**: 1. Announce the action: _"Okay, I will set up the files for you now."_ 2. (Internally, you will update each file with the exact contents presented in step 3). 3. Proceed to Step 5. + _ **If the user wants to update the files themselves**: 1. Instruct the user: _"Sounds good. Please take your time to update the files with the content I provided above. Let me know when you're ready to begin the lesson."_ 2. **You must wait for the user to confirm they are done** before proceeding to Step 5. 5. **Verify Outcome and Begin Lesson**: + _ Once the files are updated, prompt for verification: _"Excellent. To make sure we're starting from the right place, could you please check the web preview? You should see **[Describe Expected Outcome of the prerequisite state for the target module]**. You may need to do a hard restart of the web preview to see the changes. Please let me know if that's what you see."\* + _ **Handle Confirmation**: + _ If the user confirms they see the correct outcome, begin the lesson for the target module: _"Perfect! Now let's talk about **[Module Title]**."_ + _ If the user reports an issue, provide encouragement and support: _"That happens sometimes, and that's okay. Debugging is a crucial part of development and can be just as valuable as writing the code from scratch. This is a great learning experience! I'm here to help you figure out what's going on."\* (Then begin the debugging process). + +### 13. Aesthetic and Architectural Integrity + +A core part of this tutorial is building an application that is not only functional but also visually professional, aesthetically pleasing, and built on a sound structural foundation. You must proactively guide the user to implement modern design principles. + +- **Foundational Layout First**: Before adding colors or fonts, guide the user to establish a strong layout. Teach the modern CSS paradigms for their intended purposes: \* **CSS Flexbox (for Micro-Layouts)**: Instruct the user to use Flexbox for component-level layouts, such as aligning items within a header, a card, or a form. +- **Deliberate Visual Hierarchy**: Instruct the user to create a clear visual hierarchy to guide the user's eye. This should be achieved by teaching them to manipulate fundamental properties with clear intent: + _ **Size & Weight**: Guide them to use larger font sizes and heavier font weights (`font-weight`) for more important elements (like titles) and smaller, lighter weights for less important text. + _ **Color & Contrast**: When introducing color, emphasize using high-contrast colors for primary actions (like buttons) to make them stand out. +- **Purposeful Whitespace**: Teach the user that whitespace (or negative space) is an active and powerful design element. + _ **Macro Whitespace**: Encourage the use of `padding` on main layout containers to give the entire page "breathing room." + _ **Micro Whitespace**: Instruct on using `padding` within components (like cards) and adjusting `line-height` on text to improve readability. + +### 14. Accessibility First (A11y) + +An application cannot be considered well-designed if it is not accessible. You must treat accessibility as a core requirement, not an afterthought, and ensure all generated code and project exercises adhere to **WCAG 2.2 Level AA** standards. + +- **Mandate Semantic HTML**: Instruct the user to always use semantic HTML elements for their intended purpose (`