mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b0854e2ec6 | |||
| 29f50976db | |||
| d207ea06d1 | |||
| 18e2dba7ae | |||
| e31eaf8595 | |||
| e6b7472d11 | |||
| a6db152c78 | |||
| 778edfc63c | |||
| 89893972c8 | |||
| aeff926381 | |||
| 24cc1441f5 | |||
| 6eea132952 | |||
| a47ea85c5e | |||
| 9ee4441c55 | |||
| e7b3b34e62 | |||
| ca2165e8df | |||
| d845643ba1 | |||
| 8f109da1c8 | |||
| 6de37b8f0a | |||
| 3cddc3d6bc | |||
| 0dfb4e8c8b | |||
| 97fa1cf544 | |||
| 9d4b580c12 | |||
| 0ef8b01804 | |||
| c06cbb2ec4 | |||
| 688b1c5b53 | |||
| 12ad314d5a | |||
| 8b9aea9503 | |||
| 1d9d74a2d1 | |||
| d4567b28f7 | |||
| e6d520f3d9 | |||
| e6f01d777d | |||
| fa3e7f87e7 | |||
| 8d184f9a31 | |||
| 2a61728137 | |||
| 63013546e1 | |||
| 9ebd41e39c | |||
| 34b2669d3a | |||
| d654c7933a | |||
| b101ad0d98 | |||
| 75a11d9ac8 | |||
| 2cdf074034 | |||
| 2566cbb48c | |||
| afeab76497 | |||
| bfb738c3c3 | |||
| eeef3e7479 | |||
| c2e5d76674 | |||
| d249665bd2 | |||
| 4f8a7280c1 | |||
| 04472d23c2 | |||
| 8dbae89ed4 | |||
| 6e13179fe1 | |||
| 1a66199a7f | |||
| 9e8a919385 | |||
| 367ef56224 | |||
| fdc6786b22 | |||
| b9c1604399 | |||
| 859eb13f98 | |||
| 1f84f01ff9 | |||
| 462c157e8a | |||
| 91366401f0 | |||
| b98b0da37e | |||
| dffb7c5218 | |||
| a926402b4c | |||
| 9067d362d4 | |||
| dc661ebda8 | |||
| c1f9d807e6 | |||
| 2afaaf0bf9 | |||
| 0c9c8f51df | |||
| cf27076918 | |||
| 48010b3c73 | |||
| 91a576db20 | |||
| 2050d8408d | |||
| e1d61243bd | |||
| 65330f03a9 | |||
| 17d3de25da | |||
| 2ed5a2db79 | |||
| 7dad7dfecd | |||
| 88b2ff25d7 | |||
| 7b5405b070 | |||
| d8d7784b3a | |||
| f6957b24cd | |||
| 8267d25dfd | |||
| 71f428679b | |||
| 12a9d2152e | |||
| 1c31828620 | |||
| 51fd5e3742 | |||
| 723342a60a | |||
| a8d5a31356 | |||
| 9ceadb0903 | |||
| f6bce3158a | |||
| ca4df9c334 | |||
| d87917542a | |||
| c7a2d76722 | |||
| 7b7dce358d | |||
| 1670798aa8 | |||
| 305042a945 | |||
| 8493eb7e65 | |||
| 7fe7836925 | |||
| 60104086ba | |||
| 8a79690fdc | |||
| 0562eae68e | |||
| fbc53e7c2d | |||
| f2523a8fef | |||
| e6088ab736 | |||
| 0a17e98ae2 | |||
| fe602c9c4e | |||
| b9ab8b99a1 | |||
| 0540a0339e | |||
| 89e7e52041 | |||
| bdac537852 | |||
| 80e602ca5c | |||
| 9619255dd5 | |||
| eca48016c4 | |||
| ec60d10422 | |||
| 9557e87ad8 | |||
| 45116097c1 | |||
| 4a8ab4f149 | |||
| 9f8a256467 | |||
| 6da24e6451 | |||
| f78ab1ed43 | |||
| 3df4d898fa | |||
| a1e9ccb340 | |||
| 7c9c54922c | |||
| aa261e7e01 | |||
| ee3fe6b8a2 | |||
| 97c18f4527 | |||
| df5cc1fbbf | |||
| 723d21455a | |||
| 72073cfb16 | |||
| 41c6877c01 | |||
| 12e0cfab54 | |||
| aab41ca42f | |||
| 3482636b5a | |||
| b11923cd7a | |||
| 0cf388e882 | |||
| d19ddd1a87 | |||
| c380d56c6d | |||
| f84d826a3a | |||
| 78cf10b28b | |||
| a0e5b85694 | |||
| ace0a50646 | |||
| 0f27f89d38 | |||
| a1e036cfd1 | |||
| 7ea1d4720a | |||
| 560dd44de4 | |||
| 87cdd84c17 | |||
| 4059ecaed1 | |||
| e03291bc34 | |||
| ffebb3e785 | |||
| 0f435a3cf9 | |||
| 1c50869792 | |||
| f6b828e292 | |||
| bd42b795f7 |
@@ -1,3 +1 @@
|
||||
4.0.0
|
||||
# [NB: this comment has to be after the first line, see https://github.com/bazelbuild/bazelisk/issues/117]
|
||||
# When updating the Bazel version you also need to update the RBE toolchains version in package.bzl
|
||||
|
||||
+11
-8
@@ -24,19 +24,19 @@ version: 2.1
|
||||
# **NOTE 2 **: If you change the cache key prefix, also sync the cache_key_fallback to match.
|
||||
# **NOTE 3 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
|
||||
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
|
||||
var_3: &cache_key v4-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
var_3: &cache_key v4-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "aio/yarn.lock" }}
|
||||
# We invalidate the cache if the Bazel version changes because otherwise the `bazelisk` cache
|
||||
# folder will contain all previously used versions and ultimately cause the cache restoring to
|
||||
# be slower due to its growing size.
|
||||
var_4: &cache_key_fallback v4-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Windows needs its own cache key because binaries in node_modules are different.
|
||||
var_3_win: &cache_key_win v4-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
var_3_win: &cache_key_win v4-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "aio/yarn.lock" }}
|
||||
var_4_win: &cache_key_win_fallback v4-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
||||
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
||||
var_5: &components_repo_unit_tests_cache_key v1-angular-components-{{ checksum "month.txt" }}-7cc42f5d835b7971e9ff73e220b801cf7834d515
|
||||
var_5: &components_repo_unit_tests_cache_key v1-angular-components-{{ checksum "month.txt" }}-d090617912da8e70aa336aa5b4d804b1b535402e
|
||||
var_6: &components_repo_unit_tests_cache_key_fallback v1-angular-components-{{ checksum "month.txt" }}
|
||||
|
||||
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
|
||||
@@ -441,6 +441,9 @@ jobs:
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
- run: yarn --cwd aio payload-size aio-local
|
||||
# Run tests with RxJS v7.
|
||||
- run: yarn --cwd aio add rxjs@7.1.0
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
|
||||
test_aio_tools:
|
||||
executor: default-executor
|
||||
@@ -469,6 +472,10 @@ jobs:
|
||||
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
|
||||
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
|
||||
- run: yarn --cwd aio example-e2e --setup --local --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
# Run tests with RxJS v7.
|
||||
# (Exclude some examples that are not yet compatible with v7.)
|
||||
- run: yarn --cwd aio/tools/examples/shared add rxjs@7.1.0 && yarn --cwd aio boilerplate:add
|
||||
- run: yarn --cwd aio example-e2e --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --exclude=practical-observable-usage --exclude=upgrade-module --exclude=upgrade-phonecat
|
||||
|
||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||
aio_preview:
|
||||
@@ -671,7 +678,7 @@ jobs:
|
||||
background: true
|
||||
# add module umd tsc compile option so the test can work
|
||||
# properly in the legacy browsers
|
||||
- run: yarn tsc -p packages --module UMD
|
||||
- run: yarn tsc -p packages/tsconfig-legacy-saucelabs.json --module UMD
|
||||
- run: yarn tsc -p modules --module UMD
|
||||
- run: yarn bazel build //packages/zone.js:npm_package
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
@@ -734,10 +741,6 @@ jobs:
|
||||
# variable. It needs to be hardcoded here, because env variables interpolation is
|
||||
# not supported.
|
||||
- '/tmp/angular-components-repo'
|
||||
- run:
|
||||
# TODO(devversion): remove once https://github.com/angular/components/pull/23056 is available.
|
||||
name: Replace RBE container instance name
|
||||
command: sed -i "s#default_instance#primary_instance#g" ${COMPONENTS_REPO_TMP_DIR}/.bazelrc
|
||||
- run:
|
||||
# Updates the `angular/components` `package.json` file to refer to the release output
|
||||
# inside the `packages-dist` directory.
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ setPublicVar COMPONENTS_REPO_TMP_DIR "/tmp/angular-components-repo"
|
||||
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
|
||||
setPublicVar COMPONENTS_REPO_BRANCH "master"
|
||||
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
|
||||
setPublicVar COMPONENTS_REPO_COMMIT "7cc42f5d835b7971e9ff73e220b801cf7834d515"
|
||||
setPublicVar COMPONENTS_REPO_COMMIT "d090617912da8e70aa336aa5b4d804b1b535402e"
|
||||
|
||||
|
||||
####################################################################################################
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
name: "Bug Report"
|
||||
about: Report a bug in the Angular Framework
|
||||
---
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
# Bug Report
|
||||
|
||||
### Affected Package
|
||||
<!-- Can you pin-point one or more @angular/* packages as the source of the bug? -->
|
||||
<!-- edit: --> The issue is caused by package @angular/....
|
||||
|
||||
|
||||
### Is this a regression?
|
||||
|
||||
<!-- Did this behavior use to work in the previous version? -->
|
||||
<!-- edit: --> Yes, the previous version in which this bug was not present was: ....
|
||||
|
||||
|
||||
### Description
|
||||
<!-- edit: --> A clear and concise description of the problem...
|
||||
|
||||
|
||||
## Minimal Reproduction
|
||||
<!--
|
||||
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-ivy
|
||||
-->
|
||||
<!-- edit: --> https://stackblitz.com/...
|
||||
|
||||
<!--
|
||||
If StackBlitz is not suitable 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/blob/master/CONTRIBUTING.md#submit-issue
|
||||
-->
|
||||
|
||||
## Exception or Error
|
||||
<pre><code>
|
||||
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
|
||||
|
||||
</code></pre>
|
||||
|
||||
|
||||
## Your Environment
|
||||
|
||||
**Angular Version:**
|
||||
<pre><code>
|
||||
<!-- run `ng version` and paste output below -->
|
||||
|
||||
</code></pre>
|
||||
|
||||
**Anything else relevant?**
|
||||
<!-- 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. -->
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Bug Report
|
||||
description: Report a bug in the Angular Framework
|
||||
|
||||
body:
|
||||
- type: dropdown
|
||||
id: affected-packages
|
||||
attributes:
|
||||
label: Which @angular/* package(s) are the source of the bug?
|
||||
options:
|
||||
- animations
|
||||
- bazel
|
||||
- common
|
||||
- compiler-cli
|
||||
- compiler
|
||||
- elements
|
||||
- forms
|
||||
- language-service
|
||||
- localize
|
||||
- platform-browser-dynamic
|
||||
- platform-browser
|
||||
- platform-server
|
||||
- router
|
||||
- service-worker
|
||||
- upgrade
|
||||
- Don't known / other
|
||||
multiple: true
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: is-regression
|
||||
attributes:
|
||||
label: Is this a regression?
|
||||
options:
|
||||
- 'Yes'
|
||||
- 'No'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Please provide a link to a minimal reproduction of the bug
|
||||
|
||||
- type: textarea
|
||||
id: exception-or-error
|
||||
attributes:
|
||||
label: Please provide the exception or error you saw
|
||||
render: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Please provide the environment you discovered this bug in
|
||||
render: true
|
||||
placeholder: |
|
||||
Angular CLI: 12.0.5
|
||||
Node: 14.17.0
|
||||
Package Manager: yarn 1.22.10
|
||||
OS: linux x64
|
||||
|
||||
Angular: 12.0.5
|
||||
... animations, cli, common, compiler, compiler-cli, core, forms
|
||||
... platform-browser, platform-browser-dynamic, router
|
||||
|
||||
Package Version
|
||||
---------------------------------------------------------
|
||||
@angular-devkit/architect 0.1200.5
|
||||
@angular-devkit/build-angular 12.0.5
|
||||
@angular-devkit/core 12.0.5
|
||||
rxjs 6.6.7
|
||||
typescript 4.2.4
|
||||
|
||||
- type: textarea
|
||||
id: other
|
||||
attributes:
|
||||
label: Anything else?
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: "Feature Request"
|
||||
about: Suggest a feature for Angular Framework
|
||||
---
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
# Feature Request
|
||||
|
||||
### Relevant Package
|
||||
<!-- Can you pin-point one or more @angular/* packages the are relevant for this feature request? -->
|
||||
<!-- edit: --> This feature request is for @angular/....
|
||||
|
||||
|
||||
### Description
|
||||
<!-- edit: --> A clear and concise description of the problem or missing capability...
|
||||
|
||||
|
||||
### Describe the solution you'd like
|
||||
<!-- edit: --> If you have a solution in mind, please describe it.
|
||||
|
||||
|
||||
### Describe alternatives you've considered
|
||||
<!-- edit: --> Have you considered any alternative solutions or workarounds?
|
||||
@@ -0,0 +1,46 @@
|
||||
name: 'Feature Request'
|
||||
description: Suggest a feature for Angular Framework
|
||||
|
||||
body:
|
||||
- type: dropdown
|
||||
id: affected-packages
|
||||
attributes:
|
||||
label: Which @angular/* package(s) are relevant/releated to the feature request?
|
||||
options:
|
||||
- animations
|
||||
- bazel
|
||||
- common
|
||||
- compiler-cli
|
||||
- compiler
|
||||
- elements
|
||||
- forms
|
||||
- language-service
|
||||
- localize
|
||||
- platform-browser-dynamic
|
||||
- platform-browser
|
||||
- platform-server
|
||||
- router
|
||||
- service-worker
|
||||
- upgrade
|
||||
multiple: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed-solution
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives-considered
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
name: "Docs or angular.io Bug Report"
|
||||
about: Report an issue in Angular's documentation or angular.io application
|
||||
---
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
|
||||
# Docs or angular.io Bug Report
|
||||
|
||||
### Description
|
||||
|
||||
<!-- edit: --> A clear and concise description of the problem...
|
||||
|
||||
|
||||
## Minimal Reproduction
|
||||
|
||||
### What's the affected URL?**
|
||||
<!-- edit: --> https://angular.io/...
|
||||
|
||||
### Reproduction Steps**
|
||||
<!-- If applicable please list the steps to take to reproduce the issue -->
|
||||
|
||||
### Expected vs Actual Behavior**
|
||||
<!-- If applicable please describe the difference between the expected and actual behavior after following the repro steps. -->
|
||||
|
||||
## Screenshot
|
||||
<!-- Often a screenshot can help to capture the issue better than a long description. -->
|
||||
<!-- upload a screenshot:-->
|
||||
|
||||
## Exception or Error
|
||||
<pre><code>
|
||||
<!-- If the issue is accompanied by an exception or an error, please share it below: -->
|
||||
|
||||
</code></pre>
|
||||
|
||||
## Your Environment
|
||||
|
||||
### Browser info
|
||||
<!-- Is this a browser specific issue? If so, please specify the device, browser, and version. -->
|
||||
|
||||
### Anything else relevant?
|
||||
<!-- Please provide additional info if necessary. -->
|
||||
@@ -0,0 +1,42 @@
|
||||
name: 'Docs or angular.io Bug Report'
|
||||
description: Report an issue in Angular's documentation or angular.io application
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: affected-url
|
||||
attributes:
|
||||
label: What is the affected URL?
|
||||
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: Please provide the steps to reproduce the issue
|
||||
|
||||
- type: textarea
|
||||
id: expected-vs-actual-behavior
|
||||
attributes:
|
||||
label: Please provide the expected behavior vs the actual behavior you encountered
|
||||
|
||||
- type: textarea
|
||||
id: screenshot
|
||||
attributes:
|
||||
label: Please provide a screenshot if possible
|
||||
|
||||
- type: textarea
|
||||
id: exception-or-error
|
||||
attributes:
|
||||
label: Please provide the exception or error you saw
|
||||
render: true
|
||||
|
||||
- type: textarea
|
||||
id: browser-info
|
||||
attributes:
|
||||
label: Is this a browser specific issue? If so, please specify the device, browser, and version.
|
||||
render: true
|
||||
@@ -10,6 +10,6 @@ jobs:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/feature-request@8467fd69f068914f8f25e10b1a80c183ee886de4
|
||||
- uses: angular/dev-infra/github-actions/feature-request@f83903fe1ac848407ef81465f66588e5accb6537
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
_
|
||||
@@ -15,5 +15,6 @@ export const caretaker: CaretakerConfig = {
|
||||
name: 'Initial Triage Queue',
|
||||
query: `is:open no:milestone`,
|
||||
}
|
||||
]
|
||||
],
|
||||
caretakerGroup: 'angular-caretaker',
|
||||
};
|
||||
|
||||
+4
-1
@@ -25,6 +25,9 @@ export const merge: DevInfraMergeConfig['merge'] = async api => {
|
||||
'master': '5aeb9a4124922d8ac08eb73b8f322905a32b0b3a',
|
||||
'10.0.x': '27b95ba64a5d99757f4042073fd1860e20e3ed24',
|
||||
},
|
||||
targetLabelExemptScopes: ['dev-infra', 'docs-infra']
|
||||
// `dev-infra` and `docs-infra` are not affecting the public NPM packages. Similarly,
|
||||
// the `bazel` package is not considered part of the public API so that features
|
||||
// can land in patch branches.
|
||||
targetLabelExemptScopes: ['dev-infra', 'docs-infra', 'bazel']
|
||||
};
|
||||
};
|
||||
|
||||
+3
-3
@@ -36,18 +36,18 @@ filegroup(
|
||||
srcs = [
|
||||
# We also declare the unminified AngularJS files since these can be used for
|
||||
# local debugging (e.g. see: packages/upgrade/test/common/test_helpers.ts)
|
||||
"@npm//:node_modules/angular/angular.js",
|
||||
"@npm//:node_modules/angular/angular.min.js",
|
||||
"@npm//:node_modules/angular-1.5/angular.js",
|
||||
"@npm//:node_modules/angular-1.5/angular.min.js",
|
||||
"@npm//:node_modules/angular-1.6/angular.js",
|
||||
"@npm//:node_modules/angular-1.6/angular.min.js",
|
||||
"@npm//:node_modules/angular-1.7/angular.js",
|
||||
"@npm//:node_modules/angular-1.7/angular.min.js",
|
||||
"@npm//:node_modules/angular-mocks/angular-mocks.js",
|
||||
"@npm//:node_modules/angular-mocks-1.5/angular-mocks.js",
|
||||
"@npm//:node_modules/angular-mocks-1.6/angular-mocks.js",
|
||||
"@npm//:node_modules/angular-mocks-1.7/angular-mocks.js",
|
||||
"@npm//:node_modules/angular-mocks-1.8/angular-mocks.js",
|
||||
"@npm//:node_modules/angular-1.8/angular.js",
|
||||
"@npm//:node_modules/angular-1.8/angular.min.js",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,77 @@
|
||||
<a name="12.1.3"></a>
|
||||
# 12.1.3 (2021-07-21)
|
||||
### animations
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [3cddc3d6bc](https://github.com/angular/angular/commit/3cddc3d6bc1045b76980ba3e866350d58deb0718) | fix(animations): normalize final styles in buildStyles ([#42763](https://github.com/angular/angular/pull/42763)) |
|
||||
### compiler-cli
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [d207ea06d1](https://github.com/angular/angular/commit/d207ea06d1bb554e822bab2eadcf12ef50117bd4) | fix(compiler-cli): use correct module import for types behind a `forwardRef` ([#42887](https://github.com/angular/angular/pull/42887)) |
|
||||
| [e6d520f3d9](https://github.com/angular/angular/commit/e6d520f3d9104c63129b94fbf5fe54b392715fc5) | perf(compiler-cli): optimize cycle detection using a persistent cache ([#41271](https://github.com/angular/angular/pull/41271)) |
|
||||
### core
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [a6db152c78](https://github.com/angular/angular/commit/a6db152c78bc82ef39e529ab5ea55f810b17fa2e) | fix(core): use correct injector when resolving DI tokens from within a directive provider factory ([#42886](https://github.com/angular/angular/pull/42886)) |
|
||||
## Special Thanks:
|
||||
Alan Agius, David Shevitz, George Kalpakas, Joey Perrott, JoostK, Krzysztof Kotowicz, Minko Gechev, Paul Gschwendtner and dario-piotrowicz
|
||||
|
||||
|
||||
<a name="12.1.2"></a>
|
||||
# 12.1.2 (2021-07-14)
|
||||
### bazel
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [4a8ab4f149](https://github.com/angular/angular/commit/4a8ab4f1499a560bb4373b088785e43f26384e1c) | fix(bazel): enable dts bundling for Ivy packages ([#42728](https://github.com/angular/angular/pull/42728)) |
|
||||
### common
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [d654c7933a](https://github.com/angular/angular/commit/d654c7933a732c23dc1e68b17766457d2d11a4d8) | fix(common): re-sort output of `KeyValuePipe` when `compareFn` changes ([#42821](https://github.com/angular/angular/pull/42821)) |
|
||||
### compiler
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [2566cbb48c](https://github.com/angular/angular/commit/2566cbb48cddd23c4b29d01ad557bc71ac716700) | fix(compiler): add mappings for all HTML entities ([#42818](https://github.com/angular/angular/pull/42818)) |
|
||||
| [65330f03a9](https://github.com/angular/angular/commit/65330f03a92f2743719d0e0d4cedcaadbb8c5b21) | fix(compiler): incorrect context object being referenced from listener instructions inside embedded views ([#42755](https://github.com/angular/angular/pull/42755)) |
|
||||
### compiler-cli
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [17d3de25da](https://github.com/angular/angular/commit/17d3de25daf339612c267001f0c2237533d7c789) | fix(compiler-cli): properly emit literal types when recreating type parameters in a different file ([#42761](https://github.com/angular/angular/pull/42761)) |
|
||||
| [0a17e98ae2](https://github.com/angular/angular/commit/0a17e98ae2813338f03e3e01bb7cfa6024f4632b) | fix(compiler-cli): inline type checking instructions no longer prevent incremental reuse ([#42759](https://github.com/angular/angular/pull/42759)) |
|
||||
| [45116097c1](https://github.com/angular/angular/commit/45116097c1dafd902014e0f680e0d50023467883) | fix(compiler-cli): support reflecting namespace declarations ([#42728](https://github.com/angular/angular/pull/42728)) |
|
||||
| [df5cc1fbbf](https://github.com/angular/angular/commit/df5cc1fbbf1b1f02f1f45603552a2530bb59688d) | fix(compiler-cli): return directives for an element on a microsyntax template ([#42640](https://github.com/angular/angular/pull/42640)) |
|
||||
### core
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [63013546e1](https://github.com/angular/angular/commit/63013546e130d823db26ad96e1d4cb346e633a03) | fix(core): associate the NgModule scope for an overridden component ([#42817](https://github.com/angular/angular/pull/42817)) |
|
||||
| [9ebd41e39c](https://github.com/angular/angular/commit/9ebd41e39c597ef6df2de2fb63d6458337e2757d) | fix(core): allow proper type inference when `ngFor` is used with a `trackBy` function ([#42692](https://github.com/angular/angular/pull/42692)) |
|
||||
| [41c6877c01](https://github.com/angular/angular/commit/41c6877c013ba84ba5e46309b2310c097ad9e354) | fix(core): error in TestBed if module is reset mid-compilation in ViewEngine ([#42669](https://github.com/angular/angular/pull/42669)) |
|
||||
### language-service
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [97c18f4527](https://github.com/angular/angular/commit/97c18f4527187206db8b0f5730b80d2220027f9f) | fix(language-service): Do not override TS LS methods not supported by VE NgLS ([#42727](https://github.com/angular/angular/pull/42727)) |
|
||||
### service-worker
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| [d87917542a](https://github.com/angular/angular/commit/d87917542a00ed6f58c7b95e3eba6aeddc5eee01) | fix(service-worker): correctly handle unrecoverable state when a client no longer exists ([#42736](https://github.com/angular/angular/pull/42736)) |
|
||||
| [f2523a8fef](https://github.com/angular/angular/commit/f2523a8fef549fab6ec480ecfc966c9d938a5423) | fix(service-worker): avoid storing redundant metadata for hashed assets ([#42606](https://github.com/angular/angular/pull/42606)) |
|
||||
## Special Thanks:
|
||||
Alan Agius, Andrew Kushnir, Andrew Scott, Arthur Ming, Bastian, Borislav Ivanov, David Gilson, David Shevitz, Gabriele Franchitto, George Kalpakas, Joey Perrott, JoostK, Kristiyan Kostadinov, Mark Goho, Meir Blumenfeld, Paul Gschwendtner, Pete Bacon Darwin, Ryan Andersen, Theoklitos Bampouris, behrooz bozorg chami, dario-piotrowicz, ivanwonder and mgechev
|
||||
|
||||
|
||||
<a name="12.1.1"></a>
|
||||
# 12.1.1 (2021-06-30)
|
||||
### compiler-cli
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| f6b828e292 | fix(compiler-cli): add support for partially evaluating types (#41661) |
|
||||
### platform-browser
|
||||
| Commit | Description |
|
||||
| -- | -- |
|
||||
| d19ddd1a87 | fix(platform-browser): in `Meta.addTag()` do not add duplicate meta tags (#42703) |
|
||||
## Special Thanks:
|
||||
Alan Agius, Dario Piotrowicz, George Kalpakas, George Looshch, Lars Gyrup Brink Nielsen, Paul Gschwendtner, Pete Bacon Darwin, Zach Arend, codebriefcase, dario-piotrowicz, marvinbeckert, mgechev and pavlenko
|
||||
|
||||
|
||||
<a name="12.1.0"></a>
|
||||
# 12.1.0 (2021-06-24)
|
||||
### compiler
|
||||
|
||||
@@ -8,8 +8,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
# Fetch rules_nodejs so we can install our npm dependencies
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
sha256 = "0fa2d443571c9e02fcb7363a74ae591bdcce2dd76af8677a95965edf329d778a",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.6.0/rules_nodejs-3.6.0.tar.gz"],
|
||||
sha256 = "5c40083120eadec50a3497084f99bc75a85400ea727e82e0b2f422720573130f",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/4.0.0-beta.0/rules_nodejs-4.0.0-beta.0.tar.gz"],
|
||||
)
|
||||
|
||||
# Check the rules_nodejs version and download npm dependencies
|
||||
@@ -34,11 +34,6 @@ yarn_install(
|
||||
yarn_lock = "//:yarn.lock",
|
||||
)
|
||||
|
||||
# Load angular dependencies
|
||||
load("//packages/bazel:package.bzl", "rules_angular_dev_dependencies")
|
||||
|
||||
rules_angular_dev_dependencies()
|
||||
|
||||
# Load protractor dependencies
|
||||
load("@npm//@bazel/protractor:package.bzl", "npm_bazel_protractor_dependencies")
|
||||
|
||||
@@ -52,13 +47,3 @@ web_test_repositories()
|
||||
load("//dev-infra/bazel/browsers:browser_repositories.bzl", "browser_repositories")
|
||||
|
||||
browser_repositories()
|
||||
|
||||
# Setup the rules_sass toolchain
|
||||
load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories")
|
||||
|
||||
sass_repositories()
|
||||
|
||||
# Setup the skydoc toolchain
|
||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
||||
|
||||
skydoc_repositories()
|
||||
|
||||
@@ -50,5 +50,5 @@ export class PopupComponent {
|
||||
private _message = '';
|
||||
|
||||
@Output()
|
||||
closed = new EventEmitter();
|
||||
closed = new EventEmitter<void>();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// #docplaster
|
||||
// #docregion import-http
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
// #docregion props
|
||||
import { Product } from './products';
|
||||
// #enddocregion import-http
|
||||
// #enddocregion props, import-http
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
@@ -79,8 +79,7 @@ export class ConfigService {
|
||||
// The backend returned an unsuccessful response code.
|
||||
// The response body may contain clues as to what went wrong.
|
||||
console.error(
|
||||
`Backend returned code ${error.status}, ` +
|
||||
`body was: ${error.error}`);
|
||||
`Backend returned code ${error.status}, body was: `, error.error);
|
||||
}
|
||||
// Return an observable with a user-facing error message.
|
||||
return throwError(
|
||||
|
||||
@@ -14,7 +14,7 @@ import { NoopInterceptor } from './noop-interceptor';
|
||||
// #enddocregion interceptor-providers
|
||||
import { TrimNameInterceptor } from './trim-name-interceptor';
|
||||
import { UploadInterceptor } from './upload-interceptor';
|
||||
|
||||
import { RetryInterceptor } from './retry-interceptor';
|
||||
// #docregion interceptor-providers
|
||||
|
||||
/** Http interceptor providers in outside-in order */
|
||||
|
||||
@@ -39,7 +39,7 @@ export class RetryInterceptor implements HttpInterceptor {
|
||||
tap(null,
|
||||
() => {
|
||||
// An error has occurred, so increment this request's ERROR_COUNT.
|
||||
req.set(ERROR_COUNT, req.get(ERROR_COUNT) + 1);
|
||||
req.context.set(ERROR_COUNT, req.context.get(ERROR_COUNT) + 1);
|
||||
}),
|
||||
// #docregion reading-context
|
||||
// Retry the request a configurable number of times.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="center-layout-wide">
|
||||
<div class="nf-container l-flex-wrap flex-center">
|
||||
<img src="assets/images/support/angular-404.svg" width="300" height="300"/>
|
||||
<div class="nf-response l-flex-wrap">
|
||||
<div class="nf-response l-flex-wrap center">
|
||||
<h1 class="no-anchor no-toc">Page Not Found</h1>
|
||||
<p>We're sorry. The page you are looking for cannot be found.</p>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +173,7 @@ Define metadata objects with the following limited syntax:
|
||||
<td><code>`pie is ${multiplier} times better than cake`</code></td>
|
||||
<tr>
|
||||
<td>Literal string</td>
|
||||
<td><code>pi</code></td>
|
||||
<td><code>'pi'</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Literal number</td>
|
||||
@@ -558,7 +558,6 @@ In [Angular Ivy](guide/ivy), the template type checker has been completely rewri
|
||||
As a result, templates that previously compiled under View Engine can fail type checking under Ivy. This can happen because Ivy's stricter checking catches genuine errors, or because application code is not typed correctly, or because the application uses libraries in which typings are inaccurate or not specific enough.
|
||||
|
||||
This stricter type checking is not enabled by default in version 9, but can be enabled by setting the `strictTemplates` configuration option.
|
||||
We do expect to make strict type checking the default in the future.
|
||||
|
||||
For more information about type-checking options, and about improvements to template type checking in version 9 and above, see [Template type checking](guide/template-typecheck).
|
||||
|
||||
|
||||
@@ -111,20 +111,6 @@ Project-specific [TypeScript](https://www.typescriptlang.org/) configuration fil
|
||||
| `tsconfig.spec.json` | [TypeScript](https://www.typescriptlang.org/) configuration for the application tests. See [TypeScript Configuration](guide/typescript-configuration). |
|
||||
| `tslint.json` | Application-specific [TSLint](https://palantir.github.io/tslint/) configuration. |
|
||||
|
||||
### End-to-end test files
|
||||
|
||||
An `e2e/` folder at the top level contains source files for a set of end-to-end tests that correspond to the root-level application, along with test-specific configuration files.
|
||||
|
||||
For a multi-project workspace, application-specific end-to-end tests are in the project root, under `projects/project-name/e2e/`.
|
||||
|
||||
<code-example language="none">
|
||||
e2e/
|
||||
src/ (end-to-end tests for my-app)
|
||||
app.e2e-spec.ts
|
||||
app.po.ts
|
||||
protractor.conf.js (test-tool config)
|
||||
tsconfig.json (TypeScript config inherits from workspace)
|
||||
</code-example>
|
||||
|
||||
{@a multiple-projects}
|
||||
|
||||
@@ -160,9 +146,6 @@ my-workspace/
|
||||
projects/ (generated applications and libraries)
|
||||
my-first-app/ --(an explicitly generated application)
|
||||
... --(application-specific config)
|
||||
e2e/ ----(corresponding e2e tests)
|
||||
src/ ----(e2e tests source)
|
||||
... ----(e2e-specific config)
|
||||
src/ --(source and support files for application)
|
||||
my-lib/ --(a generated library)
|
||||
... --(library-specific config)
|
||||
@@ -173,7 +156,7 @@ my-workspace/
|
||||
|
||||
When you generate a library using the CLI (with a command such as `ng generate library my-lib`), the generated files go into the `projects/` folder of the workspace. For more information about creating your own libraries, see [Creating Libraries](guide/creating-libraries).
|
||||
|
||||
Libraries (unlike applications and their associated e2e projects) have their own `package.json` configuration file.
|
||||
Libraries unlike applications have their own `package.json` configuration file.
|
||||
|
||||
Under the `projects/` folder, the `my-lib` folder contains your library code.
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Angular Roadmap
|
||||
|
||||
<p class="roadmap-last-updated">Last updated: 2021-05-19</p>
|
||||
|
||||
Angular receives a large number of feature requests, both from inside Google and from the broader open-source community. At the same time, our list of projects contains plenty of maintenance tasks, code refactorings, and potential performance improvements. We bring together representatives from developer relations, product management, and engineering to prioritize this list. As new projects come into the queue, we regularly position them based on relative priority to other projects. As work gets done, projects will move up in the queue.
|
||||
|
||||
The projects below are not associated with a particular Angular version. We'll release them on completion, and they will be part of a specific version based on our release schedule, following semantic versioning. For example, features are released in the next minor after they are complete, or the next major if they include breaking changes.
|
||||
|
||||
## In Progress
|
||||
## In progress
|
||||
|
||||
### Improve test times and debugging with automatic test environment tear down
|
||||
|
||||
@@ -80,10 +82,16 @@ A long-standing feature request is to add the ability to add directives to host
|
||||
|
||||
A common problem with web applications is their slow initial load time. A way to improve it is to apply more granular code-splitting on a component level. To encourage this practice, we’ll be working on more ergonomic code-splitting APIs.
|
||||
|
||||
<details class="roadmap-done-details" open="true">
|
||||
<summary class="roadmap-done-summary">
|
||||
<h2>Done</h2>
|
||||
<details class="completed-details" open="true">
|
||||
<summary>
|
||||
<h2>Completed</h2>
|
||||
<span class="actions">
|
||||
<span class="action-expand">Show all</span>
|
||||
<span class="action-collapse">Hide all</span>
|
||||
<i class="material-icons expand">expand_more</i>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="details-content">
|
||||
|
||||
### Accelerated debugging and performance profiling with Angular DevTools
|
||||
|
||||
@@ -157,10 +165,11 @@ _Completed Q4 2020_
|
||||
|
||||
With the deprecation of TSLint we will be moving to ESLint. As part of the process, we will work on ensuring backward compatibility with our current recommended TSLint configuration, implement a migration strategy for existing Angular applications and introduce new tooling to the Angular CLI toolchain.
|
||||
|
||||
### Operation Bye Bye Backlog (aka Operation Byelog)
|
||||
### Operation Bye Bye Backlog (aka Operation Byelog)
|
||||
|
||||
_Completed Q4 2020_
|
||||
|
||||
We are actively investing up to 50% of our engineering capacity on triaging issues and PRs until we have a clear understanding of broader community needs. After that, we'll commit up to 20% of our engineering capacity to keep up with new submissions promptly.
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -2528,7 +2528,7 @@ Add the `NavigationExtras` object to the `router.navigate()` method that navigat
|
||||
<code-example path="router/src/app/auth/auth.guard.4.ts" header="src/app/auth/auth.guard.ts (v3)"></code-example>
|
||||
|
||||
You can also preserve query parameters and fragments across navigations without having to provide them again when navigating.
|
||||
In the `LoginComponent`, you'll add an *object* as the second argument in the `router.navigateUrl()` function and provide the `queryParamsHandling` and `preserveFragment` to pass along the current query parameters and fragment to the next route.
|
||||
In the `LoginComponent`, you'll add an *object* as the second argument in the `router.navigate()` function and provide the `queryParamsHandling` and `preserveFragment` to pass along the current query parameters and fragment to the next route.
|
||||
|
||||
<code-example path="router/src/app/auth/login/login.component.ts" header="src/app/auth/login/login.component.ts (preserve)" region="preserve"></code-example>
|
||||
|
||||
|
||||
@@ -34,9 +34,14 @@ Before creating a route, you should be familiar with the following:
|
||||
<p>Add more routing features to the Tour of Heroes tutorial.</p>
|
||||
<p class="card-footer">Routing Tour of Heroes</p>
|
||||
</a>
|
||||
<a href="guide/routing-with-urlmatcher" class="docs-card" title="Creating custom route matches tutorial">
|
||||
<section>Creating custom route matches tutorial</section>
|
||||
<p>A tutorial that covers how to use custom matching strategy patterns with Angular routing.</p>
|
||||
<p class="card-footer">Custom route matches tutorial</p>
|
||||
</a>
|
||||
<a href="guide/router-reference" class="docs-card" title="Router reference">
|
||||
<section>Router reference</section>
|
||||
<p>Describes some core router API concepts.</p>
|
||||
<p class="card-footer">Router reference</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,6 +59,8 @@ To systematically block XSS bugs, Angular treats all values as untrusted by defa
|
||||
|
||||
Unlike values to be used for rendering, Angular templates are considered trusted by default, and should be treated as executable code. Never generate templates by concatenating user input and template syntax. Doing this would enable attackers to [inject arbitrary code](https://en.wikipedia.org/wiki/Code_injection) into your application. To prevent these vulnerabilities, always use the default [AOT template compiler](/guide/security#offline-template-compiler) in production deployments.
|
||||
|
||||
An additional layer of protection can be provided through the use of Content security policy and Trusted Types. These web platform features operate at the DOM level which is the most effective place to prevent XSS issues because they can't be bypassed using other, lower-level APIs. For this reason, we strongly encourage developers to take advantage of these features by configuring the [content security policy](#content-security-policy) for their application and enabling [trusted types enforcement](#trusted-types).
|
||||
|
||||
### Sanitization and security contexts
|
||||
|
||||
_Sanitization_ is the inspection of an untrusted value, turning it into a value that's safe to insert into
|
||||
@@ -100,7 +102,7 @@ Angular recognizes the value as unsafe and automatically sanitizes it, which rem
|
||||
|
||||
### Direct use of the DOM APIs and explicit sanitization calls
|
||||
|
||||
Built-in browser DOM APIs don't automatically protect you from security vulnerabilities.
|
||||
Unless you enforce Trusted Types, the built-in browser DOM APIs don't automatically protect you from security vulnerabilities.
|
||||
For example, `document`, the node available through `ElementRef`, and many third-party APIs
|
||||
contain unsafe methods. In the same way, if you interact with other libraries that manipulate
|
||||
the DOM, you likely won't have the same automatic sanitization as with Angular interpolations.
|
||||
@@ -169,6 +171,59 @@ technique to prevent XSS. To enable CSP, configure your web server to return an
|
||||
[Web Fundamentals guide](https://developers.google.com/web/fundamentals/security/csp) on the
|
||||
Google Developers website.
|
||||
|
||||
{@a trusted-types}
|
||||
### Enforcing Trusted Types
|
||||
|
||||
We recommend the use of [Trusted Types](https://w3c.github.io/webappsec-trusted-types/dist/spec/) as a way to help secure your applications from cross-site scripting attacks. Trusted Types is a [web platform](https://en.wikipedia.org/wiki/Web_platform)
|
||||
feature that can help you prevent cross-site scripting attacks by enforcing
|
||||
safer coding practices. Trusted Types can also help simplify the auditing of application code.
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
Trusted Types might not yet be available in all browsers your application targets. In the case your Trusted-Types-enabled application runs in a browser that doesn't support Trusted Types, the functionality of the application will be preserved, and your application will be guarded against XSS by way of Angular's DomSanitizer. See [caniuse.com/trusted-types](https://caniuse.com/trusted-types) for the current browser support.
|
||||
|
||||
</div>
|
||||
|
||||
To enforce Trusted Types for your application, you must configure your application's web server to emit HTTP headers with one of the following Angular policies:
|
||||
|
||||
* `angular` - This policy is used in security-reviewed code that is internal to Angular, and is required for Angular to function when Trusted Types are enforced. Any inline template values or content sanitized by Angular is treated as safe by this policy.
|
||||
* `angular#unsafe-bypass` - This policy is used for applications that use any of the methods in Angular's [DomSanitizer](api/platform-browser/DomSanitizer) that bypass security, such as `bypassSecurityTrustHtml`. Any application that uses these methods must enable this policy.
|
||||
* `angular#unsafe-jit` - This policy is used by the [JIT compiler](api/core/Compiler). You must enable this policy if your application interacts directly with the JIT compiler or is running in JIT mode using the [platform browser dynamic](api/platform-browser-dynamic/platformBrowserDynamic).
|
||||
|
||||
You should configure the HTTP headers for Trusted Types in the following locations:
|
||||
|
||||
* Production serving infrastructure
|
||||
* Angular CLI (`ng serve`), using the `headers` property in the `angular.json` file, for local development and end-to-end testing
|
||||
* Karma (`ng test`), using the `customHeaders` property in the `karma.config.js` file, for unit testing
|
||||
|
||||
The following is an example of a header specifically configured for Trusted Types and Angular:
|
||||
|
||||
<code-example language="html">
|
||||
Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';
|
||||
</code-example>
|
||||
|
||||
The following is an example of a header specifically configured for Trusted Types and Angular applications that use any of the methods in Angular's [DomSanitizer](api/platform-browser/DomSanitizer) that bypasses security.
|
||||
|
||||
<code-example language="html">
|
||||
Content-Security-Policy: trusted-types angular angular#unsafe-bypass; require-trusted-types-for 'script';
|
||||
</code-example>
|
||||
|
||||
The following is an example of a header specifically configured for Trusted Types and Angular applications using JIT:
|
||||
|
||||
<code-example language="html">
|
||||
Content-Security-Policy: trusted-types angular angular#unsafe-jit; require-trusted-types-for 'script';
|
||||
</code-example>
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<header>Community contributions</header>
|
||||
|
||||
To learn more about troubleshooting Trusted Type configurations, the following resource might be helpful:
|
||||
|
||||
[Prevent DOM-based cross-site scripting vulnerabilities with Trusted Types](https://web.dev/trusted-types/#how-to-use-trusted-types)
|
||||
|
||||
</div>
|
||||
|
||||
{@a offline-template-compiler}
|
||||
|
||||
### Use the AOT template compiler
|
||||
|
||||
@@ -344,6 +344,6 @@ After you've set up your application for testing, you may find the following tes
|
||||
* [Basics of testing components](guide/testing-components-basics)—discover the basics of testing Angular components.
|
||||
* [Component testing scenarios](guide/testing-components-scenarios)—read about the various kinds of component testing scenarios and use cases.
|
||||
* [Testing attribute directives](guide/testing-attribute-directives)—learn about how to test your attribute directives.
|
||||
* [Testing pipes](guide/testing-pipes)—find out how to test attribute directives.
|
||||
* [Testing pipes](guide/testing-pipes)—find out how to test pipes.
|
||||
* [Debugging tests](guide/test-debugging)—uncover common testing bugs.
|
||||
* [Testing utility APIs](guide/testing-utility-apis)—get familiar with Angular testing features.
|
||||
|
||||
@@ -140,7 +140,7 @@ See [Build target](#build-target) below for more information.
|
||||
|
||||
* The `architect/test` section overrides build-option defaults for test builds and supplies additional test-running defaults for the `ng test` command.
|
||||
|
||||
* The `architect/lint` section configures defaults for options of the `ng lint` command, which performs code analysis on project source files. The default linting tool for Angular is [TSLint](https://palantir.github.io/tslint/).
|
||||
* The `architect/lint` section configures defaults for options of the `ng lint` command, which performs code analysis on project source files.
|
||||
|
||||
* The `architect/extract-i18n` section configures defaults for options of the `ng extract-i18n` command, which extracts marked message strings from source code and outputs translation files.
|
||||
|
||||
|
||||
@@ -55,13 +55,19 @@
|
||||
"Podcasts": {
|
||||
"order": 3,
|
||||
"resources": {
|
||||
"sdfjkdkfj": {
|
||||
"adventures-in-angular": {
|
||||
"desc": "Adventures in Angular is a weekly podcast dedicated to the Angular platform and related technologies, tools, languages, and practices.",
|
||||
"logo": "",
|
||||
"title": "Adventures in Angular",
|
||||
"url": "https://devchat.tv/adv-in-angular/"
|
||||
},
|
||||
"sdlkfjsldfkj": {
|
||||
"angular-show": {
|
||||
"desc": "ng-conf's official all-Angular podcast.",
|
||||
"logo": "",
|
||||
"title": "The Angular Show",
|
||||
"url": "https://www.spreaker.com/show/angular-show"
|
||||
},
|
||||
"angular-air": {
|
||||
"desc": "Weekly video podcast hosted by Jeff Whelpley with all the latest and greatest happenings in the wild world of Angular.",
|
||||
"logo": "",
|
||||
"title": "AngularAir",
|
||||
@@ -73,10 +79,10 @@
|
||||
"title": "Angular Rocks",
|
||||
"url": "https://angularrocks.com"
|
||||
},
|
||||
"sdlkfjsldfkz": {
|
||||
"happy-angular": {
|
||||
"desc": "A weekly German podcast for Angular on the go",
|
||||
"logo": "",
|
||||
"title": "Happy Angular Podcast",
|
||||
"title": "Happy Angular",
|
||||
"url": "https://happy-angular.de/"
|
||||
},
|
||||
"ngruair": {
|
||||
@@ -84,12 +90,6 @@
|
||||
"logo": "",
|
||||
"title": "NgRuAir",
|
||||
"url": "https://github.com/ngRuAir/ngruair"
|
||||
},
|
||||
"the-deep-dive": {
|
||||
"desc": "The advanced web development podcast about Angular, RxJS, TypeScript and other technologies. English, audio only.",
|
||||
"logo": "https://i.imgur.com/mmE5Feq.jpg",
|
||||
"title": "The Deep Dive",
|
||||
"url": "https://thedeepdive.simplecast.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,11 +202,6 @@
|
||||
"title": "Template reference variables",
|
||||
"tooltip": "Introductory guide to referring to DOM elements within a template."
|
||||
},
|
||||
{
|
||||
"url": "guide/inputs-outputs",
|
||||
"title": "Inputs and Outputs",
|
||||
"tooltip": "Introductory guide to sharing data between parent and child directives or components."
|
||||
},
|
||||
{
|
||||
"url": "guide/svg-in-templates",
|
||||
"title": "SVG as templates",
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
Angular's `<ng-template>` element defines a template that doesn't render anything by default.
|
||||
Angular's `<ng-template>` element defines a template that is not rendered by default.
|
||||
|
||||
With `<ng-template>`, you can render the content manually for full control over how the content displays.
|
||||
With `<ng-template>`, you can define template content that is only being rendered by Angular when you, whether directly or indirectly, specifically instruct it to do so, allowing you to have full control over how and when the content is displayed.
|
||||
|
||||
@usageNotes
|
||||
|
||||
### Structural Directives
|
||||
|
||||
One of the main uses for `<ng-template>` is to hold template content that will be used by [Structural directives](/guide/structural-directives). Those directives can add and remove copies of the template content based on their own logic.
|
||||
|
||||
When using the [structural directive shorthand](/guide/structural-directives#structural-directive-shorthand), Angular creates an `<ng-template>` element behind the scenes.
|
||||
|
||||
### TemplateRef
|
||||
|
||||
`<ng-template>` elements are represented as instances of the `TemplateRef` class.
|
||||
|
||||
To add copies of the template to the DOM, pass this object to the `ViewContainerRef` method `createEmbeddedView()`.
|
||||
|
||||
### Template Variables
|
||||
|
||||
`<ng-template>` elements can be referenced in templates using [standard template variables](/guide/template-reference-variables#how-angular-assigns-values-to-template-variables).
|
||||
|
||||
_This is how `<ng-template>` elements are used as `ngIf` else clauses._
|
||||
|
||||
Such template variables can be used in conjunction with `ngTemplateOutlet` directives to render the content defined inside `<ng-template>` tags.
|
||||
|
||||
|
||||
### Querying
|
||||
|
||||
A [Query](/api/core/Query) (such as `ViewChild`) can find the `TemplateRef` associated to an `<ng-template>` element so that it can be used programmatically; for instance, to pass it to the `ViewContainerRef` method `createEmbeddedView()`.
|
||||
|
||||
|
||||
### Context
|
||||
Inside the `<ng-template>` tags you can reference variables present in the surrounding outer template.
|
||||
Additionally, a context object can be associated with `<ng-template>` elements.
|
||||
Such an object contains variables that can be accessed from within the template contents via template (`let` and `as`) declarations.
|
||||
@@ -219,6 +219,13 @@ The Phone XL price is over $700, so the **Notify Me** button appears on that pro
|
||||
To make the **Notify Me** button work, the child component needs to notify and pass the data to the parent component.
|
||||
The `ProductAlertsComponent` needs to emit an event when the user clicks **Notify Me** and the `ProductListComponent` needs to respond to the event.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
In new components, the Angular Generator includes an empty `constructor()`, the `OnInit` interface, and the `ngOnInit()` method.
|
||||
Since these steps don't use them, the following code examples omit them for brevity.
|
||||
|
||||
</div>
|
||||
|
||||
1. In `product-alerts.component.ts`, import `Output` and `EventEmitter` from `@angular/core`.
|
||||
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.ts" path="getting-started/src/app/product-alerts/product-alerts.component.ts" region="imports"></code-example>
|
||||
@@ -228,13 +235,6 @@ The `ProductAlertsComponent` needs to emit an event when the user clicks **Notif
|
||||
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.ts" header="src/app/product-alerts/product-alerts.component.ts" region="input-output"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
In new components, the Angular Generator includes an empty `constructor()`, the `OnInit` interface, and the `ngOnInit()` method.
|
||||
Since these steps don't use them, the following code example omits them for brevity.
|
||||
|
||||
</div>
|
||||
|
||||
1. In `product-alerts.component.html`, update the **Notify Me** button with an event binding to call the `notify.emit()` method.
|
||||
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.html" path="getting-started/src/app/product-alerts/product-alerts.component.html"></code-example>
|
||||
|
||||
@@ -30,6 +30,7 @@ This section walks you through adding a **Buy** button and setting up a cart ser
|
||||
|
||||
<code-example header="src/app/cart.service.ts" path="getting-started/src/app/cart.service.1.ts"></code-example>
|
||||
|
||||
1. Import the `Product` interface from `./products.js`.
|
||||
1. In the `CartService` class, define an `items` property to store the array of the current products in the cart.
|
||||
|
||||
<code-example path="getting-started/src/app/cart.service.ts" header="src/app/cart.service.ts" region="props"></code-example>
|
||||
|
||||
@@ -22,7 +22,7 @@ To set up your development environment, follow the instructions in [Local Enviro
|
||||
|
||||
## Create a new workspace and an initial application
|
||||
|
||||
You develop applications in the context of an Angular [workspace](guide/glossary#workspace). A workspace contains the files for one or more [projects](guide/glossary#project). A project is the set of files that comprise an app, a library, or end-to-end (e2e) tests. For this tutorial, you will create a new workspace.
|
||||
You develop applications in the context of an Angular [workspace](guide/glossary#workspace). A workspace contains the files for one or more [projects](guide/glossary#project). A project is the set of files that comprise an application or a library. For this tutorial, you will create a new workspace.
|
||||
|
||||
To create a new workspace and an initial application project:
|
||||
|
||||
|
||||
+15
-15
@@ -86,27 +86,27 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "12.0.5",
|
||||
"@angular/cdk": "12.0.5",
|
||||
"@angular/common": "12.0.5",
|
||||
"@angular/compiler": "12.0.5",
|
||||
"@angular/core": "12.0.5",
|
||||
"@angular/elements": "12.0.5",
|
||||
"@angular/forms": "12.0.5",
|
||||
"@angular/material": "12.0.5",
|
||||
"@angular/platform-browser": "12.0.5",
|
||||
"@angular/platform-browser-dynamic": "12.0.5",
|
||||
"@angular/router": "12.0.5",
|
||||
"@angular/service-worker": "12.0.5",
|
||||
"@angular/animations": "12.1.1",
|
||||
"@angular/cdk": "12.1.1",
|
||||
"@angular/common": "12.1.1",
|
||||
"@angular/compiler": "12.1.1",
|
||||
"@angular/core": "12.1.1",
|
||||
"@angular/elements": "12.1.1",
|
||||
"@angular/forms": "12.1.1",
|
||||
"@angular/material": "12.1.1",
|
||||
"@angular/platform-browser": "12.1.1",
|
||||
"@angular/platform-browser-dynamic": "12.1.1",
|
||||
"@angular/router": "12.1.1",
|
||||
"@angular/service-worker": "12.1.1",
|
||||
"@webcomponents/custom-elements": "1.4.3",
|
||||
"rxjs": "^6.6.7",
|
||||
"tslib": "^2.2.0",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "12.0.4",
|
||||
"@angular/cli": "12.0.4",
|
||||
"@angular/compiler-cli": "12.0.5",
|
||||
"@angular-devkit/build-angular": "12.1.1",
|
||||
"@angular/cli": "12.1.1",
|
||||
"@angular/compiler-cli": "12.1.1",
|
||||
"@swc/cli": "^0.1.36",
|
||||
"@swc/core": "^1.2.51",
|
||||
"@types/jasmine": "~3.7.0",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="content">
|
||||
<div class="nf-container l-flex-wrap flex-center">
|
||||
<img src="assets/images/support/angular-404.svg" width="300" height="300" />
|
||||
<div class="nf-response l-flex-wrap">
|
||||
<div class="nf-response l-flex-wrap center">
|
||||
<h1 class="no-toc" id="page-not-found">Resource Not Found</h1>
|
||||
<p>We're sorry. The resource you are looking for cannot be found.</p>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<mat-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
|
||||
<mat-sidenav [ngClass]="{'collapsed': !dockSideNav}" #sidenav class="sidenav" [mode]="mode" [opened]="isOpened" (openedChange)="updateHostClasses()">
|
||||
<aio-nav-menu *ngIf="!showTopMenu" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow" [isWide]="false"></aio-nav-menu>
|
||||
<aio-nav-menu *ngIf="!showTopMenu" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow" [isWide]="dockSideNav"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes?.SideNav" [isWide]="dockSideNav"></aio-nav-menu>
|
||||
|
||||
<div class="doc-version">
|
||||
@@ -61,11 +61,11 @@
|
||||
<aio-dt *ngIf="dtOn" [(doc)]="currentDocument"></aio-dt>
|
||||
</main>
|
||||
|
||||
</mat-sidenav-container>
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (wheel)="restrainScrolling($event)">
|
||||
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (wheel)="restrainScrolling($event)">
|
||||
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
|
||||
</div>
|
||||
</mat-sidenav-container>
|
||||
|
||||
<footer class="no-print">
|
||||
<aio-footer [nodes]="footerNodes" [versionInfo]="versionInfo"></aio-footer>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
|
||||
const sideNavView = 'SideNav';
|
||||
export const showTopMenuWidth = 1048;
|
||||
export const showTopMenuWidth = 1150;
|
||||
export const dockSideNavWidth = 992;
|
||||
export const showFloatingTocWidth = 800;
|
||||
|
||||
@@ -79,7 +79,7 @@ export class AppComponent implements OnInit {
|
||||
private currentUrl: string;
|
||||
|
||||
get isOpened() { return this.dockSideNav && this.isSideNavDoc; }
|
||||
get mode() { return this.dockSideNav && (this.isSideNavDoc || this.showTopMenu) ? 'side' : 'over'; }
|
||||
get mode() { return this.isOpened ? 'side' : 'over'; }
|
||||
|
||||
// Search related properties
|
||||
showSearchResults = false;
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ApiService implements OnDestroy {
|
||||
private apiBase = DOC_CONTENT_URL_PREFIX + 'api/';
|
||||
private apiListJsonDefault = 'api-list.json';
|
||||
private firstTime = true;
|
||||
private onDestroy = new Subject();
|
||||
private onDestroy = new Subject<void>();
|
||||
private sectionsSubject = new ReplaySubject<ApiSection[]>(1);
|
||||
private _sections = this.sectionsSubject.pipe(takeUntil(this.onDestroy));
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ import { SearchService } from 'app/search/search.service';
|
||||
@Component({
|
||||
selector: 'aio-file-not-found-search',
|
||||
template:
|
||||
`<p>Let's see if any of these search results help...</p>
|
||||
`<div class="alert is-helpful">
|
||||
<p>Let's see if any of these search results help...</p>
|
||||
</div>
|
||||
<aio-search-results class="embedded" [searchResults]="searchResults | async"></aio-search-results>`
|
||||
})
|
||||
export class FileNotFoundSearchComponent implements OnInit {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
isCollapsed = true;
|
||||
isEmbedded = false;
|
||||
@ViewChildren('tocItem') private items: QueryList<ElementRef>;
|
||||
private onDestroy = new Subject();
|
||||
private onDestroy = new Subject<void>();
|
||||
primaryMax = 4;
|
||||
tocList: TocItem[];
|
||||
|
||||
|
||||
@@ -70,13 +70,16 @@ describe('DocumentService', () => {
|
||||
it('should encode the request path to be case-insensitive', () => {
|
||||
const { docService, locationService } = getServices('initial/Doc');
|
||||
docService.currentDocument.subscribe();
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'initial/d_oc.json').flush({});
|
||||
locationService.go('NEW/Doc');
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'n_e_w_/d_oc.json').flush({});
|
||||
locationService.go('doc_with_underscores');
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'doc__with__underscores.json').flush({});
|
||||
locationService.go('DOC_WITH_UNDERSCORES');
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'd_o_c___w_i_t_h___u_n_d_e_r_s_c_o_r_e_s_.json').flush({});
|
||||
|
||||
expect(() => {
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'initial/d_oc.json').flush({});
|
||||
locationService.go('NEW/Doc');
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'n_e_w_/d_oc.json').flush({});
|
||||
locationService.go('doc_with_underscores');
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'doc__with__underscores.json').flush({});
|
||||
locationService.go('DOC_WITH_UNDERSCORES');
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'd_o_c___w_i_t_h___u_n_d_e_r_s_c_o_r_e_s_.json').flush({});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should emit the not-found document if the document is not found on the server', () => {
|
||||
|
||||
@@ -18,10 +18,11 @@ export const DOC_CONTENT_URL_PREFIX = CONTENT_URL_PREFIX + 'docs/';
|
||||
const FETCHING_ERROR_CONTENTS = (path: string) => `
|
||||
<div class="nf-container l-flex-wrap flex-center">
|
||||
<div class="nf-icon material-icons">error_outline</div>
|
||||
<div class="nf-response l-flex-wrap">
|
||||
<h1 class="no-toc">Request for document failed.</h1>
|
||||
<div class="nf-response l-flex-wrap center">
|
||||
<h1 class="no-toc">Request for document failed</h1>
|
||||
<p>
|
||||
We are unable to retrieve the "${path}" page at this time.
|
||||
<br/>
|
||||
Please check your connection and try again later.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -260,10 +260,13 @@ export class DocViewerComponent implements OnDestroy {
|
||||
* (See https://github.com/angular/angular/issues/28114.)
|
||||
*/
|
||||
async function printSwDebugInfo(): Promise<void> {
|
||||
console.log(`\nServiceWorker: ${navigator.serviceWorker?.controller?.state ?? 'N/A'}`);
|
||||
const sep = '\n----------';
|
||||
const swState = navigator.serviceWorker?.controller?.state ?? 'N/A';
|
||||
|
||||
console.log(`\nServiceWorker: ${swState}`);
|
||||
|
||||
if (typeof caches === 'undefined') {
|
||||
console.log('\nCaches: N/A');
|
||||
console.log(`${sep}\nCaches: N/A`);
|
||||
} else {
|
||||
const allCacheNames = await caches.keys();
|
||||
const swCacheNames = allCacheNames.filter(name => name.startsWith('ngsw:/:'));
|
||||
@@ -273,11 +276,28 @@ async function printSwDebugInfo(): Promise<void> {
|
||||
await findCachesAndPrintEntries(swCacheNames, 'assets:app-shell:meta', true);
|
||||
}
|
||||
|
||||
if (swState === 'activated') {
|
||||
console.log(sep);
|
||||
await fetchAndPrintSwInternalDebugInfo();
|
||||
}
|
||||
|
||||
console.warn(
|
||||
'\nIf you see this error, please report an issue at ' +
|
||||
`${sep}\nIf you see this error, please report an issue at ` +
|
||||
'https://github.com/angular/angular/issues/new?template=3-docs-bug.md including the above logs.');
|
||||
|
||||
// Internal helpers
|
||||
async function fetchAndPrintSwInternalDebugInfo() {
|
||||
try {
|
||||
const res = await fetch('/ngsw/state');
|
||||
if (!res.ok) {
|
||||
throw new Error(`Response ${res.status} ${res.statusText}`);
|
||||
}
|
||||
console.log(await res.text());
|
||||
} catch (err) {
|
||||
console.log(`Failed to retrieve debug info from '/ngsw/state': ${err.message || err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function findCachesAndPrintEntries(
|
||||
swCacheNames: string[], nameSuffix: string, includeValues: boolean,
|
||||
ignoredKeys: string[] = []): Promise<void> {
|
||||
|
||||
@@ -23,7 +23,7 @@ export class NotificationComponent implements OnInit {
|
||||
@Input() dismissOnContentClick: boolean;
|
||||
@Input() notificationId: string;
|
||||
@Input() expirationDate: string;
|
||||
@Output() dismissed = new EventEmitter();
|
||||
@Output() dismissed = new EventEmitter<void>();
|
||||
|
||||
@HostBinding('@hideAnimation')
|
||||
showNotification: 'show'|'hide';
|
||||
|
||||
@@ -56,10 +56,40 @@ describe('ReportingErrorHandler service', () => {
|
||||
expect(onerrorSpy).toHaveBeenCalledWith(error.message, undefined, undefined, undefined, error);
|
||||
});
|
||||
|
||||
it('should send a non-error object to window.onerror', () => {
|
||||
const error = {reason: 'this is an error message'};
|
||||
handler.handleError(error);
|
||||
expect(onerrorSpy).toHaveBeenCalledWith(JSON.stringify(error));
|
||||
});
|
||||
|
||||
it('should send a non-error object with circular references to window.onerror', () => {
|
||||
const error = {
|
||||
reason: 'this is an error message',
|
||||
get self() { return this; },
|
||||
toString() { return `{reason: ${this.reason}}`; },
|
||||
};
|
||||
handler.handleError(error);
|
||||
expect(onerrorSpy).toHaveBeenCalledWith('{reason: this is an error message}');
|
||||
});
|
||||
|
||||
it('should send an error string to window.onerror', () => {
|
||||
const error = 'this is an error message';
|
||||
handler.handleError(error);
|
||||
expect(onerrorSpy).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should send a non-object, non-string error stringified to window.onerror', () => {
|
||||
handler.handleError(404);
|
||||
handler.handleError(false);
|
||||
handler.handleError(null);
|
||||
handler.handleError(undefined);
|
||||
|
||||
expect(onerrorSpy.calls.allArgs()).toEqual([
|
||||
['404'],
|
||||
['false'],
|
||||
['null'],
|
||||
['undefined'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,8 +17,7 @@ export class ReportingErrorHandler extends ErrorHandler {
|
||||
* Send error info to Google Analytics, in addition to the default handling.
|
||||
* @param error Information about the error.
|
||||
*/
|
||||
handleError(error: string | Error) {
|
||||
|
||||
handleError(error: any) {
|
||||
try {
|
||||
super.handleError(error);
|
||||
} catch (e) {
|
||||
@@ -27,12 +26,19 @@ export class ReportingErrorHandler extends ErrorHandler {
|
||||
this.reportError(error);
|
||||
}
|
||||
|
||||
private reportError(error: string | Error) {
|
||||
private reportError(error: unknown) {
|
||||
if (this.window.onerror) {
|
||||
if (typeof error === 'string') {
|
||||
this.window.onerror(error);
|
||||
} else {
|
||||
if (error instanceof Error) {
|
||||
this.window.onerror(error.message, undefined, undefined, undefined, error);
|
||||
} else {
|
||||
if (typeof error === 'object') {
|
||||
try {
|
||||
error = JSON.stringify(error);
|
||||
} catch {
|
||||
// Ignore the error and just let it be stringified.
|
||||
}
|
||||
}
|
||||
this.window.onerror(`${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export class ScrollSpiedElementGroup {
|
||||
@Injectable()
|
||||
export class ScrollSpyService {
|
||||
private spiedElementGroups: ScrollSpiedElementGroup[] = [];
|
||||
private onStopListening = new Subject();
|
||||
private onStopListening = new Subject<void>();
|
||||
private resizeEvents = fromEvent(window, 'resize').pipe(auditTime(300), takeUntil(this.onStopListening));
|
||||
private scrollEvents = fromEvent(window, 'scroll').pipe(auditTime(10), takeUntil(this.onStopListening));
|
||||
private lastContentHeight: number;
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@
|
||||
function formatError(msg, url, line, col, e) {
|
||||
var stack;
|
||||
msg = msg.replace(/^Error: /, '');
|
||||
if (e) {
|
||||
if (e && e.stack) {
|
||||
stack = e.stack
|
||||
// strip the leading "Error: " from the stack trace
|
||||
.replace(/^Error: /, '')
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
mat-sidenav-container.sidenav-container {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
transform: none;
|
||||
|
||||
&.has-floating-toc {
|
||||
max-width: 82%;
|
||||
.mat-sidenav-content {
|
||||
margin-right: 18vw;
|
||||
}
|
||||
}
|
||||
|
||||
.sidenav-content {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@use '../../mixins';
|
||||
|
||||
// VARIABLES
|
||||
$showTopMenuWidth: 1048px;
|
||||
$showTopMenuWidth: 1150px;
|
||||
$hideTopMenuWidth: $showTopMenuWidth - 1;
|
||||
$hamburgerShownMargin: 0 8px 0 0;
|
||||
$hamburgerHiddenMargin: 0 16px 0 -64px;
|
||||
@@ -132,7 +132,10 @@ mat-toolbar.app-toolbar {
|
||||
|
||||
.nav-link-inner {
|
||||
border-radius: 4px;
|
||||
font-size: 1.3rem;
|
||||
font-size: clamp(10px, 1.6rem, 20px);
|
||||
padding: 8px 16px;
|
||||
padding: 8px clamp(5px, 0.7vw, 16px);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -183,7 +186,7 @@ mat-toolbar.app-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@media (max-width: 515px) {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
@@ -200,11 +203,7 @@ mat-toolbar.app-toolbar {
|
||||
.toolbar-external-icons-container {
|
||||
a {
|
||||
@media screen and (max-width: 480px) {
|
||||
margin: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: -8px;
|
||||
}
|
||||
margin: 0 -8px 0 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
@use 'sass:color';
|
||||
@use '../constants';
|
||||
@use '../mixins';
|
||||
|
||||
code-example,
|
||||
code-tabs {
|
||||
clear: both;
|
||||
display: block;
|
||||
|
||||
code {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.mat-card {
|
||||
padding: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
code-example {
|
||||
&:not(.no-box) {
|
||||
background-color: rgba($backgroundgray, 0.2);
|
||||
border: 0.5px solid $lightgray;
|
||||
border-radius: 5px;
|
||||
color: $darkgray;
|
||||
margin: 16px auto;
|
||||
}
|
||||
|
||||
&.no-box {
|
||||
pre.prettyprint {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
// TERMINAL / SHELL TEXT STYLES
|
||||
&[language=sh], &[language=bash] {
|
||||
background-color: $darkgray;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: $accentblue;
|
||||
border-radius: 5px 5px 0 0;
|
||||
color: $offwhite;
|
||||
@include mixins.font-size(16);
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
code-tabs {
|
||||
margin: 16px 0;
|
||||
|
||||
.code-tab-group {
|
||||
.mat-tab-label {
|
||||
&:hover {
|
||||
background: rgba(black, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-tab-body {
|
||||
overflow-y: hidden;
|
||||
|
||||
.mat-tab-body-content {
|
||||
height: auto;
|
||||
transform: none;
|
||||
|
||||
.fadeIn {
|
||||
animation: opacity 2s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code-example.avoid header,
|
||||
code-example.avoidFile header {
|
||||
border: 2px solid $anti-pattern;
|
||||
background: $anti-pattern;
|
||||
}
|
||||
|
||||
code-example.avoid,
|
||||
code-example.avoidFile,
|
||||
code-tabs.avoid mat-tab-body,
|
||||
code-tabs.avoidFile mat-tab-body {
|
||||
border: 0.5px solid $anti-pattern;
|
||||
}
|
||||
|
||||
aio-code {
|
||||
pre.prettyprint {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
margin: 16px 24px;
|
||||
white-space: pre-wrap;
|
||||
align-items: center;
|
||||
|
||||
code {
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
span {
|
||||
@include mixins.line-height(24);
|
||||
}
|
||||
|
||||
ol.linenums {
|
||||
margin: 0;
|
||||
color: color.adjust($mediumgray, $lightness: 25%);
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
font-family: $code-font;
|
||||
font-size: 90%;
|
||||
@include mixins.line-height(24);
|
||||
|
||||
&::marker {
|
||||
color: color.adjust($mediumgray, $lightness: 25%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-missing {
|
||||
color: $darkred;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
right: -19px;
|
||||
padding: 0;
|
||||
overflow: visible; // This is required for the button to be displayed correctly in IE11.
|
||||
|
||||
color: $blue-grey-200;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $mediumgray;
|
||||
}
|
||||
}
|
||||
|
||||
&.lang-sh, &.lang-bash {
|
||||
.copy-button {
|
||||
color: $mediumgray;
|
||||
|
||||
&:hover {
|
||||
color: $lightgray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidenav-content {
|
||||
code {
|
||||
a {
|
||||
color: $darkblue;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(pre) {
|
||||
> code {
|
||||
background-color: rgba($lightgray, 0.3);
|
||||
border-radius: 4px;
|
||||
color: $deepgray;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
&:not(a) > code {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-guide-cheatsheet & {
|
||||
td:first-of-type,
|
||||
th {
|
||||
code {
|
||||
background-color: inherit;
|
||||
padding: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-anchor {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* PRETTY PRINTING STYLES for prettify.js. */
|
||||
|
||||
/* The following class|color styles are derived from https://github.com/google/code-prettify/blob/master/src/prettify.css*/
|
||||
|
||||
/* SPAN elements with the classes below are added by prettyprint. */
|
||||
.pln { color: #000 } /* plain text */
|
||||
|
||||
@media screen {
|
||||
.str { color: #800 } /* string content */
|
||||
.kwd { color: #00f } /* a keyword */
|
||||
.com { color: #060 } /* a comment */
|
||||
.typ { color: red } /* a type name */
|
||||
.lit { color: #08c } /* a literal value */
|
||||
/* punctuation, lisp open bracket, lisp close bracket */
|
||||
.pun, .opn, .clo { color: #660 }
|
||||
.tag { color: #008 } /* a markup tag name */
|
||||
.atn { color: #606 } /* a markup attribute name */
|
||||
.atv { color: #800 } /* a markup attribute value */
|
||||
.dec, .var { color: #606 } /* a declaration; a variable name */
|
||||
.fun { color: red } /* a function name */
|
||||
}
|
||||
|
||||
/* Use higher contrast and text-weight for printable form. */
|
||||
@media print, projection {
|
||||
.str { color: #060 }
|
||||
.kwd { color: #006; font-weight: bold }
|
||||
.com { color: #600; font-style: italic }
|
||||
.typ { color: #404; font-weight: bold }
|
||||
.lit { color: #044 }
|
||||
.pun, .opn, .clo { color: #440 }
|
||||
.tag { color: #006; font-weight: bold }
|
||||
.atn { color: #404 }
|
||||
.atv { color: #060 }
|
||||
}
|
||||
|
||||
/* SHELL / TERMINAL CODE BLOCKS */
|
||||
|
||||
code-example {
|
||||
&[language=sh], &[language=bash] {
|
||||
.pnk, .blk, .pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec {
|
||||
color: $codegreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,53 +10,23 @@
|
||||
details.overloads {
|
||||
box-shadow: none;
|
||||
|
||||
.icon-action-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
> summary {
|
||||
justify-content: space-between;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-contents {
|
||||
padding: 0;
|
||||
border-radius: 2px;
|
||||
box-shadow: none;
|
||||
|
||||
> *:not(hr) {
|
||||
margin: 16px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
summary {
|
||||
height: inherit;
|
||||
padding: 0;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
clear: left;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
@include mixins.font-size(14);
|
||||
}
|
||||
.show-all {
|
||||
display: initial;
|
||||
}
|
||||
.collapse-all {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] > summary {
|
||||
.show-all {
|
||||
display: none;
|
||||
}
|
||||
.collapse-all {
|
||||
display: initial;
|
||||
> .details-content {
|
||||
padding: 0;
|
||||
border-radius: 2px;
|
||||
box-shadow: none;
|
||||
|
||||
> *:not(hr) {
|
||||
margin: 16px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.cli-name {
|
||||
font-weight: bold;
|
||||
|
||||
.kwd { color: initial } /* override code format */
|
||||
.kwd { color: inherit; } /* override code format */
|
||||
}
|
||||
|
||||
.cli-option-syntax {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
}
|
||||
|
||||
// TERMINAL / SHELL TEXT STYLES
|
||||
&.code-shell,
|
||||
&[language="sh"],
|
||||
&[language="bash"] {
|
||||
background-color: constants.$darkgray;
|
||||
@@ -96,7 +95,7 @@
|
||||
|
||||
:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(pre) {
|
||||
> code {
|
||||
background-color: if($is-dark-theme, constants.$darkgray, rgba(constants.$lightgray, 0.5));
|
||||
background-color: if($is-dark-theme, constants.$darkgray, rgba(constants.$lightgray, 0.3));
|
||||
color: if($is-dark-theme, constants.$white, constants.$darkgray);
|
||||
}
|
||||
}
|
||||
@@ -200,7 +199,6 @@
|
||||
/* SHELL / TERMINAL CODE BLOCKS */
|
||||
|
||||
code-example {
|
||||
&.code-shell,
|
||||
&[language="sh"],
|
||||
&[language="bash"] {
|
||||
.pnk,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use '~@angular/cdk' as cdk;
|
||||
@use '~@angular/material' as mat;
|
||||
|
||||
@@ -12,13 +13,13 @@ aio-cookies-popup {
|
||||
position: fixed;
|
||||
margin: 24px;
|
||||
max-width: 430px;
|
||||
padding: $inner-spacing $inner-spacing $inner-spacing / 2;
|
||||
padding: $inner-spacing $inner-spacing math.div($inner-spacing, 2);
|
||||
z-index: cdk.$overlay-container-z-index + 1;
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: $inner-spacing $inner-spacing / -2 0 0;
|
||||
margin: $inner-spacing math.div($inner-spacing, -2) 0 0;
|
||||
|
||||
.mat-button {
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -3,9 +3,5 @@
|
||||
@mixin theme($theme) {
|
||||
details {
|
||||
box-shadow: 0 1px 4px 0 rgba(constants.$black, 0.37);
|
||||
|
||||
> summary {
|
||||
color: constants.$black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,19 @@
|
||||
* </div>
|
||||
* </details>
|
||||
* ```
|
||||
*
|
||||
* Optionally, you can use an `.actions` container inside `<summary>` to show expand/collapse
|
||||
* actions and/or a rotating icon:
|
||||
* ```html
|
||||
* <summary>
|
||||
* <span>Some title</span>
|
||||
* <span class="actions">
|
||||
* <span class="action-expand">Show more</span>
|
||||
* <span class="action-collapse">Show less</span>
|
||||
* <i class="material-icons expand">expand_more</i>
|
||||
* </span>
|
||||
* </summary>
|
||||
* ```
|
||||
*/
|
||||
|
||||
details {
|
||||
@@ -21,33 +34,57 @@ details {
|
||||
cursor: pointer;
|
||||
@include mixins.font-size(16);
|
||||
position: relative;
|
||||
padding: 16px 24px;
|
||||
height: 16px;
|
||||
display: block; // Remove the built in details marker in FF
|
||||
padding: 16px;
|
||||
height: inherit;
|
||||
display: flex; // Remove the built in details marker in FF
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&::-webkit-details-marker {
|
||||
display: none; // Remove the built in details marker in webkit
|
||||
}
|
||||
|
||||
// Rotate the icon
|
||||
i.material-icons.expand {
|
||||
@include mixins.rotate(0deg);
|
||||
|
||||
@at-root #{selector.replace(&, 'details', 'details[open]')} {
|
||||
@include mixins.rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
> h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@include mixins.font-size(14);
|
||||
|
||||
// Show/hide expand and collapse actions.
|
||||
.action-expand {
|
||||
display: initial;
|
||||
|
||||
@at-root #{selector.replace(&, 'details', 'details[open]')} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-collapse {
|
||||
display: none;
|
||||
|
||||
@at-root #{selector.replace(&, 'details', 'details[open]')} {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the expand/collapse icon.
|
||||
.material-icons.expand {
|
||||
@include mixins.rotate(0deg);
|
||||
|
||||
@at-root #{selector.replace(&, 'details', 'details[open]')} {
|
||||
@include mixins.rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-contents {
|
||||
.details-content {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
@use 'sass:color';
|
||||
@use 'sass:map';
|
||||
@use '../../constants';
|
||||
|
||||
@mixin theme($theme) {
|
||||
$is-dark-theme: map.get($theme, is-dark);
|
||||
|
||||
.roadmap-done-summary::marker {
|
||||
color: if($is-dark-theme, constants.$offwhite, color.adjust(constants.$darkgray, $lightness: 10%));
|
||||
.page-guide-roadmap {
|
||||
.completed-details {
|
||||
border-top: 1px solid if($is-dark-theme, constants.$mediumgray, constants.$lightgray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
.roadmap-done-details {
|
||||
padding: 0 20px 20px;
|
||||
margin: 0 -20px;
|
||||
}
|
||||
.page-guide-roadmap {
|
||||
.completed-details {
|
||||
box-shadow: none;
|
||||
margin-top: 4rem;
|
||||
padding: 4rem 0 2rem;
|
||||
|
||||
.roadmap-done-summary {
|
||||
margin: 0 -20px;
|
||||
font-size: 3.2rem;
|
||||
height: 40px;
|
||||
padding-bottom: 0;
|
||||
display: list-item;
|
||||
user-select: none;
|
||||
> summary {
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> .details-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.roadmap-last-updated {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use '../../mixins';
|
||||
|
||||
$tocItemLineHeight: 24;
|
||||
@@ -9,15 +10,15 @@ $tocMarkerSize: 6;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
height: #{$tocMarkerSize}px;
|
||||
left: -#{($tocMarkerSize - $tocMarkerRailSize) / 2}px;
|
||||
left: -#{math.div($tocMarkerSize - $tocMarkerRailSize, 2)}px;
|
||||
position: absolute;
|
||||
top: calc(#{$tocItemTopPadding}px + #{$tocItemLineHeight / 2}px - #{$tocMarkerSize / 2}px);
|
||||
top: calc(#{$tocItemTopPadding}px + #{$tocItemLineHeight / 2 / 10}rem - #{$tocMarkerSize / 2}px);
|
||||
top: calc(#{$tocItemTopPadding}px + #{math.div($tocItemLineHeight, 2)}px - #{math.div($tocMarkerSize, 2)}px);
|
||||
top: calc(#{$tocItemTopPadding}px + #{math.div($tocItemLineHeight, 2 * 10)}rem - #{math.div($tocMarkerSize, 2)}px);
|
||||
width: #{$tocMarkerSize}px;
|
||||
}
|
||||
|
||||
.toc-container {
|
||||
width: 18%;
|
||||
width: 18vw;
|
||||
position: fixed;
|
||||
top: 76px;
|
||||
right: 0;
|
||||
@@ -158,13 +159,13 @@ aio-toc {
|
||||
}
|
||||
|
||||
&:first-child:before {
|
||||
top: calc(#{$tocItemTopPadding}px + #{$tocItemLineHeight / 2}px - #{$tocMarkerSize / 2}px);
|
||||
top: calc(#{$tocItemTopPadding}px + #{$tocItemLineHeight / 2 / 10}rem - #{$tocMarkerSize / 2}px);
|
||||
top: calc(#{$tocItemTopPadding}px + #{math.div($tocItemLineHeight, 2)}px - #{math.div($tocMarkerSize, 2)}px);
|
||||
top: calc(#{$tocItemTopPadding}px + #{math.div($tocItemLineHeight, 2 * 10)}rem - #{math.div($tocMarkerSize, 2)}px);
|
||||
}
|
||||
|
||||
&:last-child:before {
|
||||
bottom: calc(100% - (#{$tocItemTopPadding}px + #{$tocItemLineHeight / 2}px + #{$tocMarkerSize / 2}px));
|
||||
bottom: calc(100% - (#{$tocItemTopPadding}px + #{$tocItemLineHeight / 2 / 10}rem + #{$tocMarkerSize / 2}px));
|
||||
bottom: calc(100% - (#{$tocItemTopPadding}px + #{math.div($tocItemLineHeight, 2)}px + #{math.div($tocMarkerSize, 2)}px));
|
||||
bottom: calc(100% - (#{$tocItemTopPadding}px + #{math.div($tocItemLineHeight, 2 * 10)}rem + #{math.div($tocMarkerSize, 2)}px));
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
@use 'sass:list';
|
||||
@use 'sass:math';
|
||||
@use 'sass:selector';
|
||||
@use './constants';
|
||||
|
||||
// REM Font Adjustments
|
||||
@mixin font-size($sizeValue) {
|
||||
font-size: ($sizeValue) + px;
|
||||
font-size: ($sizeValue / 10) + rem;
|
||||
font-size: math.div($sizeValue, 10) + rem;
|
||||
}
|
||||
|
||||
@mixin letter-spacing($spacingValue) {
|
||||
letter-spacing: ($spacingValue) + px;
|
||||
letter-spacing: ($spacingValue / 10) + rem;
|
||||
letter-spacing: math.div($spacingValue, 10) + rem;
|
||||
}
|
||||
|
||||
@mixin line-height($heightValue) {
|
||||
line-height: ($heightValue) + px;
|
||||
line-height: ($heightValue / 10) + rem;
|
||||
line-height: math.div($heightValue, 10) + rem;
|
||||
}
|
||||
|
||||
// PLACEHOLDER
|
||||
|
||||
@@ -5,7 +5,9 @@ describe('site App', () => {
|
||||
let page: SitePage;
|
||||
|
||||
beforeEach(async () => {
|
||||
await SitePage.setWindowWidth(1050); // Make the window wide enough to show the SideNav side-by-side.
|
||||
// Make the window wide enough to show the SideNav side-by-side
|
||||
// (bigger than the app component's showTopMenuWidth).
|
||||
await SitePage.setWindowWidth(1200);
|
||||
page = new SitePage();
|
||||
});
|
||||
|
||||
|
||||
@@ -25,9 +25,15 @@ const IGNORED_EXAMPLES = [];
|
||||
* Run Protractor End-to-End Tests for Doc Samples
|
||||
*
|
||||
* Flags
|
||||
* --filter to filter/select _example app subdir names
|
||||
* --filter to filter/select example app subdir names
|
||||
* Can be used multiple times to include multiple patterns.
|
||||
* e.g. --filter=foo // all example apps with 'foo' in their folder names.
|
||||
*
|
||||
* --exclude to exclude example app subdir names
|
||||
* Can be used multiple times to exclude multiple patterns.
|
||||
* NOTE: `--exclude` is always cosidered after `--filter`.
|
||||
* e.g. --exclude=bar // Exclude all example apps with 'bar' in their folder names.
|
||||
*
|
||||
* --setup to run yarn install, copy boilerplate and update webdriver
|
||||
* e.g. --setup
|
||||
*
|
||||
@@ -60,7 +66,7 @@ function runE2e() {
|
||||
return Promise.resolve()
|
||||
.then(
|
||||
() => findAndRunE2eTests(
|
||||
argv.filter, outputFile, argv.shard,
|
||||
argv.filter, argv.exclude, outputFile, argv.shard,
|
||||
argv.cliSpecsConcurrency || DEFAULT_CLI_SPECS_CONCURRENCY, argv.retry || 1))
|
||||
.then((status) => {
|
||||
reportStatus(status, outputFile);
|
||||
@@ -76,7 +82,9 @@ function runE2e() {
|
||||
|
||||
// Finds all of the *e2e-spec.tests under the examples folder along with the corresponding apps
|
||||
// that they should run under. Then run each app/spec collection sequentially.
|
||||
function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency, maxAttempts) {
|
||||
function findAndRunE2eTests(
|
||||
includeFilter, excludeFilter, outputFile, shard, cliSpecsConcurrency, maxAttempts) {
|
||||
const filter = {include: includeFilter, exclude: excludeFilter};
|
||||
const shardParts = shard ? shard.split('/') : [0, 1];
|
||||
const shardModulo = parseInt(shardParts[0], 10);
|
||||
const shardDivider = parseInt(shardParts[1], 10);
|
||||
@@ -84,7 +92,8 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency, maxA
|
||||
// create an output file with header.
|
||||
const startTime = new Date().getTime();
|
||||
let header = `Doc Sample Protractor Results on ${new Date().toLocaleString()}\n`;
|
||||
header += ` Filter: ${filter ? filter : 'All tests'}\n\n`;
|
||||
header += ` Include: ${filter.include || 'All tests'}\n`;
|
||||
header += ` Exclude: ${filter.exclude || 'No tests'}\n\n`;
|
||||
fs.writeFileSync(outputFile, header);
|
||||
|
||||
const status = {passed: [], failed: []};
|
||||
@@ -378,16 +387,23 @@ function getE2eSpecs(basePath, filter) {
|
||||
// Find all e2e specs in a given example folder.
|
||||
function getE2eSpecsFor(basePath, specFile, filter) {
|
||||
// Only get spec file at the example root.
|
||||
// The formatter doesn't understand nested template string expressions (honestly, neither do I).
|
||||
// clang-format off
|
||||
const e2eSpecGlob = `${filter ? `*${filter}*` : '*'}/${specFile}`;
|
||||
// clang-format on
|
||||
const e2eSpecGlob = [
|
||||
`${filter.include ? filterToGlob(filter.include) : '*'}/${specFile}`,
|
||||
`!${filter.exclude ? filterToGlob(filter.exclude) : ''}/${specFile}`,
|
||||
];
|
||||
return globby(e2eSpecGlob, {cwd: basePath, nodir: true})
|
||||
.then(
|
||||
paths => paths.filter(file => !IGNORED_EXAMPLES.some(ignored => file.startsWith(ignored)))
|
||||
.map(file => path.join(basePath, file)));
|
||||
}
|
||||
|
||||
function filterToGlob(filter) {
|
||||
// `filter` can be either a string (if there is one occurrence of the corresponding option) or an
|
||||
// array (if there are two or more occurrences of the corresponding option). In other words, if
|
||||
// `filter` is an array, it will have more than one element.
|
||||
return Array.isArray(filter) ? `*{${filter.join(',')}}*` : `*${filter}*`;
|
||||
}
|
||||
|
||||
// Load configuration for an example. Used for SystemJS
|
||||
function loadExampleConfig(exampleFolder) {
|
||||
// Default config.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<section class="description">
|
||||
<h2>Description</h2>
|
||||
{$ doc.description | trimBlankLines | marked $}
|
||||
{% if doc.usageNotes %}<p>Further information available in the <a href="#usage-notes">Usage Notes</a>...</p>{%
|
||||
{% if doc.usageNotes %}<p>Further information is available in the <a href="#usage-notes">Usage Notes...</a></p>{%
|
||||
endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
@@ -130,16 +130,14 @@
|
||||
<td>
|
||||
<details class="overloads">
|
||||
<summary>
|
||||
<div class="icon-action-header">
|
||||
<h4 class="no-anchor">{$ method.overloads.length $} overloads...</h4>
|
||||
<span class="actions">
|
||||
<span class="show-all">Show All</span>
|
||||
<span class="collapse-all">Hide All</span>
|
||||
<i class="material-icons expand">expand_more</i>
|
||||
</span>
|
||||
</div>
|
||||
<h4 class="no-anchor">{$ method.overloads.length $} overloads...</h4>
|
||||
<span class="actions">
|
||||
<span class="action-expand">Show All</span>
|
||||
<span class="action-collapse">Hide All</span>
|
||||
<i class="material-icons expand">expand_more</i>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="detail-contents">
|
||||
<div class="details-content">
|
||||
{% if method.isAbstract %}
|
||||
{$ renderOverloadInfo(method, cssClass + '-overload', method) $}
|
||||
<hr class="hr-margin fullwidth">
|
||||
|
||||
+498
-592
File diff suppressed because it is too large
Load Diff
+17
-4
@@ -1,13 +1,16 @@
|
||||
# BEGIN-INTERNAL
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
|
||||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
load("//dev-infra:index.bzl", "ng_dev_rolled_up_generated_file")
|
||||
load("//dev-infra:defaults.bzl", "ng_dev_rolled_up_generated_file", "ts_library")
|
||||
# END-INTERNAL
|
||||
|
||||
exports_files(["tsconfig.json"])
|
||||
|
||||
# BEGIN-INTERNAL
|
||||
ts_library(
|
||||
name = "cli",
|
||||
srcs = [
|
||||
"cli.ts",
|
||||
],
|
||||
module_name = "@angular/dev-infra-private",
|
||||
deps = [
|
||||
"//dev-infra/caretaker",
|
||||
"//dev-infra/commit-message",
|
||||
@@ -42,20 +45,28 @@ genrule(
|
||||
pkg_npm(
|
||||
name = "npm_package",
|
||||
srcs = [
|
||||
# Main bazel entry-point for the shared `dev-infra` package.
|
||||
"index.bzl",
|
||||
"BUILD.bazel",
|
||||
# Some tools within `dev-infra` which are shipped as Bazel rules might
|
||||
# rely on a tsconfig file. We bring the config into the NPM package.
|
||||
"tsconfig.json",
|
||||
"//dev-infra/bazel:files",
|
||||
"//dev-infra/benchmark:files",
|
||||
],
|
||||
substitutions = {
|
||||
# angular/angular should not consume it's own packages, so we use
|
||||
# substitutions to replace these in the published version of dev-infra.
|
||||
"@angular//dev-infra/": "@npm//@angular/dev-infra-private/",
|
||||
"//dev-infra/": "@npm//@angular/dev-infra-private/",
|
||||
"//dev-infra:": "@npm//@angular/dev-infra-private:",
|
||||
|
||||
# Substitutions needed for `//dev-infra/benchmark`:
|
||||
"//packages/benchpress": "@npm//@angular/benchpress",
|
||||
"//packages/bazel": "@npm//@angular/bazel",
|
||||
"//packages/zone.js/bundles:zone.umd.js": "@npm//zone.js",
|
||||
"//packages/core": "@npm//@angular/core",
|
||||
"//packages/platform-browser": "@npm//@angular/platform-browser",
|
||||
|
||||
# This substitution is particularly verbose because we need to make sure
|
||||
# that only things available via Angular Bazel are imported from
|
||||
# tools/defaults.bzl.
|
||||
@@ -68,6 +79,7 @@ pkg_npm(
|
||||
"//dev-infra/benchmark/driver-utilities",
|
||||
"//dev-infra/commit-message",
|
||||
"//dev-infra/ts-circular-dependencies",
|
||||
"//dev-infra/tslint-rules",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -101,3 +113,4 @@ ng_dev_rolled_up_generated_file(
|
||||
"//dev-infra/release/build",
|
||||
],
|
||||
)
|
||||
# END-INTERNAL
|
||||
|
||||
@@ -5,6 +5,8 @@ filegroup(
|
||||
srcs = [
|
||||
"BUILD.bazel",
|
||||
"expand_template.bzl",
|
||||
"extract_js_module_output.bzl",
|
||||
"//dev-infra/bazel/api-golden:files",
|
||||
"//dev-infra/bazel/browsers:files",
|
||||
"//dev-infra/bazel/remote-execution:files",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files([
|
||||
"index.ts",
|
||||
"index_npm_packages.ts",
|
||||
])
|
||||
|
||||
ts_library(
|
||||
name = "api-golden",
|
||||
srcs = [
|
||||
"find_entry_points.ts",
|
||||
"index.ts",
|
||||
"index_npm_packages.ts",
|
||||
"test_api_report.ts",
|
||||
],
|
||||
# A tsconfig needs to be specified as otherwise `ts_library` will look for the config
|
||||
# in `//:package.json` and this breaks when the BUILD file is copied to `@npm//`.
|
||||
tsconfig = "//dev-infra:tsconfig.json",
|
||||
deps = [
|
||||
"@npm//@bazel/runfiles",
|
||||
"@npm//@microsoft/api-extractor",
|
||||
"@npm//@types/node",
|
||||
"@npm//chalk",
|
||||
"@npm//tslib",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
# Expose the sources in the dev-infra NPM package.
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]),
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {lstatSync, readdirSync, readFileSync} from 'fs';
|
||||
import {dirname, join} from 'path';
|
||||
|
||||
/** Interface describing a resolved NPM package entry point. */
|
||||
export interface PackageEntryPoint {
|
||||
typesEntryPointPath: string;
|
||||
packageJsonPath: string;
|
||||
}
|
||||
|
||||
/** Interface describing contents of a `package.json`. */
|
||||
interface PackageJson {
|
||||
types?: string;
|
||||
typings?: string;
|
||||
}
|
||||
|
||||
/** Finds all entry points within a given NPM package directory. */
|
||||
export function findEntryPointsWithinNpmPackage(dirPath: string): PackageEntryPoint[] {
|
||||
const entryPoints: PackageEntryPoint[] = [];
|
||||
|
||||
for (const packageJsonFilePath of findPackageJsonFilesInDirectory(dirPath)) {
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonFilePath, 'utf8')) as PackageJson;
|
||||
const typesFile = packageJson.types || packageJson.typings;
|
||||
|
||||
if (typesFile) {
|
||||
entryPoints.push({
|
||||
packageJsonPath: packageJsonFilePath,
|
||||
typesEntryPointPath: join(dirname(packageJsonFilePath), typesFile),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entryPoints;
|
||||
}
|
||||
|
||||
/** Determine if the provided path is a directory. */
|
||||
function isDirectory(dirPath: string) {
|
||||
try {
|
||||
return lstatSync(dirPath).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Finds all `package.json` files within a directory. */
|
||||
function* findPackageJsonFilesInDirectory(directoryPath: string): IterableIterator<string> {
|
||||
for (const fileName of readdirSync(directoryPath)) {
|
||||
const fullPath = join(directoryPath, fileName);
|
||||
if (isDirectory(fullPath)) {
|
||||
yield* findPackageJsonFilesInDirectory(fullPath);
|
||||
} else if (fileName === 'package.json') {
|
||||
yield fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
load("//dev-infra/bazel:extract_js_module_output.bzl", "extract_js_module_output")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "nodejs_test")
|
||||
|
||||
nodejs_test_args = [
|
||||
# Needed so that node doesn't walk back to the source directory.
|
||||
# From there, the relative imports would point to .ts files.
|
||||
"--node_options=--preserve-symlinks",
|
||||
# TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver
|
||||
# See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324
|
||||
"--bazel_patch_module_resolver",
|
||||
]
|
||||
|
||||
default_strip_export_pattern = "^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDef)"
|
||||
|
||||
"""Escapes a Regular expression so that it can be passed as process argument."""
|
||||
|
||||
def _escape_regex_for_arg(value):
|
||||
return "\"%s\"" % value
|
||||
|
||||
"""
|
||||
Extracts type names from a list of NPM type targets.
|
||||
|
||||
For example: Consider the `@npm//@types/node` target. This function extracts `node`
|
||||
from the label. This is needed so that the Node types can be wired up within a
|
||||
TypeScript program using the `types` tsconfig option.
|
||||
"""
|
||||
|
||||
def extract_type_names_from_labels(type_targets):
|
||||
type_names = []
|
||||
for type_target in type_targets:
|
||||
type_package = Label(type_target).package
|
||||
|
||||
if (type_package.startswith("@types/")):
|
||||
type_names.append(type_package[len("@types/"):])
|
||||
else:
|
||||
fail("Expected type target to match the following format: " +
|
||||
"`@<npm_workspace>//@types/<name>`, but got: %s" % type_target)
|
||||
|
||||
return type_names
|
||||
|
||||
"""
|
||||
Builds an API report for the specified entry-point and compares it against the
|
||||
specified golden
|
||||
"""
|
||||
|
||||
def api_golden_test(
|
||||
name,
|
||||
golden,
|
||||
entry_point,
|
||||
data = [],
|
||||
strip_export_pattern = default_strip_export_pattern,
|
||||
types = [],
|
||||
**kwargs):
|
||||
quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern)
|
||||
|
||||
kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"]
|
||||
|
||||
# For API golden tests not running against a NPM package, we extract all transitive
|
||||
# declarations of the specified `data` targets. This is necessary because API extractor
|
||||
# needs to resolve other targets that have been linked by the Bazel NodeJS rules. The
|
||||
# linker by default only provides access to JavaScript sources, but the API extractor is
|
||||
# specifically concerned with type definitions that we can extract manually here.
|
||||
extract_js_module_output(
|
||||
name = "%s_data_typings" % name,
|
||||
deps = data,
|
||||
provider = "JSModuleInfo",
|
||||
include_declarations = True,
|
||||
include_default_files = False,
|
||||
)
|
||||
|
||||
test_data = ["//dev-infra/bazel/api-golden", "//:package.json", ":%s_data_typings" % name] + \
|
||||
data + types
|
||||
|
||||
nodejs_test(
|
||||
name = name,
|
||||
data = test_data,
|
||||
entry_point = "//dev-infra/bazel/api-golden:index.ts",
|
||||
templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern] +
|
||||
extract_type_names_from_labels(types),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = name + ".accept",
|
||||
testonly = True,
|
||||
data = test_data,
|
||||
entry_point = "//dev-infra/bazel/api-golden:index.ts",
|
||||
templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern] +
|
||||
extract_type_names_from_labels(types),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
"""
|
||||
Builds an API report for all entrypoints within the given NPM package and compares it
|
||||
against goldens within the specified directory.
|
||||
"""
|
||||
|
||||
def api_golden_test_npm_package(
|
||||
name,
|
||||
golden_dir,
|
||||
npm_package,
|
||||
data = [],
|
||||
strip_export_pattern = default_strip_export_pattern,
|
||||
types = [],
|
||||
**kwargs):
|
||||
quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern)
|
||||
|
||||
kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"]
|
||||
|
||||
nodejs_test(
|
||||
name = name,
|
||||
data = ["//dev-infra/bazel/api-golden"] + data + types,
|
||||
entry_point = "//dev-infra/bazel/api-golden:index_npm_packages.ts",
|
||||
templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern] +
|
||||
extract_type_names_from_labels(types),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = name + ".accept",
|
||||
testonly = True,
|
||||
data = ["//dev-infra/bazel/api-golden"] + data + types,
|
||||
entry_point = "//dev-infra/bazel/api-golden:index_npm_packages.ts",
|
||||
templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern] +
|
||||
extract_type_names_from_labels(types),
|
||||
**kwargs
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import * as chalk from 'chalk';
|
||||
|
||||
import {testApiGolden} from './test_api_report';
|
||||
|
||||
/**
|
||||
* Entry point for the `api_golden_test` Bazel rule. This function builds an API report for
|
||||
* the specified entry point file and compares it against the specified golden file.
|
||||
*/
|
||||
async function main(
|
||||
goldenFilePath: string, entryPointFilePath: string, approveGolden: boolean,
|
||||
stripExportPattern: RegExp, typeNames: string[]) {
|
||||
const {succeeded, apiReportChanged} = await testApiGolden(
|
||||
goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames);
|
||||
|
||||
if (!succeeded && apiReportChanged) {
|
||||
console.error(chalk.red(`The API signature has changed and the golden file is outdated.`));
|
||||
console.info(chalk.yellow(
|
||||
`Golden can be updated by running: yarn bazel run ${process.env.TEST_TARGET}.accept`));
|
||||
}
|
||||
|
||||
// Bazel expects `3` as exit code for failing tests.
|
||||
process.exitCode = succeeded ? 0 : 3;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
const goldenFilePath = runfiles.resolve(args[0]);
|
||||
const entryPointFilePath = runfiles.resolve(args[1]);
|
||||
const approveGolden = args[2] === 'true';
|
||||
const stripExportPattern = new RegExp(args[3]);
|
||||
const typeNames = args.slice(4);
|
||||
|
||||
main(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames)
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import * as chalk from 'chalk';
|
||||
import {join, relative} from 'path';
|
||||
|
||||
import {findEntryPointsWithinNpmPackage} from './find_entry_points';
|
||||
import {testApiGolden} from './test_api_report';
|
||||
|
||||
/**
|
||||
* Entry point for the `api_golden_test_npm_package` Bazel rule. This function determines
|
||||
* all types within the specified NPM package and builds API reports that will be compared
|
||||
* against golden files within the given golden directory.
|
||||
*/
|
||||
async function main(
|
||||
goldenDir: string, npmPackageDir: string, approveGolden: boolean, stripExportPattern: RegExp,
|
||||
typeNames: string[]) {
|
||||
const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir);
|
||||
const outdatedGoldens: string[] = [];
|
||||
let allTestsSucceeding = true;
|
||||
|
||||
for (const {packageJsonPath, typesEntryPointPath} of entryPoints) {
|
||||
const pkgRelativeName = relative(npmPackageDir, typesEntryPointPath);
|
||||
// API extractor generates API reports as markdown files. For each types
|
||||
// entry-point we maintain a separate golden file. These golden files are
|
||||
// based on the name of the entry-point `.d.ts` file in the NPM package,
|
||||
// but with the proper `.md` file extension.
|
||||
// See: https://api-extractor.com/pages/overview/demo_api_report/.
|
||||
const goldenName = pkgRelativeName.replace(/\.d\.ts$/, '.md');
|
||||
const goldenFilePath = join(goldenDir, goldenName);
|
||||
|
||||
const {succeeded, apiReportChanged} = await testApiGolden(
|
||||
goldenFilePath, typesEntryPointPath, approveGolden, stripExportPattern, typeNames,
|
||||
packageJsonPath);
|
||||
|
||||
// Keep track of outdated goldens.
|
||||
if (!succeeded && apiReportChanged) {
|
||||
outdatedGoldens.push(goldenName);
|
||||
}
|
||||
|
||||
allTestsSucceeding = allTestsSucceeding && succeeded;
|
||||
}
|
||||
|
||||
if (outdatedGoldens.length) {
|
||||
console.error(chalk.red(`The following goldens are outdated:`));
|
||||
outdatedGoldens.forEach(name => console.info(`- ${name}`));
|
||||
console.info();
|
||||
console.info(chalk.yellow(
|
||||
`The goldens can be updated by running: yarn bazel run ${process.env.TEST_TARGET}.accept`));
|
||||
}
|
||||
|
||||
// Bazel expects `3` as exit code for failing tests.
|
||||
process.exitCode = allTestsSucceeding ? 0 : 3;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
const goldenDir = runfiles.resolve(args[0]);
|
||||
const npmPackageDir = runfiles.resolve(args[1]);
|
||||
const approveGolden = args[2] === 'true';
|
||||
const stripExportPattern = new RegExp(args[3]);
|
||||
const typeNames = args.slice(4);
|
||||
|
||||
main(goldenDir, npmPackageDir, approveGolden, stripExportPattern, typeNames).catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import {ConsoleMessageId, Extractor, ExtractorConfig, ExtractorLogLevel, ExtractorMessage, ExtractorMessageId, ExtractorResult, IConfigFile} from '@microsoft/api-extractor';
|
||||
import {AstModule} from '@microsoft/api-extractor/lib/analyzer/AstModule';
|
||||
import {ExportAnalyzer} from '@microsoft/api-extractor/lib/analyzer/ExportAnalyzer';
|
||||
import {basename, dirname} from 'path';
|
||||
|
||||
/**
|
||||
* Original definition of the `ExportAnalyzer#fetchAstModuleExportInfo` method.
|
||||
* We store the original function since we monkey-patch it later to account for
|
||||
* specified strip export patterns.
|
||||
* */
|
||||
const _origFetchAstModuleExportInfo = ExportAnalyzer.prototype.fetchAstModuleExportInfo;
|
||||
|
||||
/**
|
||||
* Builds an API report for the given entry-point file and compares
|
||||
* it against a golden file.
|
||||
*
|
||||
* @param goldenFilePath Path to an API report file that is used as golden
|
||||
* @param indexFilePath Entry point file that is analyzed to build the API report.
|
||||
* @param approveGolden Whether the golden file should be updated.
|
||||
* @param stripExportPattern Regular Expression that can be used to filter out exports
|
||||
* from the API report.
|
||||
* @param typeNames Name of types which should be included for analysis of the entry-point.
|
||||
* Types are expected to exist within the default `node_modules/@types/` folder.
|
||||
* @param packageJsonPath Optional path to a `package.json` file that contains the entry
|
||||
* point. Note that the `package.json` is currently only used by `api-extractor` to determine
|
||||
* the package name displayed within the API golden.
|
||||
*/
|
||||
export async function testApiGolden(
|
||||
goldenFilePath: string, indexFilePath: string, approveGolden: boolean,
|
||||
stripExportPattern: RegExp, typeNames: string[] = [],
|
||||
packageJsonPath = resolveWorkspacePackageJsonPath()): Promise<ExtractorResult> {
|
||||
// If no `TEST_TMPDIR` is defined, then this script runs using `bazel run`. We use
|
||||
// the runfile directory as temporary directory for API extractor.
|
||||
const tempDir = process.env.TEST_TMPDIR ?? process.cwd();
|
||||
|
||||
const configObject: IConfigFile = {
|
||||
compiler: {
|
||||
overrideTsconfig:
|
||||
// We disable automatic `@types` resolution as this throws-off API reports
|
||||
// when the API test is run outside sandbox. Instead we expect a list of
|
||||
// hard-coded types that should be included. This works in non-sandbox too.
|
||||
{files: [indexFilePath], compilerOptions: {types: typeNames, lib: ['esnext', 'dom']}}
|
||||
},
|
||||
projectFolder: dirname(packageJsonPath),
|
||||
mainEntryPointFilePath: indexFilePath,
|
||||
dtsRollup: {enabled: false},
|
||||
docModel: {enabled: false},
|
||||
apiReport: {
|
||||
enabled: true,
|
||||
reportFolder: dirname(goldenFilePath),
|
||||
reportTempFolder: tempDir,
|
||||
reportFileName: basename(goldenFilePath),
|
||||
},
|
||||
tsdocMetadata: {enabled: false},
|
||||
newlineKind: 'lf',
|
||||
messages: {
|
||||
extractorMessageReporting: {
|
||||
// If an export does not have a release tag (like `@public`), API extractor maps
|
||||
// considers it still as `Public`. We hide the message for now given the Angular
|
||||
// repositories do not follow the TSDoc standard. https://tsdoc.org/.
|
||||
// TODO: Make this an error once TSDoc standard is followed in all projects.
|
||||
[ExtractorMessageId.MissingReleaseTag]: {logLevel: ExtractorLogLevel.None},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// We read the specified `package.json` manually and build a package name that is
|
||||
// compatible with the API extractor. This is a workaround for a bug in api-extractor.
|
||||
// TODO remove once https://github.com/microsoft/rushstack/issues/2774 is resolved.
|
||||
const packageJson = require(packageJsonPath);
|
||||
const packageNameSegments = packageJson.name.split('/');
|
||||
const packageName = packageNameSegments.length === 1 ?
|
||||
packageNameSegments[0] :
|
||||
`${packageNameSegments[0]}/${packageNameSegments.slice(1).join('_')}`;
|
||||
|
||||
const extractorConfig = ExtractorConfig.prepare({
|
||||
configObject,
|
||||
// TODO: Remove workaround once https://github.com/microsoft/rushstack/issues/2774 is fixed.
|
||||
packageJson: {name: packageName},
|
||||
packageJsonFullPath: packageJsonPath,
|
||||
configObjectFullPath: undefined,
|
||||
});
|
||||
|
||||
// This patches the `ExportAnalyzer` of `api-extractor` so that we can filter out
|
||||
// exports that match a specified pattern. Ideally this would not be needed as the
|
||||
// TSDoc JSDoc annotations could be used to filter out exports from the API report,
|
||||
// but there are cases in Angular where exports cannot be `@internal` but at the same
|
||||
// time are denoted as unstable. Such exports are allowed to change frequently and should
|
||||
// not be captured in the API report (as this would be unnecessarily inconvenient).
|
||||
ExportAnalyzer.prototype.fetchAstModuleExportInfo = function(module: AstModule) {
|
||||
const info = _origFetchAstModuleExportInfo.apply(this, [module]);
|
||||
|
||||
info.exportedLocalEntities.forEach((entity, exportName) => {
|
||||
if (stripExportPattern.test(exportName)) {
|
||||
info.exportedLocalEntities.delete(exportName);
|
||||
}
|
||||
});
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
return Extractor.invoke(extractorConfig, {
|
||||
// If the golden should be approved, then `localBuild: true` instructs
|
||||
// API extractor to update the file.
|
||||
localBuild: approveGolden,
|
||||
// Process messages from the API extractor (and modify log levels if needed).
|
||||
messageCallback: msg => processExtractorMessage(msg, approveGolden),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an API extractor message. Microsoft's API extractor allows developers to
|
||||
* handle messages before API extractor prints them. This allows us to adjust log level
|
||||
* for certain messages, or to fully prevent messages from being printed out.
|
||||
* */
|
||||
async function processExtractorMessage(message: ExtractorMessage, isApprove: boolean) {
|
||||
// If the golden does not match, we hide the error as API extractor prints
|
||||
// a warning asking the user to manually copy the new API report. We print
|
||||
// a custom warning below asking the developer to run the `.accept` Bazel target.
|
||||
// TODO: Simplify once https://github.com/microsoft/rushstack/issues/2773 is resolved.
|
||||
if (message.messageId === ConsoleMessageId.ApiReportNotCopied) {
|
||||
// Mark the message as handled so that API-extractor does not print it. We print
|
||||
// a message manually after extraction.
|
||||
message.handled = true;
|
||||
message.logLevel = isApprove ? ExtractorLogLevel.None : ExtractorLogLevel.Error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves the `package.json` of the workspace executing this action. */
|
||||
function resolveWorkspacePackageJsonPath(): string {
|
||||
const workspaceName = process.env.BAZEL_WORKSPACE!;
|
||||
return runfiles.resolve(`${workspaceName}/package.json`);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Implementation of the `browser_archive` rule."""
|
||||
|
||||
def _browser_archive_impl(ctx):
|
||||
ctx.report_progress("Downloading browser archive from: %s" % ctx.attr.url)
|
||||
ctx.download_and_extract(
|
||||
url = ctx.attr.url,
|
||||
sha256 = ctx.attr.sha256,
|
||||
)
|
||||
|
||||
# The browser archive has been downloaded and extracted. We now generate a repository
|
||||
# `BUILD.bazel` file that exposes the archive files, together with the specified
|
||||
# named files using the `browser_configure` rule.
|
||||
ctx.file("BUILD.bazel", content = """
|
||||
load("@angular//dev-infra/bazel/browsers:browser_configure.bzl", "browser_configure")
|
||||
|
||||
licenses(%s)
|
||||
|
||||
browser_configure(
|
||||
name = "metadata",
|
||||
files = glob(["**/*"]),
|
||||
named_files = %s,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""" % (str(ctx.attr.licenses), str(ctx.attr.named_files)))
|
||||
|
||||
"""
|
||||
Rule that can be used to download and unpack a browser archive in a dedicated Bazel
|
||||
repository. Additionally, files within the archive can be denoted with an unique name
|
||||
so that web tests can access browser files in a platform-agnostic way, regardless of
|
||||
which `browser_archive` repository is added as dependency.
|
||||
|
||||
As an example for the concept of denoting archive files with an unique name, consider a case
|
||||
where a a web test decides conditionally based on the current exec platform which
|
||||
`browser_archive` repository is used (e.g. mac, windows or linux). The archives are different
|
||||
for each platform. The test usually would need to determine the current platform, and know how
|
||||
each archive is structured in order to access the browser binary within the repository. By
|
||||
defining named files though, the web test could just pull a named file called `BINARY` that
|
||||
always resolves to the browser binary in a platform-agnostic way.
|
||||
|
||||
Note #1: This rule exists as an alternative to the `platform_http_file` concept
|
||||
from `rules_webtesting` because the `platform_http_file` rule does not extract the archive
|
||||
directly, but relies on later build actions to perform the unpacking. This results in less
|
||||
efficient caching because build actions are invalidated more frequently (e.g. `bazel clean).
|
||||
We also noticed that the extraction within RBE containers is rather unstable, and extracting
|
||||
the archives as part of a Bazel repository mitigates this (as extractions happens on the host).
|
||||
|
||||
Note #2: Additionally `rules_webtesting` defines a single repository for all platforms,
|
||||
where only an archive for the current host platform is pulled. This breaks cross-compilation
|
||||
because the wrong platform archive would be used for web tests that run in the exec platform.
|
||||
"""
|
||||
browser_archive = repository_rule(
|
||||
implementation = _browser_archive_impl,
|
||||
attrs = {
|
||||
"url": attr.string(
|
||||
doc = "Browser archive to download and extract.",
|
||||
mandatory = True,
|
||||
),
|
||||
"sha256": attr.string(
|
||||
doc = "SHA256 checksum for the archive.",
|
||||
mandatory = True,
|
||||
),
|
||||
"licenses": attr.string_list(
|
||||
mandatory = True,
|
||||
allow_empty = False,
|
||||
doc = """
|
||||
Licenses that apply to the archive. Will be passed to a `licenses` invocation
|
||||
within the repository. https://docs.bazel.build/versions/0.24.0/be/functions.html#licenses.
|
||||
""",
|
||||
),
|
||||
"named_files": attr.string_dict(
|
||||
doc = """
|
||||
Dictionary that maps files to unique identifiers. This is useful
|
||||
if browser archives are different on different platforms and the web
|
||||
tests would not want to care about archive-specific paths. e.g. targets
|
||||
expect a `CHROMIUM` key to point to the Chromium browser binary.
|
||||
""",
|
||||
mandatory = True,
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
load("@io_bazel_rules_webtesting//web/internal:metadata.bzl", "metadata")
|
||||
load("@io_bazel_rules_webtesting//web/internal:provider.bzl", "WebTestInfo")
|
||||
|
||||
"""Converts the specified label to a manifest path"""
|
||||
|
||||
def _label_to_manifest_path(label):
|
||||
if label.package != "":
|
||||
return "%s/%s" % (label.workspace_name, label.package)
|
||||
return label.workspace_name
|
||||
|
||||
"""Implementation of the `browser_configure` rule."""
|
||||
|
||||
def _browser_configure_impl(ctx):
|
||||
named_files = {}
|
||||
base_dir = _label_to_manifest_path(ctx.label)
|
||||
|
||||
# Update the named files to manifest paths that can be resolved
|
||||
# with Bazel runfile resolution in web tests.
|
||||
for n, p in ctx.attr.named_files.items():
|
||||
named_files[n] = base_dir + "/" + p
|
||||
|
||||
# Create a web test metadata file that will be provided as part of
|
||||
# the `WebTestInfo` provider.
|
||||
metadata.create_file(
|
||||
ctx = ctx,
|
||||
output = ctx.outputs.web_test_metadata,
|
||||
web_test_files = [
|
||||
metadata.web_test_files(ctx = ctx, named_files = named_files),
|
||||
],
|
||||
)
|
||||
|
||||
return [
|
||||
DefaultInfo(runfiles = ctx.runfiles(files = ctx.files.files)),
|
||||
WebTestInfo(metadata = ctx.outputs.web_test_metadata),
|
||||
]
|
||||
|
||||
"""
|
||||
Rule that is used in combination with the `browser_archive` rule. It captures a set
|
||||
of files which are needed for dealing with a browser. Additionally, specific files
|
||||
for the browser can be denoted with an unique name so that web tests can access browser
|
||||
files in a platform-agnostic way, regardless of which browser repository is selected.
|
||||
|
||||
The specified browser files are exposed as runfiles of the target defined through this
|
||||
rule. The unique names with their associated files are captured within a metadata file
|
||||
that is exposed through a `WebTestInfo` provider. Web tests will be able to deal with
|
||||
this metadata file to resolve browser files in a platform-agnostic way.
|
||||
|
||||
More details on this can be found in the `browser_archive` rule.
|
||||
"""
|
||||
browser_configure = rule(
|
||||
attrs = {
|
||||
"files": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
doc = "List of files which are needed for the browser.",
|
||||
),
|
||||
"named_files": attr.string_dict(
|
||||
doc = """
|
||||
Dictionary that maps files to unique identifiers. This is useful
|
||||
if browser archives are different on different platforms and the web
|
||||
tests would not want to care about archive-specific paths. e.g. targets
|
||||
expect a `CHROMIUM` key to point to the Chromium browser binary.
|
||||
""",
|
||||
mandatory = True,
|
||||
),
|
||||
},
|
||||
outputs = {"web_test_metadata": "%{name}.gen.json"},
|
||||
implementation = _browser_configure_impl,
|
||||
)
|
||||
@@ -1,17 +1,3 @@
|
||||
# Copyright 2018 The Bazel Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Pinned browser versions.
|
||||
|
||||
This function is here to make browser repositories work with cross-platform RBE.
|
||||
|
||||
@@ -1,74 +1,26 @@
|
||||
load("@io_bazel_rules_webtesting//web:web.bzl", "browser", "web_test_archive")
|
||||
load("@io_bazel_rules_webtesting//web:web.bzl", "browser")
|
||||
|
||||
# Copyright Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
################################################################################
|
||||
#
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# Override of chromium web_test_archive so that the archive is selected based on platform
|
||||
web_test_archive(
|
||||
name = "chromium_archive",
|
||||
testonly = True,
|
||||
archive = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": "@org_chromium_chromium_amd64//file",
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": "@org_chromium_chromium_macos//file",
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": "@org_chromium_chromium_windows//file",
|
||||
}),
|
||||
extract = "build",
|
||||
named_files = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": {"CHROMIUM": "chrome-linux/chrome"},
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": {"CHROMIUM": "chrome-mac/Chromium.app/Contents/MacOS/chromium"},
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": {"CHROMIUM": "chrome-win/chrome.exe"},
|
||||
}),
|
||||
visibility = ["//dev-infra/bazel/browsers:__subpackages__"],
|
||||
)
|
||||
|
||||
# Override of chromedriver web_test_archive so that the archive is selected based on platform
|
||||
web_test_archive(
|
||||
name = "chromedriver_archive",
|
||||
testonly = True,
|
||||
archive = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": "@org_chromium_chromedriver_amd64//file",
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": "@org_chromium_chromedriver_macos//file",
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": "@org_chromium_chromedriver_windows//file",
|
||||
}),
|
||||
extract = "build",
|
||||
named_files = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": {
|
||||
"CHROMEDRIVER": "chromedriver_linux64/chromedriver",
|
||||
},
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": {
|
||||
"CHROMEDRIVER": "chromedriver_mac64/chromedriver",
|
||||
},
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": {
|
||||
"CHROMEDRIVER": "chromedriver_win32/chromedriver.exe",
|
||||
},
|
||||
}),
|
||||
visibility = ["//dev-infra/bazel/browsers:__subpackages__"],
|
||||
)
|
||||
|
||||
browser(
|
||||
name = "chromium",
|
||||
metadata = "chromium.json",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":chromedriver_archive",
|
||||
":chromium_archive",
|
||||
"@io_bazel_rules_webtesting//go/wsl",
|
||||
],
|
||||
] + select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": [
|
||||
"@org_chromium_chromedriver_amd64//:metadata",
|
||||
"@org_chromium_chromium_amd64//:metadata",
|
||||
],
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": [
|
||||
"@org_chromium_chromedriver_macos//:metadata",
|
||||
"@org_chromium_chromium_macos//:metadata",
|
||||
],
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": [
|
||||
"@org_chromium_chromedriver_windows//:metadata",
|
||||
"@org_chromium_chromium_windows//:metadata",
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
load("//dev-infra/bazel/browsers:platform_http_file.bzl", "platform_http_file")
|
||||
load("//dev-infra/bazel/browsers:browser_archive_repo.bzl", "browser_archive")
|
||||
|
||||
"""
|
||||
Defines repositories for Chromium that can be used inside Karma unit tests
|
||||
@@ -9,50 +9,68 @@ def define_chromium_repositories():
|
||||
# To update to a newer version of Chromium see instructions in
|
||||
# https://github.com/angular/angular/blob/master/dev-infra/bazel/browsers/README.md.
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_chromium_chromium_amd64",
|
||||
licenses = ["notice"], # BSD 3-clause (maybe more?)
|
||||
sha256 = "36759ed6d151645d00a3a015200334edc70188b422eec51bcaa5790c8e906e27",
|
||||
# 87.0.4280
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/812847/chrome-linux.zip"],
|
||||
url = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/812847/chrome-linux.zip",
|
||||
named_files = {
|
||||
"CHROMIUM": "chrome-linux/chrome",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_chromium_chromium_macos",
|
||||
licenses = ["notice"], # BSD 3-clause (maybe more?)
|
||||
sha256 = "e10533c84ef57232975d6bde9cd28fd0354371e9556dda85e01178e6dcd56b93",
|
||||
# 87.0.4280
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/812851/chrome-mac.zip"],
|
||||
url = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/812851/chrome-mac.zip",
|
||||
named_files = {
|
||||
"CHROMIUM": "chrome-mac/Chromium.app/Contents/MacOS/chromium",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_chromium_chromium_windows",
|
||||
licenses = ["notice"], # BSD 3-clause (maybe more?)
|
||||
sha256 = "40d0dec1892d729db2f7d8f27feff762b070a02f04d4e14f4e37b97d6b7c3c8f",
|
||||
# 87.0.4280
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/812822/chrome-win.zip"],
|
||||
url = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/812822/chrome-win.zip",
|
||||
named_files = {
|
||||
"CHROMIUM": "chrome-win/chrome.exe",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_chromium_chromedriver_amd64",
|
||||
licenses = ["reciprocal"], # BSD 3-clause, ICU, MPL 1.1, libpng (BSD/MIT-like), Academic Free License v. 2.0, BSD 2-clause, MIT
|
||||
sha256 = "d859f8ecb21e26d3ddaf3f229da695bc86512f4e6c9fe32533af7a8b36783ec5",
|
||||
# 87.0.4280
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/812847/chromedriver_linux64.zip"],
|
||||
url = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/812847/chromedriver_linux64.zip",
|
||||
named_files = {
|
||||
"CHROMEDRIVER": "chromedriver_linux64/chromedriver",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_chromium_chromedriver_macos",
|
||||
licenses = ["reciprocal"], # BSD 3-clause, ICU, MPL 1.1, libpng (BSD/MIT-like), Academic Free License v. 2.0, BSD 2-clause, MIT
|
||||
sha256 = "aa7a99fa23287725d7108cc07baa94e6f0ef4171ff7b134018387a939a67d93d",
|
||||
# 87.0.4280
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/812851/chromedriver_mac64.zip"],
|
||||
url = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/812851/chromedriver_mac64.zip",
|
||||
named_files = {
|
||||
"CHROMEDRIVER": "chromedriver_mac64/chromedriver",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_chromium_chromedriver_windows",
|
||||
licenses = ["reciprocal"], # BSD 3-clause, ICU, MPL 1.1, libpng (BSD/MIT-like), Academic Free License v. 2.0, BSD 2-clause, MIT
|
||||
sha256 = "826f2bd0c50b823e7642860ed08cacf69d3756002a71ac30cdd77c68f31d2d24",
|
||||
# 87.0.4280
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/812822/chromedriver_win32.zip"],
|
||||
url = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/812822/chromedriver_win32.zip",
|
||||
named_files = {
|
||||
"CHROMEDRIVER": "chromedriver_win32/chromedriver.exe",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,52 +1,7 @@
|
||||
load("@io_bazel_rules_webtesting//web:web.bzl", "browser", "web_test_archive")
|
||||
load("@io_bazel_rules_webtesting//web:web.bzl", "browser")
|
||||
|
||||
# Copyright Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
################################################################################
|
||||
#
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# Override of firefox web_test_archive so that the archive is selected based on platform.
|
||||
web_test_archive(
|
||||
name = "firefox_archive",
|
||||
testonly = True,
|
||||
archive = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": "@org_mozilla_firefox_amd64//file",
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": "@org_mozilla_firefox_macos//file",
|
||||
}),
|
||||
extract = "build",
|
||||
named_files = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": {"FIREFOX": "firefox/firefox"},
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": {"FIREFOX": "Firefox.app/Contents/MacOS/firefox"},
|
||||
}),
|
||||
visibility = ["//dev-infra/bazel/browsers:__subpackages__"],
|
||||
)
|
||||
|
||||
# Override of geckodriver web_test_archive so that the archive is selected based on platform.
|
||||
web_test_archive(
|
||||
name = "geckodriver_archive",
|
||||
testonly = True,
|
||||
archive = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": "@org_mozilla_geckodriver_amd64//file",
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": "@org_mozilla_geckodriver_macos//file",
|
||||
}),
|
||||
extract = "build",
|
||||
named_files = {"GECKODRIVER": "geckodriver"},
|
||||
visibility = ["//dev-infra/bazel/browsers:__subpackages__"],
|
||||
)
|
||||
|
||||
browser(
|
||||
name = "firefox",
|
||||
disabled = select({
|
||||
@@ -56,14 +11,18 @@ browser(
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
metadata = "firefox.json",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": [],
|
||||
"//conditions:default": [
|
||||
"@io_bazel_rules_webtesting//go/wsl",
|
||||
":firefox_archive",
|
||||
":geckodriver_archive",
|
||||
deps = [
|
||||
"@io_bazel_rules_webtesting//go/wsl",
|
||||
] + select({
|
||||
"@io_bazel_rules_webtesting//common/conditions:linux": [
|
||||
"@org_mozilla_firefox_amd64//:metadata",
|
||||
"@org_mozilla_geckodriver_amd64//:metadata",
|
||||
],
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": [
|
||||
"@org_mozilla_firefox_macos//:metadata",
|
||||
"@org_mozilla_geckodriver_macos//:metadata",
|
||||
],
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": [],
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
load("//dev-infra/bazel/browsers:platform_http_file.bzl", "platform_http_file")
|
||||
load("//dev-infra/bazel/browsers:browser_archive_repo.bzl", "browser_archive")
|
||||
|
||||
"""
|
||||
Defines repositories for Firefox that can be used inside Karma unit tests
|
||||
@@ -9,34 +9,46 @@ def define_firefox_repositories():
|
||||
# Instructions on updating the Firefox version can be found in the `README.md` file
|
||||
# next to this file.
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_mozilla_firefox_amd64",
|
||||
licenses = ["reciprocal"], # MPL 2.0
|
||||
sha256 = "601e5a9a12ce680ecd82177c7887dae008d8f33690da43be1a690b76563cd992",
|
||||
# Firefox v84.0
|
||||
urls = ["https://ftp.mozilla.org/pub/firefox/releases/84.0/linux-x86_64/en-US/firefox-84.0.tar.bz2"],
|
||||
url = "https://ftp.mozilla.org/pub/firefox/releases/84.0/linux-x86_64/en-US/firefox-84.0.tar.bz2",
|
||||
named_files = {
|
||||
"FIREFOX": "firefox/firefox",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_mozilla_firefox_macos",
|
||||
licenses = ["reciprocal"], # MPL 2.0
|
||||
sha256 = "4c7bca050eb228f4f6f93a9895af0a87473e03c67401d1d2f1ba907faf87fefd",
|
||||
# Firefox v84.0
|
||||
urls = ["https://ftp.mozilla.org/pub/firefox/releases/84.0/mac/en-US/Firefox%2084.0.dmg"],
|
||||
url = "https://ftp.mozilla.org/pub/firefox/releases/84.0/mac/en-US/Firefox%2084.0.dmg",
|
||||
named_files = {
|
||||
"FIREFOX": "Firefox.app/Contents/MacOS/firefox",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_mozilla_geckodriver_amd64",
|
||||
licenses = ["reciprocal"], # MPL 2.0
|
||||
sha256 = "61bfc547a623d7305256611a81ecd24e6bf9dac555529ed6baeafcf8160900da",
|
||||
# Geckodriver v0.28.0
|
||||
urls = ["https://github.com/mozilla/geckodriver/releases/download/v0.28.0/geckodriver-v0.28.0-linux64.tar.gz"],
|
||||
url = "https://github.com/mozilla/geckodriver/releases/download/v0.28.0/geckodriver-v0.28.0-linux64.tar.gz",
|
||||
named_files = {
|
||||
"GECKODRIVER": "geckodriver",
|
||||
},
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
browser_archive(
|
||||
name = "org_mozilla_geckodriver_macos",
|
||||
licenses = ["reciprocal"], # MPL 2.0
|
||||
sha256 = "c288ff6db39adfd5eea0e25b4c3e71bfd9fb383eccf521cdd65f67ea78eb1761",
|
||||
# Geckodriver v0.28.0
|
||||
urls = ["https://github.com/mozilla/geckodriver/releases/download/v0.28.0/geckodriver-v0.28.0-macos.tar.gz"],
|
||||
url = "https://github.com/mozilla/geckodriver/releases/download/v0.28.0/geckodriver-v0.28.0-macos.tar.gz",
|
||||
named_files = {
|
||||
"GECKODRIVER": "geckodriver",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
load(
|
||||
"@io_bazel_rules_webtesting//web/internal:platform_http_file.bzl",
|
||||
_platform_http_file = "platform_http_file",
|
||||
)
|
||||
|
||||
def platform_http_file(name, licenses, sha256, urls):
|
||||
"""Platform specific browser repository.
|
||||
|
||||
This works around a deficiency in io_bazel_rules_webtesting platform_http_file in that
|
||||
it selects the platform when the repository rule is executed. This limits browsers
|
||||
tests to run on the local user platform only. For cross-platform RBE we want a repository
|
||||
to be defined per platform so the correct one can be selected.
|
||||
"""
|
||||
|
||||
_platform_http_file(
|
||||
name = name,
|
||||
amd64_sha256 = sha256,
|
||||
amd64_urls = urls,
|
||||
licenses = licenses,
|
||||
macos_sha256 = sha256,
|
||||
macos_urls = urls,
|
||||
windows_sha256 = sha256,
|
||||
windows_urls = urls,
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "JSEcmaScriptModuleInfo", "JSModuleInfo", "JSNamedModuleInfo")
|
||||
|
||||
"""Converts a provider name to its actually Starlark provider instance."""
|
||||
|
||||
def _name_to_js_module_provider(name):
|
||||
if name == "JSModuleInfo":
|
||||
return JSModuleInfo
|
||||
elif name == "JSNamedModuleInfo":
|
||||
return JSNamedModuleInfo
|
||||
elif name == "JSEcmaScriptModuleInfo":
|
||||
return JSEcmaScriptModuleInfo
|
||||
fail("Unexpected JavaScript module provider.")
|
||||
|
||||
"""Implementation of the extract_js_module_output rule."""
|
||||
|
||||
def _extract_js_module_output_impl(ctx):
|
||||
js_module_provider = _name_to_js_module_provider(ctx.attr.provider)
|
||||
depsets = []
|
||||
for dep in ctx.attr.deps:
|
||||
# Include JavaScript sources (including transitive outputs) based on the
|
||||
# configured JavaScript module provider.
|
||||
if js_module_provider in dep:
|
||||
depsets.append(dep[js_module_provider].sources)
|
||||
|
||||
# Based on whether declarations should be collected, extract direct
|
||||
# and transitive declaration files using the `DeclarationInfo` provider.
|
||||
if ctx.attr.include_declarations and DeclarationInfo in dep:
|
||||
depsets.append(dep[DeclarationInfo].transitive_declarations)
|
||||
|
||||
# Based on whether default files should be collected, extract direct
|
||||
# files which are exposed using the `DefaultInfo` provider. Also include
|
||||
# data runfiles which are needed for the current target.
|
||||
# https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html#data_runfiles
|
||||
if ctx.attr.include_default_files and DefaultInfo in dep:
|
||||
depsets.append(dep[DefaultInfo].files)
|
||||
depsets.append(dep[DefaultInfo].data_runfiles.files)
|
||||
|
||||
sources = depset(transitive = depsets)
|
||||
|
||||
return [DefaultInfo(files = sources)]
|
||||
|
||||
"""
|
||||
Rule that collects declared JavaScript module output files from a list of dependencies
|
||||
based on a configurable JavaScript module provider. The extracted outputs are exposed
|
||||
within the `DefaultInfo` provider. Targets defined using this rule can be used as input
|
||||
for rules that require JavaScript sources, or if there are multiple JavaScript output
|
||||
variants defined for a target while for example only the `JSModule` outputs are of interest.
|
||||
|
||||
As an example: This rule is helpful in combination with `ts_library` and `ng_module` as
|
||||
those rule expose multiple output flavors (which are distinguishable by the JavaScript module
|
||||
providers as imported from `providers.bzl`). i.e. these rules expose flavors for named AMD
|
||||
modules and ECMAScript module output. For reference:
|
||||
https://github.com/bazelbuild/rules_nodejs/blob/stable/packages/typescript/internal/build_defs.bzl#L334-L337
|
||||
"""
|
||||
extract_js_module_output = rule(
|
||||
implementation = _extract_js_module_output_impl,
|
||||
attrs = {
|
||||
"deps": attr.label_list(
|
||||
allow_files = True,
|
||||
),
|
||||
"provider": attr.string(
|
||||
doc = "JavaScript module info provider that is used for collecting sources from the dependencies.",
|
||||
mandatory = True,
|
||||
values = ["JSModuleInfo", "JSNamedModuleInfo", "JSEcmaScriptModuleInfo"],
|
||||
),
|
||||
"include_declarations": attr.bool(
|
||||
mandatory = True,
|
||||
doc = "Whether declaration files should be collected from the dependencies.",
|
||||
),
|
||||
"include_default_files": attr.bool(
|
||||
mandatory = True,
|
||||
doc = """
|
||||
Whether files from the `DefaultInfo` provider should be collected. Includes
|
||||
data runfiles needed for the default outputs from dependencies.
|
||||
""",
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -1,11 +1,10 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
load("//dev-infra:defaults.bzl", "ts_library")
|
||||
|
||||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "driver-utilities",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "@angular/dev-infra-private/benchmark/driver-utilities",
|
||||
tsconfig = "//dev-infra/benchmark/component_benchmark:tsconfig-e2e.json",
|
||||
deps = [
|
||||
"//packages/benchpress",
|
||||
|
||||
@@ -71,6 +71,10 @@ def ng_rollup_bundle(
|
||||
silent = True,
|
||||
format = format,
|
||||
sourcemap = "true",
|
||||
# TODO(devversion): consider removing when View Engine is removed. View Engine
|
||||
# uses Bazel manifest path imports in generated factory files.
|
||||
# e.g. `import "<workspace_root>/<..>/some_file";`
|
||||
link_workspace_root = True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ const plugins = [
|
||||
// https://github.com/angular/angular-cli/blob/1a1ceb609b9a87c4021cce3a6f0fc6d167cd09d2/packages/ngtools/webpack/src/angular_compiler_plugin.ts#L918-L920
|
||||
mainFields: ivyEnabled ? ['module_ivy_ngcc', 'main_ivy_ngcc', 'module', 'main'] :
|
||||
['module', 'main'],
|
||||
preferBuiltins: true,
|
||||
}),
|
||||
commonjs({ignoreGlobal: true}),
|
||||
sourcemaps(),
|
||||
@@ -62,6 +63,7 @@ if (useBuildOptimizer) {
|
||||
|
||||
module.exports = {
|
||||
plugins,
|
||||
onwarn: customWarningHandler,
|
||||
external: [TMPL_external],
|
||||
output: {
|
||||
globals: {TMPL_globals},
|
||||
@@ -69,6 +71,18 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
/** Custom warning handler for Rollup. */
|
||||
function customWarningHandler(warning, defaultHandler) {
|
||||
// If rollup is unable to resolve an import, we want to throw an error
|
||||
// instead of silently treating the import as external dependency.
|
||||
// https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency
|
||||
if (warning.code === 'UNRESOLVED_IMPORT') {
|
||||
throw Error(`Unresolved import: ${warning.message}`);
|
||||
}
|
||||
|
||||
defaultHandler(warning);
|
||||
}
|
||||
|
||||
/** Extracts the top-level bundle banner if specified. */
|
||||
function extractBannerIfConfigured() {
|
||||
if (!bannerFile) {
|
||||
|
||||
@@ -64,8 +64,12 @@ var GithubClient = /** @class */ (function () {
|
||||
this.repos = this._octokit.repos;
|
||||
this.issues = this._octokit.issues;
|
||||
this.git = this._octokit.git;
|
||||
this.paginate = this._octokit.paginate;
|
||||
this.rateLimit = this._octokit.rateLimit;
|
||||
this.teams = this._octokit.teams;
|
||||
// Note: These are properties from `Octokit` that are brought in by optional plugins.
|
||||
// TypeScript requires us to provide an explicit type for these.
|
||||
this.rest = this._octokit.rest;
|
||||
this.paginate = this._octokit.paginate;
|
||||
}
|
||||
return GithubClient;
|
||||
}());
|
||||
@@ -258,6 +262,16 @@ var GitClient = /** @class */ (function () {
|
||||
}
|
||||
return new semver.SemVer(latestTag, semVerOptions);
|
||||
};
|
||||
/** Retrieves the git tag matching the provided SemVer, if it exists. */
|
||||
GitClient.prototype.getMatchingTagForSemver = function (semver$1) {
|
||||
var semVerOptions = { loose: true };
|
||||
var tags = this.runGraceful(['tag', '--sort=-committerdate', '--merged']).stdout.split('\n');
|
||||
var matchingTag = tags.find(function (tag) { var _a; return ((_a = semver.parse(tag, semVerOptions)) === null || _a === void 0 ? void 0 : _a.compare(semver$1)) === 0; });
|
||||
if (matchingTag === undefined) {
|
||||
throw new Error("Unable to find a tag for the version: \"" + semver$1.format() + "\"");
|
||||
}
|
||||
return matchingTag;
|
||||
};
|
||||
/** Retrieve a list of all files in the repository changed since the provided shaOrRef. */
|
||||
GitClient.prototype.allChangesFilesSince = function (shaOrRef) {
|
||||
if (shaOrRef === void 0) { shaOrRef = 'HEAD'; }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "jasmine_node_test")
|
||||
load("//dev-infra:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "caretaker",
|
||||
@@ -7,14 +6,15 @@ ts_library(
|
||||
["**/*.ts"],
|
||||
exclude = ["**/*.spec.ts"],
|
||||
),
|
||||
module_name = "@angular/dev-infra-private/caretaker",
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"//dev-infra/release/versioning",
|
||||
"//dev-infra/utils",
|
||||
"@npm//@types/inquirer",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/node-fetch",
|
||||
"@npm//@types/yargs",
|
||||
"@npm//inquirer",
|
||||
"@npm//multimatch",
|
||||
"@npm//node-fetch",
|
||||
"@npm//typed-graphqlify",
|
||||
@@ -41,7 +41,6 @@ ts_library(
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular_es5"],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
@@ -13,10 +13,10 @@ const exampleData = 'this is example data' as const;
|
||||
|
||||
/** A simple usage of the BaseModule to illustrate the workings built into the abstract class. */
|
||||
class ConcreteBaseModule extends BaseModule<typeof exampleData> {
|
||||
async retrieveData() {
|
||||
override async retrieveData() {
|
||||
return exampleData;
|
||||
}
|
||||
async printToTerminal() {}
|
||||
override async printToTerminal() {}
|
||||
}
|
||||
|
||||
describe('BaseModule', () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ type CiData = {
|
||||
}[];
|
||||
|
||||
export class CiModule extends BaseModule<CiData> {
|
||||
async retrieveData() {
|
||||
override async retrieveData() {
|
||||
const gitRepoWithApi = {api: this.git.github, ...this.git.remoteConfig};
|
||||
const releaseTrains = await fetchActiveReleaseTrains(gitRepoWithApi);
|
||||
|
||||
@@ -52,7 +52,7 @@ export class CiModule extends BaseModule<CiData> {
|
||||
return await Promise.all(ciResultPromises);
|
||||
}
|
||||
|
||||
async printToTerminal() {
|
||||
override async printToTerminal() {
|
||||
const data = await this.data;
|
||||
const minLabelLength = Math.max(...data.map(result => result.label.length));
|
||||
info.group(bold(`CI`));
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface G3StatsData {
|
||||
}
|
||||
|
||||
export class G3Module extends BaseModule<G3StatsData|void> {
|
||||
async retrieveData() {
|
||||
override async retrieveData() {
|
||||
const toCopyToG3 = this.getG3FileIncludeAndExcludeLists();
|
||||
const latestSha = this.getLatestShas();
|
||||
|
||||
@@ -35,7 +35,7 @@ export class G3Module extends BaseModule<G3StatsData|void> {
|
||||
latestSha.g3, latestSha.master, toCopyToG3.include, toCopyToG3.exclude);
|
||||
}
|
||||
|
||||
async printToTerminal() {
|
||||
override async printToTerminal() {
|
||||
const stats = await this.data;
|
||||
if (!stats) {
|
||||
return;
|
||||
|
||||
@@ -45,7 +45,7 @@ type GithubQueryResult = {
|
||||
const MAX_RETURNED_ISSUES = 20;
|
||||
|
||||
export class GithubQueriesModule extends BaseModule<GithubQueryResults|void> {
|
||||
async retrieveData() {
|
||||
override async retrieveData() {
|
||||
// Non-null assertion is used here as the check for undefined immediately follows to confirm the
|
||||
// assertion. Typescript's type filtering does not seem to work as needed to understand
|
||||
// whether githubQueries is undefined or not.
|
||||
@@ -95,7 +95,7 @@ export class GithubQueriesModule extends BaseModule<GithubQueryResults|void> {
|
||||
return graphqlQuery;
|
||||
}
|
||||
|
||||
async printToTerminal() {
|
||||
override async printToTerminal() {
|
||||
const queryResults = await this.data;
|
||||
if (!queryResults) {
|
||||
return;
|
||||
|
||||
@@ -45,11 +45,11 @@ export const services: ServiceConfig[] = [
|
||||
];
|
||||
|
||||
export class ServicesModule extends BaseModule<StatusCheckResult[]> {
|
||||
async retrieveData() {
|
||||
override async retrieveData() {
|
||||
return Promise.all(services.map(service => this.getStatusFromStandardApi(service)));
|
||||
}
|
||||
|
||||
async printToTerminal() {
|
||||
override async printToTerminal() {
|
||||
const statuses = await this.data;
|
||||
const serviceNameMinLength = Math.max(...statuses.map(service => service.name.length));
|
||||
info.group(bold('Service Statuses'));
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
import {Argv} from 'yargs';
|
||||
import {CheckModule} from './check/cli';
|
||||
import {HandoffModule} from './handoff/cli';
|
||||
|
||||
|
||||
/** Build the parser for the caretaker commands. */
|
||||
export function buildCaretakerParser(yargs: Argv) {
|
||||
return yargs.command(CheckModule);
|
||||
return yargs.command(CheckModule).command(HandoffModule);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,13 @@
|
||||
import {assertNoErrors, getConfig, NgDevConfig} from '../utils/config';
|
||||
|
||||
export interface CaretakerConfig {
|
||||
/** Github queries showing a snapshot of pulls/issues caretakers need to monitor. */
|
||||
githubQueries?: {name: string; query: string;}[];
|
||||
/**
|
||||
* The Github group used to track current caretakers. A second group is assumed to exist with the
|
||||
* name "<group-name>-roster" containing a list of all users eligible for the caretaker group.
|
||||
* */
|
||||
caretakerGroup?: string;
|
||||
}
|
||||
|
||||
/** Retrieve and validate the config as `CaretakerConfig`. */
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Argv, CommandModule} from 'yargs';
|
||||
|
||||
import {addGithubTokenOption} from '../../utils/git/github-yargs';
|
||||
|
||||
import {updateCaretakerTeamViaPrompt} from './update-github-team';
|
||||
|
||||
|
||||
export interface CaretakerHandoffOptions {
|
||||
githubToken: string;
|
||||
}
|
||||
|
||||
/** Builds the command. */
|
||||
function builder(yargs: Argv) {
|
||||
return addGithubTokenOption(yargs);
|
||||
}
|
||||
|
||||
/** Handles the command. */
|
||||
async function handler() {
|
||||
await updateCaretakerTeamViaPrompt();
|
||||
}
|
||||
|
||||
/** yargs command module for assisting in handing off caretaker. */
|
||||
export const HandoffModule: CommandModule<{}, CaretakerHandoffOptions> = {
|
||||
handler,
|
||||
builder,
|
||||
command: 'handoff',
|
||||
describe: 'Run a handoff assistant to aide in moving to the next caretaker',
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {prompt} from 'inquirer';
|
||||
|
||||
import {debug, green, info, red, yellow} from '../../utils/console';
|
||||
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client';
|
||||
import {getCaretakerConfig} from '../config';
|
||||
|
||||
/** Update the Github caretaker group, using a prompt to obtain the new caretaker group members. */
|
||||
export async function updateCaretakerTeamViaPrompt() {
|
||||
/** Caretaker specific configuration. */
|
||||
const caretakerConfig = getCaretakerConfig().caretaker;
|
||||
|
||||
if (caretakerConfig.caretakerGroup === undefined) {
|
||||
throw Error('`caretakerGroup` is not defined in the `caretaker` config');
|
||||
}
|
||||
|
||||
/** The list of current members in the group. */
|
||||
const current = await getGroupMembers(caretakerConfig.caretakerGroup);
|
||||
/** The list of members able to be added to the group as defined by a separate roster group. */
|
||||
const roster = await getGroupMembers(`${caretakerConfig.caretakerGroup}-roster`);
|
||||
const {
|
||||
/** The list of users selected to be members of the caretaker group. */
|
||||
selected,
|
||||
/** Whether the user positively confirmed the selected made. */
|
||||
confirm
|
||||
} =
|
||||
await prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
choices: roster,
|
||||
message: 'Select 2 caretakers for the upcoming rotation:',
|
||||
default: current,
|
||||
name: 'selected',
|
||||
prefix: '',
|
||||
validate: (selected: string[]) => {
|
||||
if (selected.length !== 2) {
|
||||
return 'Please select exactly 2 caretakers for the upcoming rotation.';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
default: true,
|
||||
prefix: '',
|
||||
message: 'Are you sure?',
|
||||
name: 'confirm',
|
||||
}
|
||||
]);
|
||||
|
||||
if (confirm === false) {
|
||||
info(yellow(' ⚠ Skipping caretaker group update.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (JSON.stringify(selected) === JSON.stringify(current)) {
|
||||
info(green(' √ Caretaker group already up to date.'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await setCaretakerGroup(caretakerConfig.caretakerGroup, selected);
|
||||
} catch {
|
||||
info(red(' ✘ Failed to update caretaker group.'));
|
||||
return;
|
||||
}
|
||||
info(green(' √ Successfully updated caretaker group'));
|
||||
}
|
||||
|
||||
|
||||
/** Retrieve the current list of members for the provided group. */
|
||||
async function getGroupMembers(group: string) {
|
||||
/** The authenticated GitClient instance. */
|
||||
const git = AuthenticatedGitClient.get();
|
||||
|
||||
return (await git.github.teams.listMembersInOrg({
|
||||
org: git.remoteConfig.owner,
|
||||
team_slug: group,
|
||||
}))
|
||||
.data.filter(_ => !!_)
|
||||
.map(member => member!.login);
|
||||
}
|
||||
|
||||
async function setCaretakerGroup(group: string, members: string[]) {
|
||||
/** The authenticated GitClient instance. */
|
||||
const git = AuthenticatedGitClient.get();
|
||||
/** The full name of the group <org>/<group name>. */
|
||||
const fullSlug = `${git.remoteConfig.owner}/${group}`;
|
||||
/** The list of current members of the group. */
|
||||
const current = await getGroupMembers(group);
|
||||
/** The list of users to be removed from the group. */
|
||||
const removed = current.filter(login => !members.includes(login));
|
||||
/** Add a user to the group. */
|
||||
const add = async (username: string) => {
|
||||
debug(`Adding ${username} to ${fullSlug}.`);
|
||||
await git.github.teams.addOrUpdateMembershipForUserInOrg({
|
||||
org: git.remoteConfig.owner,
|
||||
team_slug: group,
|
||||
username,
|
||||
role: 'maintainer',
|
||||
});
|
||||
};
|
||||
/** Remove a user from the group. */
|
||||
const remove = async (username: string) => {
|
||||
debug(`Removing ${username} from ${fullSlug}.`);
|
||||
await git.github.teams.removeMembershipForUserInOrg({
|
||||
org: git.remoteConfig.owner,
|
||||
team_slug: group,
|
||||
username,
|
||||
});
|
||||
};
|
||||
|
||||
debug.group(`Caretaker Group: ${fullSlug}`);
|
||||
debug(`Current Membership: ${current.join(', ')}`);
|
||||
debug(`New Membership: ${members.join(', ')}`);
|
||||
debug(`Removed: ${removed.join(', ')}`);
|
||||
debug.groupEnd();
|
||||
|
||||
// Add members before removing to prevent the account performing the action from removing their
|
||||
// permissions to change the group membership early.
|
||||
await Promise.all(members.map(add));
|
||||
await Promise.all(removed.map(remove));
|
||||
|
||||
debug(`Successfuly updated ${fullSlug}`);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user